/* Technitium DNS Server Copyright (C) 2018 Shreyas Zare (shreyas@technitium.com) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; using System.Threading; using TechnitiumLibrary.IO; using TechnitiumLibrary.Net; using TechnitiumLibrary.Net.Dns; using TechnitiumLibrary.Net.Proxy; namespace DnsServerCore { public class DnsServer { #region enum enum ServiceState { Stopped = 0, Running = 1, Stopping = 2 } #endregion #region variables const int UDP_LISTENER_THREAD_COUNT = 3; const int TCP_SOCKET_SEND_TIMEOUT = 10000; const int TCP_SOCKET_RECV_TIMEOUT = 10000; readonly IPEndPoint _localEP; Socket _udpListener; Thread[] _udpListenerThreads; Socket _tcpListener; Thread _tcpListenerThread; readonly Zone _authoritativeZoneRoot = new Zone(true); readonly Zone _cacheZoneRoot = new Zone(false); readonly Zone _allowedZoneRoot = new Zone(true); Zone _blockedZoneRoot = new Zone(true); readonly IDnsCache _dnsCache; bool _allowRecursion = false; bool _allowRecursionOnlyForPrivateNetworks = false; NetProxy _proxy; NameServerAddress[] _forwarders; DnsClientProtocol _forwarderProtocol = DnsClientProtocol.Udp; bool _preferIPv6 = false; int _retries = 1; int _timeout = 2000; int _maxStackCount = 10; LogManager _log; LogManager _queryLog; StatsManager _stats; readonly ConcurrentDictionary _recursiveQueryLocks = new ConcurrentDictionary(); volatile ServiceState _state = ServiceState.Stopped; #endregion #region constructor static DnsServer() { //set min threads since the default value is too small { int minWorker = Environment.ProcessorCount * 64; int minIOC = Environment.ProcessorCount * 64; ThreadPool.SetMinThreads(minWorker, minIOC); } if (ServicePointManager.DefaultConnectionLimit < 512) ServicePointManager.DefaultConnectionLimit = 512; //concurrent http request limit required when using DNS-over-HTTPS } public DnsServer() : this(new IPEndPoint(IPAddress.IPv6Any, 53)) { } public DnsServer(IPAddress localIP) : this(new IPEndPoint(localIP, 53)) { } public DnsServer(IPEndPoint localEP) { _localEP = localEP; _dnsCache = new DnsCache(_cacheZoneRoot); } #endregion #region private private void ReadUdpQueryPacketsAsync(object parameter) { EndPoint remoteEP; byte[] recvBuffer = new byte[512]; int bytesRecv; if (_udpListener.AddressFamily == AddressFamily.InterNetwork) remoteEP = new IPEndPoint(IPAddress.Any, 0); else remoteEP = new IPEndPoint(IPAddress.IPv6Any, 0); try { while (true) { try { bytesRecv = _udpListener.ReceiveFrom(recvBuffer, ref remoteEP); } catch (SocketException ex) { switch (ex.SocketErrorCode) { case SocketError.ConnectionReset: case SocketError.HostUnreachable: case SocketError.MessageSize: case SocketError.NetworkReset: bytesRecv = 0; break; default: throw; } } if (bytesRecv > 0) { try { ThreadPool.QueueUserWorkItem(ProcessUdpRequestAsync, new object[] { remoteEP, new DnsDatagram(new MemoryStream(recvBuffer, 0, bytesRecv, false)) }); } catch (Exception ex) { LogManager log = _log; if (log != null) log.Write(remoteEP as IPEndPoint, ex); } } } } catch (ThreadAbortException) { //server stopping } catch (Exception ex) { LogManager log = _log; if (log != null) log.Write(remoteEP as IPEndPoint, ex); if (_state == ServiceState.Running) throw; } } private void ProcessUdpRequestAsync(object parameter) { object[] parameters = parameter as object[]; EndPoint remoteEP = parameters[0] as EndPoint; DnsDatagram request = parameters[1] as DnsDatagram; try { DnsDatagram response = ProcessQuery(request, remoteEP); //send response if (response != null) { byte[] sendBuffer = new byte[512]; MemoryStream sendBufferStream = new MemoryStream(sendBuffer); try { response.WriteTo(sendBufferStream); } catch (NotSupportedException) { DnsHeader header = response.Header; response = new DnsDatagram(new DnsHeader(header.Identifier, true, header.OPCODE, header.AuthoritativeAnswer, true, header.RecursionDesired, header.RecursionAvailable, header.AuthenticData, header.CheckingDisabled, header.RCODE, header.QDCOUNT, 0, 0, 0), response.Question, null, null, null); sendBufferStream.Position = 0; response.WriteTo(sendBufferStream); } //send dns datagram _udpListener.SendTo(sendBuffer, 0, (int)sendBufferStream.Position, SocketFlags.None, remoteEP); LogManager queryLog = _queryLog; if (queryLog != null) queryLog.Write(remoteEP as IPEndPoint, false, request, response); StatsManager stats = _stats; if (stats != null) stats.Update(response, (remoteEP as IPEndPoint).Address); } } catch (Exception ex) { LogManager queryLog = _queryLog; if (queryLog != null) queryLog.Write(remoteEP as IPEndPoint, false, request, null); LogManager log = _log; if (log != null) log.Write(remoteEP as IPEndPoint, ex); } } private void AcceptTcpConnectionAsync(object parameter) { try { while (true) { Socket socket = _tcpListener.Accept(); socket.NoDelay = true; socket.SendTimeout = TCP_SOCKET_SEND_TIMEOUT; socket.ReceiveTimeout = TCP_SOCKET_RECV_TIMEOUT; ThreadPool.QueueUserWorkItem(ProcessTcpRequestAsync, socket); } } catch (ThreadAbortException) { //server stopping } catch (Exception ex) { LogManager log = _log; if (log != null) log.Write(_localEP, ex); if (_state == ServiceState.Running) throw; } } private void ProcessTcpRequestAsync(object parameter) { Socket tcpSocket = parameter as Socket; DnsDatagram request = null; try { NetworkStream recvStream = new NetworkStream(tcpSocket); OffsetStream recvDatagramStream = new OffsetStream(recvStream, 0, 0); MemoryStream sendBufferStream = null; byte[] sendBuffer = null; ushort length; while (true) { //read dns datagram length { byte[] lengthBuffer = recvStream.ReadBytes(2); Array.Reverse(lengthBuffer, 0, 2); length = BitConverter.ToUInt16(lengthBuffer, 0); } //read dns datagram recvDatagramStream.Reset(0, length, 0); request = new DnsDatagram(recvDatagramStream); DnsDatagram response = ProcessQuery(request, tcpSocket.RemoteEndPoint); //send response if (response != null) { if (sendBufferStream == null) sendBufferStream = new MemoryStream(64); //write dns datagram sendBufferStream.Position = 0; response.WriteTo(sendBufferStream); //prepare final buffer length = Convert.ToUInt16(sendBufferStream.Position); if ((sendBuffer == null) || (sendBuffer.Length < length + 2)) sendBuffer = new byte[length + 2]; //copy datagram length byte[] lengthBuffer = BitConverter.GetBytes(length); sendBuffer[0] = lengthBuffer[1]; sendBuffer[1] = lengthBuffer[0]; //copy datagram sendBufferStream.Position = 0; sendBufferStream.Read(sendBuffer, 2, length); //send dns datagram tcpSocket.Send(sendBuffer, 0, length + 2, SocketFlags.None); LogManager queryLog = _queryLog; if (queryLog != null) queryLog.Write(tcpSocket.RemoteEndPoint as IPEndPoint, true, request, response); StatsManager stats = _stats; if (stats != null) stats.Update(response, (tcpSocket.RemoteEndPoint as IPEndPoint).Address); } } } catch (IOException) { //ignore IO exceptions } catch (Exception ex) { LogManager queryLog = _queryLog; if ((queryLog != null) && (request != null)) queryLog.Write(tcpSocket.RemoteEndPoint as IPEndPoint, true, request, null); LogManager log = _log; if (log != null) log.Write(tcpSocket.RemoteEndPoint as IPEndPoint, ex); } finally { if (tcpSocket != null) tcpSocket.Dispose(); } } private bool IsRecursionAllowed(EndPoint remoteEP) { if (!_allowRecursion) return false; if (_allowRecursionOnlyForPrivateNetworks) { switch (remoteEP.AddressFamily) { case AddressFamily.InterNetwork: case AddressFamily.InterNetworkV6: return NetUtilities.IsPrivateIP((remoteEP as IPEndPoint).Address); default: return false; } } return true; } private DnsDatagram ProcessQuery(DnsDatagram request, EndPoint remoteEP) { if (request.Header.IsResponse) return null; bool isRecursionAllowed = IsRecursionAllowed(remoteEP); switch (request.Header.OPCODE) { case DnsOpcode.StandardQuery: if ((request.Question.Length != 1) || (request.Question[0].Class != DnsClass.IN)) return new DnsDatagram(new DnsHeader(request.Header.Identifier, true, DnsOpcode.StandardQuery, false, false, request.Header.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.Refused, request.Header.QDCOUNT, 0, 0, 0), request.Question, null, null, null); switch (request.Question[0].Type) { case DnsResourceRecordType.IXFR: case DnsResourceRecordType.AXFR: case DnsResourceRecordType.MAILB: case DnsResourceRecordType.MAILA: return new DnsDatagram(new DnsHeader(request.Header.Identifier, true, DnsOpcode.StandardQuery, false, false, request.Header.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.Refused, request.Header.QDCOUNT, 0, 0, 0), request.Question, null, null, null); } try { //query authoritative zone DnsDatagram authoritativeResponse = ProcessAuthoritativeQuery(request, isRecursionAllowed); if ((authoritativeResponse.Header.RCODE != DnsResponseCode.Refused) || !request.Header.RecursionDesired || !isRecursionAllowed) return authoritativeResponse; //query blocked zone DnsDatagram blockedResponse = _blockedZoneRoot.Query(request); if (blockedResponse.Header.RCODE != DnsResponseCode.Refused) { //query allowed zone DnsDatagram allowedResponse = _allowedZoneRoot.Query(request); if (allowedResponse.Header.RCODE == DnsResponseCode.Refused) { //request domain not in allowed zone if (blockedResponse.Header.RCODE == DnsResponseCode.NameError) { DnsResourceRecord[] answer; DnsResourceRecord[] authority; switch (blockedResponse.Question[0].Type) { case DnsResourceRecordType.A: answer = new DnsResourceRecord[] { new DnsResourceRecord(blockedResponse.Question[0].Name, DnsResourceRecordType.A, blockedResponse.Question[0].Class, 60, new DnsARecord(IPAddress.Any)) }; authority = new DnsResourceRecord[] { }; break; case DnsResourceRecordType.AAAA: answer = new DnsResourceRecord[] { new DnsResourceRecord(blockedResponse.Question[0].Name, DnsResourceRecordType.AAAA, blockedResponse.Question[0].Class, 60, new DnsAAAARecord(IPAddress.IPv6Any)) }; authority = new DnsResourceRecord[] { }; break; default: answer = blockedResponse.Answer; authority = blockedResponse.Authority; break; } blockedResponse = new DnsDatagram(new DnsHeader(blockedResponse.Header.Identifier, true, blockedResponse.Header.OPCODE, false, false, blockedResponse.Header.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, blockedResponse.Header.QDCOUNT, (ushort)answer.Length, (ushort)authority.Length, 0), blockedResponse.Question, answer, authority, null); } //return blocked response blockedResponse.Tag = "blocked"; return blockedResponse; } } //do recursive query return ProcessRecursiveQuery(request); } catch (Exception ex) { LogManager log = _log; if (log != null) log.Write(remoteEP as IPEndPoint, ex); return new DnsDatagram(new DnsHeader(request.Header.Identifier, true, DnsOpcode.StandardQuery, false, false, request.Header.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.ServerFailure, request.Header.QDCOUNT, 0, 0, 0), request.Question, null, null, null); } default: return new DnsDatagram(new DnsHeader(request.Header.Identifier, true, request.Header.OPCODE, false, false, request.Header.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.Refused, request.Header.QDCOUNT, 0, 0, 0), request.Question, null, null, null); } } private DnsDatagram ProcessAuthoritativeQuery(DnsDatagram request, bool isRecursionAllowed) { DnsDatagram response = _authoritativeZoneRoot.Query(request); if (response.Header.RCODE == DnsResponseCode.NoError) { if (response.Answer.Length > 0) { DnsResourceRecordType questionType = request.Question[0].Type; DnsResourceRecord lastRR = response.Answer[response.Answer.Length - 1]; if ((lastRR.Type != questionType) && (lastRR.Type == DnsResourceRecordType.CNAME) && (questionType != DnsResourceRecordType.ANY)) { List responseAnswer = new List(); responseAnswer.AddRange(response.Answer); DnsDatagram lastResponse; while (true) { DnsDatagram cnameRequest = new DnsDatagram(new DnsHeader(0, false, DnsOpcode.StandardQuery, false, false, request.Header.RecursionDesired, false, false, false, DnsResponseCode.NoError, 1, 0, 0, 0), new DnsQuestionRecord[] { new DnsQuestionRecord((lastRR.RDATA as DnsCNAMERecord).CNAMEDomainName, questionType, DnsClass.IN) }, null, null, null); lastResponse = _authoritativeZoneRoot.Query(cnameRequest); if (lastResponse.Header.RCODE == DnsResponseCode.Refused) { if (!cnameRequest.Header.RecursionDesired || !isRecursionAllowed) break; lastResponse = ProcessRecursiveQuery(cnameRequest); } if ((lastResponse.Header.RCODE != DnsResponseCode.NoError) || (lastResponse.Answer.Length == 0)) break; responseAnswer.AddRange(lastResponse.Answer); lastRR = lastResponse.Answer[lastResponse.Answer.Length - 1]; if (lastRR.Type != DnsResourceRecordType.CNAME) break; } DnsResponseCode rcode; DnsResourceRecord[] authority; DnsResourceRecord[] additional; if (lastResponse.Header.RCODE == DnsResponseCode.Refused) { rcode = DnsResponseCode.NoError; authority = new DnsResourceRecord[] { }; additional = new DnsResourceRecord[] { }; } else { rcode = lastResponse.Header.RCODE; if (lastResponse.Header.AuthoritativeAnswer) { authority = lastResponse.Authority; additional = lastResponse.Additional; } else { if ((lastResponse.Authority.Length > 0) && (lastResponse.Authority[0].Type == DnsResourceRecordType.SOA)) authority = lastResponse.Authority; else authority = new DnsResourceRecord[] { }; additional = new DnsResourceRecord[] { }; } } return new DnsDatagram(new DnsHeader(request.Header.Identifier, true, DnsOpcode.StandardQuery, lastResponse.Header.AuthoritativeAnswer, false, request.Header.RecursionDesired, isRecursionAllowed, false, false, rcode, 1, (ushort)responseAnswer.Count, (ushort)authority.Length, (ushort)additional.Length), request.Question, responseAnswer.ToArray(), authority, additional); } } else if ((response.Authority.Length > 0) && (response.Authority[0].Type == DnsResourceRecordType.NS) && isRecursionAllowed) { if (_forwarders != null) return ProcessRecursiveQuery(request); //do recursive resolution using forwarders //do recursive resolution using response authority name servers NameServerAddress[] nameServers = NameServerAddress.GetNameServersFromResponse(response, _preferIPv6, false); return ProcessRecursiveQuery(request, nameServers); } } return response; } private DnsDatagram ProcessRecursiveQuery(DnsDatagram request, NameServerAddress[] viaNameServers = null) { DnsDatagram response = RecursiveResolve(request, viaNameServers); DnsResourceRecord[] authority; if ((response.Header.RCODE == DnsResponseCode.NoError) && (response.Answer.Length > 0)) { DnsResourceRecordType questionType = request.Question[0].Type; DnsResourceRecord lastRR = response.Answer[response.Answer.Length - 1]; if ((lastRR.Type != questionType) && (lastRR.Type == DnsResourceRecordType.CNAME) && (questionType != DnsResourceRecordType.ANY)) { List responseAnswer = new List(); responseAnswer.AddRange(response.Answer); DnsDatagram lastResponse; while (true) { DnsQuestionRecord question; if (questionType == DnsResourceRecordType.PTR) question = new DnsQuestionRecord(IPAddress.Parse((lastRR.RDATA as DnsCNAMERecord).CNAMEDomainName), DnsClass.IN); else question = new DnsQuestionRecord((lastRR.RDATA as DnsCNAMERecord).CNAMEDomainName, questionType, DnsClass.IN); lastResponse = RecursiveResolve(question, _forwarders); if ((lastResponse.Header.RCODE != DnsResponseCode.NoError) || (lastResponse.Answer.Length == 0)) break; responseAnswer.AddRange(lastResponse.Answer); lastRR = lastResponse.Answer[lastResponse.Answer.Length - 1]; if (lastRR.Type == questionType) break; if (lastRR.Type != DnsResourceRecordType.CNAME) throw new DnsServerException("Invalid response received from Dns server."); } if ((lastResponse.Authority.Length > 0) && (lastResponse.Authority[0].Type == DnsResourceRecordType.SOA)) authority = lastResponse.Authority; else authority = new DnsResourceRecord[] { }; return new DnsDatagram(new DnsHeader(request.Header.Identifier, true, DnsOpcode.StandardQuery, false, false, true, true, false, false, lastResponse.Header.RCODE, 1, (ushort)responseAnswer.Count, (ushort)authority.Length, 0), request.Question, responseAnswer.ToArray(), authority, new DnsResourceRecord[] { }); } } if ((response.Authority.Length > 0) && (response.Authority[0].Type == DnsResourceRecordType.SOA)) authority = response.Authority; else authority = new DnsResourceRecord[] { }; return new DnsDatagram(new DnsHeader(request.Header.Identifier, true, DnsOpcode.StandardQuery, false, false, true, true, false, false, response.Header.RCODE, 1, (ushort)response.Answer.Length, (ushort)authority.Length, 0), request.Question, response.Answer, authority, new DnsResourceRecord[] { }); } private DnsDatagram RecursiveResolve(DnsQuestionRecord questionRecord, NameServerAddress[] viaNameServers) { return RecursiveResolve(new DnsDatagram(new DnsHeader(0, false, DnsOpcode.StandardQuery, false, false, true, false, false, false, DnsResponseCode.NoError, 1, 0, 0, 0), new DnsQuestionRecord[] { questionRecord }, null, null, null), viaNameServers); } private DnsDatagram RecursiveResolve(DnsDatagram request, NameServerAddress[] viaNameServers) { //query cache zone to see if answer available { DnsDatagram cacheResponse = _cacheZoneRoot.Query(request); if (cacheResponse.Header.RCODE != DnsResponseCode.Refused) { if (cacheResponse.Answer.Length > 0) return cacheResponse; else if ((cacheResponse.Authority.Length == 0) || (cacheResponse.Authority[0].Type == DnsResourceRecordType.SOA)) return cacheResponse; } } //recursion with locking object newLockObj = new object(); object actualLockObj = _recursiveQueryLocks.GetOrAdd(request.Question[0], newLockObj); if (!actualLockObj.Equals(newLockObj)) { //question already being recursively resolved by another thread, wait till timeout or pulse signal bool waitTimeout; lock (actualLockObj) { waitTimeout = !Monitor.Wait(actualLockObj, _timeout); } if (!waitTimeout) { //query cache zone again to see if answer available DnsDatagram cacheResponse = _cacheZoneRoot.Query(request); if (cacheResponse.Header.RCODE != DnsResponseCode.Refused) { if (cacheResponse.Answer.Length > 0) return cacheResponse; else if ((cacheResponse.Authority.Length == 0) || (cacheResponse.Authority[0].Type == DnsResourceRecordType.SOA)) return cacheResponse; } } //wait timeout or no response available in cache so respond with server failure return new DnsDatagram(new DnsHeader(request.Header.Identifier, true, DnsOpcode.StandardQuery, false, false, request.Header.RecursionDesired, true, false, false, DnsResponseCode.ServerFailure, request.Header.QDCOUNT, 0, 0, 0), request.Question, null, null, null); } DnsClientProtocol protocol; if (_forwarders == null) { protocol = DnsClient.RecursiveResolveDefaultProtocol; } else { viaNameServers = _forwarders; //forwarder has higher weightage protocol = _forwarderProtocol; } try { return DnsClient.ResolveViaNameServers(request.Question[0], viaNameServers, _dnsCache, _proxy, _preferIPv6, protocol, _retries, _maxStackCount, _timeout); } finally { //remove question lock _recursiveQueryLocks.TryRemove(request.Question[0], out object lockObj); //pulse all waiting threads lock (newLockObj) { Monitor.PulseAll(newLockObj); } } } #endregion #region public public void Start() { if (_state != ServiceState.Stopped) return; _udpListener = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); _udpListener.DualMode = true; _udpListener.Bind(_localEP); #region this code ignores ICMP port unreachable responses which creates SocketException in ReceiveFrom() if (Environment.OSVersion.Platform == PlatformID.Win32NT) { const uint IOC_IN = 0x80000000; const uint IOC_VENDOR = 0x18000000; const uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12; _udpListener.IOControl((IOControlCode)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null); } #endregion _tcpListener = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp); _tcpListener.DualMode = true; _tcpListener.Bind(_localEP); _tcpListener.Listen(100); //start reading query packets _udpListenerThreads = new Thread[UDP_LISTENER_THREAD_COUNT]; for (int i = 0; i < UDP_LISTENER_THREAD_COUNT; i++) { _udpListenerThreads[i] = new Thread(ReadUdpQueryPacketsAsync); _udpListenerThreads[i].IsBackground = true; _udpListenerThreads[i].Start(); } _tcpListenerThread = new Thread(AcceptTcpConnectionAsync); _tcpListenerThread.IsBackground = true; _tcpListenerThread.Start(); _state = ServiceState.Running; } public void Stop() { if (_state != ServiceState.Running) return; _state = ServiceState.Stopping; for (int i = 0; i < UDP_LISTENER_THREAD_COUNT; i++) { try { _udpListenerThreads[i].Abort(); } catch (PlatformNotSupportedException) { } } try { _tcpListenerThread.Abort(); } catch (PlatformNotSupportedException) { } _udpListener.Dispose(); _tcpListener.Dispose(); _state = ServiceState.Stopped; } #endregion #region properties public IPEndPoint LocalEP { get { return _localEP; } } public Zone AuthoritativeZoneRoot { get { return _authoritativeZoneRoot; } } public Zone CacheZoneRoot { get { return _cacheZoneRoot; } } public Zone AllowedZoneRoot { get { return _allowedZoneRoot; } } public Zone BlockedZoneRoot { get { return _blockedZoneRoot; } set { if (value == null) throw new ArgumentNullException(); if (!value.IsAuthoritative) throw new ArgumentException("Blocked zone must be authoritative."); _blockedZoneRoot = value; } } internal IDnsCache Cache { get { return _dnsCache; } } public bool AllowRecursion { get { return _allowRecursion; } set { _allowRecursion = value; } } public bool AllowRecursionOnlyForPrivateNetworks { get { return _allowRecursionOnlyForPrivateNetworks; } set { _allowRecursionOnlyForPrivateNetworks = value; } } public NetProxy Proxy { get { return _proxy; } set { _proxy = value; } } public NameServerAddress[] Forwarders { get { return _forwarders; } set { _forwarders = value; } } public DnsClientProtocol ForwarderProtocol { get { return _forwarderProtocol; } set { _forwarderProtocol = value; } } public bool PreferIPv6 { get { return _preferIPv6; } set { _preferIPv6 = value; } } public int Retries { get { return _retries; } set { _retries = value; } } public int Timeout { get { return _timeout; } set { _timeout = value; } } public int MaxStackCount { get { return _maxStackCount; } set { _maxStackCount = value; } } public LogManager LogManager { get { return _log; } set { _log = value; } } public LogManager QueryLogManager { get { return _queryLog; } set { _queryLog = value; } } public StatsManager StatsManager { get { return _stats; } set { _stats = value; } } #endregion class DnsCache : IDnsCache { #region variables readonly Zone _cacheZoneRoot; #endregion #region constructor public DnsCache(Zone cacheZoneRoot) { _cacheZoneRoot = cacheZoneRoot; } #endregion #region public public DnsDatagram Query(DnsDatagram request) { return _cacheZoneRoot.Query(request); } public void CacheResponse(DnsDatagram response) { _cacheZoneRoot.CacheResponse(response); } #endregion } } }