diff --git a/DnsServer.sln b/DnsServer.sln
index 7d3f1263..f2acce0e 100644
--- a/DnsServer.sln
+++ b/DnsServer.sln
@@ -3,7 +3,25 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DnsServerCore", "DnsServerCore\DnsServerCore.csproj", "{A561CF13-FE21-40A1-BF8D-BD242304187A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechnitiumLibrary.Net", "..\TechnitiumLibrary\TechnitiumLibrary.Net\TechnitiumLibrary.Net.csproj", "{C8293A12-5A6A-4F53-BEBE-35A6D37BD891}"
+EndProject
Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A561CF13-FE21-40A1-BF8D-BD242304187A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A561CF13-FE21-40A1-BF8D-BD242304187A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A561CF13-FE21-40A1-BF8D-BD242304187A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A561CF13-FE21-40A1-BF8D-BD242304187A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C8293A12-5A6A-4F53-BEBE-35A6D37BD891}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C8293A12-5A6A-4F53-BEBE-35A6D37BD891}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C8293A12-5A6A-4F53-BEBE-35A6D37BD891}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C8293A12-5A6A-4F53-BEBE-35A6D37BD891}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
diff --git a/DnsServerCore/DnsServer.cs b/DnsServerCore/DnsServer.cs
new file mode 100644
index 00000000..a346b2f3
--- /dev/null
+++ b/DnsServerCore/DnsServer.cs
@@ -0,0 +1,491 @@
+/*
+Technitium Library
+Copyright (C) 2017 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.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using TechnitiumLibrary.IO;
+using TechnitiumLibrary.Net;
+
+namespace DnsServerCore
+{
+ public class DnsServer : IDisposable
+ {
+ #region variables
+
+ const int BUFFER_MAX_SIZE = 65535;
+ const int TCP_SOCKET_SEND_TIMEOUT = 30000;
+ const int TCP_SOCKET_RECV_TIMEOUT = 60000;
+
+ Socket _udpListener;
+ Thread _udpListenerThread;
+
+ Socket _tcpListener;
+ Thread _tcpListenerThread;
+
+ Zone _authoritativeZoneRoot = new Zone(true);
+ Zone _cacheZoneRoot = new Zone(false);
+
+ bool _allowRecursion;
+ NameServerAddress[] _forwarders;
+ bool _enableIPv6 = false;
+
+ #endregion
+
+ #region constructor
+
+ public DnsServer()
+ : this(new IPEndPoint(IPAddress.IPv6Any, 53))
+ { }
+
+ public DnsServer(IPAddress localIP, int port = 53)
+ : this(new IPEndPoint(localIP, port))
+ { }
+
+ public DnsServer(IPEndPoint localEP)
+ {
+ _udpListener = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
+ _udpListener.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
+ _udpListener.Bind(localEP);
+
+ _tcpListener = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
+ _tcpListener.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
+ _tcpListener.Bind(localEP);
+ _tcpListener.Listen(10);
+
+ //start reading query packets
+ _udpListenerThread = new Thread(ReadUdpQueryPacketsAsync);
+ _udpListenerThread.IsBackground = true;
+ _udpListenerThread.Start(_udpListener);
+
+ _tcpListenerThread = new Thread(AcceptTcpConnectionAsync);
+ _tcpListenerThread.IsBackground = true;
+ _tcpListenerThread.Start(_tcpListener);
+ }
+
+ #endregion
+
+ #region IDisposable Support
+
+ bool _disposed = false;
+
+ ~DnsServer()
+ {
+ Dispose(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ if (_udpListener != null)
+ _udpListener.Dispose();
+
+ if (_tcpListener != null)
+ _tcpListener.Dispose();
+ }
+
+ _disposed = true;
+ }
+ }
+
+ #endregion
+
+ #region private
+
+ private void ReadUdpQueryPacketsAsync(object parameter)
+ {
+ Socket udpListener = parameter as Socket;
+
+ EndPoint remoteEP;
+ FixMemoryStream recvBufferStream = new FixMemoryStream(BUFFER_MAX_SIZE);
+ FixMemoryStream sendBufferStream = new FixMemoryStream(BUFFER_MAX_SIZE);
+ int bytesRecv;
+
+ if (udpListener.AddressFamily == AddressFamily.InterNetwork)
+ remoteEP = new IPEndPoint(IPAddress.Any, 0);
+ else
+ remoteEP = new IPEndPoint(IPAddress.IPv6Any, 0);
+
+ while (true)
+ {
+ bytesRecv = udpListener.ReceiveFrom(recvBufferStream.Buffer, ref remoteEP);
+
+ if (bytesRecv > 0)
+ {
+ recvBufferStream.Position = 0;
+ recvBufferStream.SetLength(bytesRecv);
+
+ IPEndPoint remoteNodeEP = remoteEP as IPEndPoint;
+
+ try
+ {
+ DnsDatagram response = ProcessQuery(recvBufferStream);
+
+ //send response
+ if (response != null)
+ {
+ sendBufferStream.Position = 0;
+ response.WriteTo(sendBufferStream);
+ udpListener.SendTo(sendBufferStream.Buffer, 0, (int)sendBufferStream.Position, SocketFlags.None, remoteEP);
+ }
+ }
+ catch
+ { }
+ }
+ }
+ }
+
+ private void AcceptTcpConnectionAsync(object parameter)
+ {
+ Socket tcpListener = parameter as Socket;
+
+ while (true)
+ {
+ Socket socket = tcpListener.Accept();
+
+ socket.NoDelay = true;
+ socket.SendTimeout = TCP_SOCKET_SEND_TIMEOUT;
+ socket.ReceiveTimeout = TCP_SOCKET_RECV_TIMEOUT;
+
+ ThreadPool.QueueUserWorkItem(ReadTcpQueryPacketsAsync, socket);
+ }
+ }
+
+ private void ReadTcpQueryPacketsAsync(object parameter)
+ {
+ Socket tcpSocket = parameter as Socket;
+
+ try
+ {
+ FixMemoryStream recvBufferStream = new FixMemoryStream(BUFFER_MAX_SIZE);
+ FixMemoryStream sendBufferStream = new FixMemoryStream(BUFFER_MAX_SIZE);
+ int bytesRecv;
+
+ while (true)
+ {
+ //read dns datagram length
+ bytesRecv = tcpSocket.Receive(recvBufferStream.Buffer, 0, 2, SocketFlags.None);
+ if (bytesRecv < 1)
+ throw new SocketException();
+
+ Array.Reverse(recvBufferStream.Buffer, 0, 2);
+ short length = BitConverter.ToInt16(recvBufferStream.Buffer, 0);
+
+ //read dns datagram
+ int offset = 0;
+ while (offset < length)
+ {
+ bytesRecv = tcpSocket.Receive(recvBufferStream.Buffer, offset, length, SocketFlags.None);
+ if (bytesRecv < 1)
+ throw new SocketException();
+
+ offset += bytesRecv;
+ }
+
+ bytesRecv = length;
+
+ if (bytesRecv > 0)
+ {
+ recvBufferStream.Position = 0;
+ recvBufferStream.SetLength(bytesRecv);
+
+ DnsDatagram response = ProcessQuery(recvBufferStream);
+
+ //send response
+ if (response != null)
+ {
+ //write dns datagram from 3rd position
+ sendBufferStream.Position = 2;
+ response.WriteTo(sendBufferStream);
+
+ //write dns datagram length at beginning
+ byte[] lengthBytes = BitConverter.GetBytes(Convert.ToInt16(sendBufferStream.Position - 2));
+ sendBufferStream.Buffer[0] = lengthBytes[1];
+ sendBufferStream.Buffer[1] = lengthBytes[0];
+
+ //send dns datagram
+ tcpSocket.Send(sendBufferStream.Buffer, 0, (int)sendBufferStream.Position, SocketFlags.None);
+ }
+ }
+ }
+ }
+ catch
+ { }
+ finally
+ {
+ if (tcpSocket != null)
+ tcpSocket.Dispose();
+ }
+ }
+
+ private DnsDatagram ProcessQuery(Stream s)
+ {
+ DnsDatagram request;
+
+ try
+ {
+ request = new DnsDatagram(s);
+ }
+ catch
+ {
+ return null;
+ }
+
+ if (request.Header.IsResponse)
+ return null;
+
+ switch (request.Header.OPCODE)
+ {
+ case DnsOpcode.StandardQuery:
+ try
+ {
+ DnsDatagram authoritativeResponse = Zone.Query(_authoritativeZoneRoot, request, _enableIPv6);
+
+ if ((authoritativeResponse.Header.RCODE != DnsResponseCode.Refused) || !request.Header.RecursionDesired || !_allowRecursion)
+ return authoritativeResponse;
+
+ return RecursiveQuery(request);
+ }
+ catch
+ {
+ return new DnsDatagram(new DnsHeader(request.Header.Identifier, true, DnsOpcode.StandardQuery, false, false, request.Header.RecursionDesired, _allowRecursion, 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, _allowRecursion, false, false, DnsResponseCode.Refused, request.Header.QDCOUNT, 0, 0, 0), request.Question, null, null, null);
+ }
+ }
+
+ public DnsDatagram RecursiveQuery(DnsDatagram request)
+ {
+ DnsDatagram originalRequest = request;
+ List responses = new List(1);
+
+ while (true)
+ {
+ DnsDatagram response = Resolve(request);
+ responses.Add(response);
+
+ if (response.Header.RCODE != DnsResponseCode.NoError)
+ break;
+
+ if (response.Answer.Length == 0)
+ break;
+
+ List newQuestions = new List();
+
+ foreach (DnsQuestionRecord question in request.Question)
+ {
+ for (int i = 0; i < response.Answer.Length; i++)
+ {
+ DnsResourceRecord answerRecord = response.Answer[i];
+
+ if ((answerRecord.Type == DnsResourceRecordType.CNAME) && question.Name.Equals(answerRecord.Name, StringComparison.CurrentCultureIgnoreCase))
+ {
+ string cnameDomain = (answerRecord.RDATA as DnsCNAMERecord).CNAMEDomainName;
+ bool containsAnswer = false;
+
+ for (int j = i + 1; j < response.Answer.Length; j++)
+ {
+ DnsResourceRecord answer = response.Answer[j];
+
+ if ((answer.Type == question.Type) && cnameDomain.Equals(answer.Name, StringComparison.CurrentCultureIgnoreCase))
+ {
+ containsAnswer = true;
+ break;
+ }
+ }
+
+ if (!containsAnswer)
+ newQuestions.Add(new DnsQuestionRecord((answerRecord.RDATA as DnsCNAMERecord).CNAMEDomainName, question.Type, question.Class));
+
+ break;
+ }
+ }
+ }
+
+ if (newQuestions.Count == 0)
+ break;
+
+ request = new DnsDatagram(new DnsHeader(0, false, DnsOpcode.StandardQuery, false, false, true, false, false, false, DnsResponseCode.NoError, Convert.ToUInt16(newQuestions.Count), 0, 0, 0), newQuestions.ToArray(), null, null, null);
+ }
+
+ return MergeResponseAnswers(originalRequest, responses);
+ }
+
+ private DnsDatagram Resolve(DnsDatagram request)
+ {
+ DnsDatagram cacheResponse = Zone.Query(_cacheZoneRoot, request, _enableIPv6);
+
+ if (cacheResponse.Header.RCODE != DnsResponseCode.Refused)
+ return cacheResponse;
+
+ List responses = new List();
+
+ foreach (DnsQuestionRecord questionRecord in request.Question)
+ {
+ NameServerAddress[] nameServers = NameServerAddress.GetNameServersFromResponse(cacheResponse, _enableIPv6);
+
+ if (nameServers.Length == 0)
+ {
+ if (_enableIPv6)
+ nameServers = DnsClient.ROOT_NAME_SERVERS_IPv6;
+ else
+ nameServers = DnsClient.ROOT_NAME_SERVERS_IPv4;
+ }
+
+ int hopCount = 0;
+ bool working = true;
+
+ while (working && ((hopCount++) < 64))
+ {
+ DnsClient client = new DnsClient(nameServers, _enableIPv6, false);
+
+ DnsDatagram response = client.Resolve(questionRecord);
+
+ Zone.CacheResponse(_cacheZoneRoot, response);
+
+ switch (response.Header.RCODE)
+ {
+ case DnsResponseCode.NoError:
+ if ((response.Answer.Length > 0) || (response.Authority.Length == 0))
+ {
+ responses.Add(response);
+ working = false;
+ }
+ else
+ {
+ nameServers = NameServerAddress.GetNameServersFromResponse(response, _enableIPv6);
+
+ if (nameServers.Length == 0)
+ {
+ responses.Add(response);
+ working = false;
+ }
+ }
+ break;
+
+ default:
+ responses.Add(response);
+ working = false;
+ break;
+ }
+ }
+ }
+
+ return MergeResponseAnswers(request, responses);
+ }
+
+ private DnsDatagram MergeResponseAnswers(DnsDatagram request, List responses)
+ {
+ switch (responses.Count)
+ {
+ case 0:
+ return null;
+
+ case 1:
+ DnsDatagram responseReceived = responses[0];
+ return new DnsDatagram(new DnsHeader(request.Header.Identifier, true, request.Header.OPCODE, false, false, true, true, false, false, responseReceived.Header.RCODE, request.Header.QDCOUNT, responseReceived.Header.ANCOUNT, responseReceived.Header.NSCOUNT, responseReceived.Header.ARCOUNT), request.Question, responseReceived.Answer, responseReceived.Authority, responseReceived.Additional);
+
+ default:
+ List responseAnswer = new List();
+ List responseAuthority = new List();
+ List responseAdditional = new List();
+
+ foreach (DnsDatagram response in responses)
+ {
+ responseAnswer.AddRange(response.Answer);
+
+ if (response.Authority != null)
+ responseAuthority.AddRange(response.Authority);
+
+ if (response.Additional != null)
+ responseAuthority.AddRange(response.Additional);
+ }
+
+ return new DnsDatagram(new DnsHeader(request.Header.Identifier, true, request.Header.OPCODE, false, false, true, true, false, false, responses[0].Header.RCODE, request.Header.QDCOUNT, Convert.ToUInt16(responseAnswer.Count), Convert.ToUInt16(responseAuthority.Count), Convert.ToUInt16(responseAdditional.Count)), request.Question, responseAnswer.ToArray(), responseAuthority.ToArray(), responseAdditional.ToArray());
+ }
+ }
+
+ #endregion
+
+ #region properties
+
+ public Zone AuthoritativeZoneRoot
+ { get { return _authoritativeZoneRoot; } }
+
+ public Zone CacheZoneRoot
+ { get { return _cacheZoneRoot; } }
+
+ public bool AllowRecursion
+ {
+ get { return _allowRecursion; }
+ set { _allowRecursion = value; }
+ }
+
+ public NameServerAddress[] Forwarders
+ {
+ get { return _forwarders; }
+ set { _forwarders = value; }
+ }
+
+ public bool EnableIPv6
+ {
+ get { return _enableIPv6; }
+ set { _enableIPv6 = value; }
+ }
+ #endregion
+ }
+
+ public class DnsServerException : Exception
+ {
+ #region constructors
+
+ public DnsServerException()
+ : base()
+ { }
+
+ public DnsServerException(string message)
+ : base(message)
+ { }
+
+ public DnsServerException(string message, Exception innerException)
+ : base(message, innerException)
+ { }
+
+ protected DnsServerException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
+ : base(info, context)
+ { }
+
+ #endregion
+ }
+
+}
diff --git a/DnsServerCore/DnsServerCore.csproj b/DnsServerCore/DnsServerCore.csproj
new file mode 100644
index 00000000..c152a008
--- /dev/null
+++ b/DnsServerCore/DnsServerCore.csproj
@@ -0,0 +1,61 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {A561CF13-FE21-40A1-BF8D-BD242304187A}
+ Library
+ Properties
+ DnsServerCore
+ DnsServerCore
+ v4.6.1
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {e0ba5456-feaa-4380-92bb-6b1c4bc3dc70}
+ TechnitiumLibrary.IO
+
+
+ {c8293a12-5a6a-4f53-bebe-35a6d37bd891}
+ TechnitiumLibrary.Net
+
+
+
+
+
\ No newline at end of file
diff --git a/DnsServerCore/Properties/AssemblyInfo.cs b/DnsServerCore/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..2783e0e1
--- /dev/null
+++ b/DnsServerCore/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Technitium DNS Server")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Technitium")]
+[assembly: AssemblyProduct("Technitium DNS Server")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("a561cf13-fe21-40a1-bf8d-bd242304187a")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/DnsServerCore/Zone.cs b/DnsServerCore/Zone.cs
new file mode 100644
index 00000000..d673e659
--- /dev/null
+++ b/DnsServerCore/Zone.cs
@@ -0,0 +1,654 @@
+/*
+Technitium Library
+Copyright (C) 2017 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.Generic;
+using System.Threading;
+using TechnitiumLibrary.Net;
+
+namespace DnsServerCore
+{
+ public class Zone
+ {
+ #region variables
+
+ string _name;
+ bool _authoritativeZone;
+
+ Dictionary _subZone = new Dictionary();
+ ReaderWriterLockSlim _subZoneLock = new ReaderWriterLockSlim();
+
+ Dictionary> _zoneEntries = new Dictionary>();
+ ReaderWriterLockSlim _zoneEntriesLock = new ReaderWriterLockSlim();
+
+ #endregion
+
+ #region constructor
+
+ public Zone(bool authoritativeZone)
+ {
+ _name = "";
+ _authoritativeZone = authoritativeZone;
+
+ if (!_authoritativeZone)
+ LoadRootHintsInCache();
+ }
+
+ private Zone(string name, bool authoritativeZone)
+ {
+ _name = name.ToLower();
+ _authoritativeZone = authoritativeZone;
+ }
+
+ #endregion
+
+ #region private
+
+ private void LoadRootHintsInCache()
+ {
+ //load root server records
+ DnsResourceRecordData[] nsRecords = new DnsResourceRecordData[DnsClient.ROOT_NAME_SERVERS_IPv4.Length];
+
+ for (int i = 0; i < DnsClient.ROOT_NAME_SERVERS_IPv4.Length; i++)
+ {
+ NameServerAddress rootServer = DnsClient.ROOT_NAME_SERVERS_IPv4[i];
+
+ nsRecords[i] = new DnsNSRecord(rootServer.Domain);
+ SetRecord(rootServer.Domain, DnsResourceRecordType.A, 172800, new DnsResourceRecordData[] { new DnsARecord(rootServer.EndPoint.Address) });
+ }
+
+ for (int i = 0; i < DnsClient.ROOT_NAME_SERVERS_IPv6.Length; i++)
+ {
+ NameServerAddress rootServer = DnsClient.ROOT_NAME_SERVERS_IPv6[i];
+
+ SetRecord(rootServer.Domain, DnsResourceRecordType.AAAA, 172800, new DnsResourceRecordData[] { new DnsAAAARecord(rootServer.EndPoint.Address) });
+ }
+
+ SetRecord("", DnsResourceRecordType.NS, 172800, nsRecords);
+ }
+
+ private static string[] ConvertDomainToPath(string domainName)
+ {
+ if (domainName == null)
+ return new string[] { };
+
+ string[] path = domainName.ToLower().Split('.');
+ Array.Reverse(path);
+
+ return path;
+ }
+
+ #endregion
+
+ #region public static
+
+ public static Zone CreateZone(Zone rootZone, string domain)
+ {
+ Zone currentZone = rootZone;
+ string[] path = ConvertDomainToPath(domain);
+
+ for (int i = 0; i < path.Length; i++)
+ {
+ string nextZoneLabel = path[i];
+
+ ReaderWriterLockSlim currentSubZoneLock = currentZone._subZoneLock;
+ currentSubZoneLock.EnterWriteLock();
+ try
+ {
+ if (currentZone._subZone.ContainsKey(nextZoneLabel))
+ {
+ currentZone = currentZone._subZone[nextZoneLabel];
+ }
+ else
+ {
+ string zoneName = nextZoneLabel;
+
+ if (currentZone._name != "")
+ zoneName += "." + currentZone._name;
+
+ Zone nextZone = new Zone(zoneName, currentZone._authoritativeZone);
+ currentZone._subZone.Add(nextZoneLabel, nextZone);
+
+ currentZone = nextZone;
+ }
+ }
+ finally
+ {
+ currentSubZoneLock.ExitWriteLock();
+ }
+ }
+
+ return currentZone;
+ }
+
+ public static Zone GetClosestZone(Zone rootZone, string domain)
+ {
+ Zone currentZone = rootZone;
+ string[] path = ConvertDomainToPath(domain);
+
+ for (int i = 0; i < path.Length; i++)
+ {
+ string nextZoneLabel = path[i];
+
+ ReaderWriterLockSlim currentSubZoneLock = currentZone._subZoneLock;
+ currentSubZoneLock.EnterReadLock();
+ try
+ {
+ if (currentZone._subZone.ContainsKey(nextZoneLabel))
+ currentZone = currentZone._subZone[nextZoneLabel];
+ else
+ return currentZone;
+ }
+ finally
+ {
+ currentSubZoneLock.ExitReadLock();
+ }
+ }
+
+ return currentZone;
+ }
+
+ public static void DeleteZone(Zone rootZone, string domain)
+ {
+ Zone currentZone = rootZone;
+ string[] path = ConvertDomainToPath(domain);
+
+ //find parent zone
+ for (int i = 0; i < path.Length - 1; i++)
+ {
+ string nextZoneLabel = path[i];
+
+ ReaderWriterLockSlim currentSubZoneLock = currentZone._subZoneLock;
+ currentSubZoneLock.EnterReadLock();
+ try
+ {
+ if (currentZone._subZone.ContainsKey(nextZoneLabel))
+ currentZone = currentZone._subZone[nextZoneLabel];
+ else
+ return;
+ }
+ finally
+ {
+ currentSubZoneLock.ExitReadLock();
+ }
+ }
+
+ currentZone._subZoneLock.EnterWriteLock();
+ try
+ {
+ currentZone._subZone.Remove(path[path.Length - 1]);
+ }
+ finally
+ {
+ currentZone._subZoneLock.ExitWriteLock();
+ }
+ }
+
+ private static DnsDatagram Query(Zone rootZone, string domain, DnsResourceRecordType type, bool enableIPv6)
+ {
+ Zone closestZone = GetClosestZone(rootZone, domain);
+ DnsResourceRecord[] soaAuthority = null;
+
+ if (rootZone._authoritativeZone)
+ {
+ soaAuthority = closestZone.GetRecord(closestZone.Name, DnsResourceRecordType.SOA);
+ if (soaAuthority == null)
+ return null; //authoritative zone not found
+ }
+
+ DnsResourceRecord[] answer = closestZone.GetRecord(domain, type);
+ DnsResourceRecord[] authority = null;
+ DnsResourceRecord[] additional = null;
+
+ if (answer == null)
+ {
+ if (rootZone._authoritativeZone)
+ {
+ //domain name doesnt exists in authoritative zone
+ authority = soaAuthority;
+ }
+ else
+ {
+ //domain name doesnt exists in cache; return closest available authority NS records
+ string closestZoneName = closestZone.Name;
+
+ while (true)
+ {
+ authority = closestZone.GetRecord(closestZoneName, DnsResourceRecordType.NS);
+
+ if ((authority != null) && (authority[0].Type == DnsResourceRecordType.NS))
+ break;
+
+ int i = closestZoneName.IndexOf('.');
+ if (i < 0)
+ closestZoneName = "";
+ else
+ closestZoneName = closestZoneName.Substring(i + 1);
+
+ closestZone = GetClosestZone(rootZone, closestZoneName);
+ }
+ }
+ }
+ else if (answer.Length == 0)
+ {
+ //no records available for requested type
+ authority = closestZone.GetRecord(domain, DnsResourceRecordType.NS);
+
+ if (authority.Length == 0)
+ authority = soaAuthority;
+ }
+ else if (answer[0] == null)
+ {
+ //NameError entry found in cache
+ authority = closestZone.GetRecord(closestZone.Name, DnsResourceRecordType.SOA);
+ }
+ else if ((type != DnsResourceRecordType.NS) && (type != DnsResourceRecordType.ANY) && ((type == DnsResourceRecordType.CNAME) || (answer[0].Type != DnsResourceRecordType.CNAME)))
+ {
+ authority = closestZone.GetRecord(closestZone.Name, DnsResourceRecordType.NS);
+ }
+
+ //fill in glue records for NS records in authority
+ if ((authority != null) && (authority[0].Type != DnsResourceRecordType.SOA))
+ {
+ List additionalList = new List();
+ Zone closestNSZone = null;
+
+ foreach (DnsResourceRecord record in authority)
+ {
+ DnsNSRecord nsRecord = record.RDATA as DnsNSRecord;
+
+ if ((closestNSZone == null) || !nsRecord.NSDomainName.EndsWith(closestNSZone._name))
+ closestNSZone = GetClosestZone(rootZone, nsRecord.NSDomainName);
+
+ DnsResourceRecord[] nsAnswersA = closestNSZone.GetRecord(nsRecord.NSDomainName, DnsResourceRecordType.A);
+ if (nsAnswersA != null)
+ additionalList.AddRange(nsAnswersA);
+
+ if (enableIPv6)
+ {
+ DnsResourceRecord[] nsAnswersAAAA = closestNSZone.GetRecord(nsRecord.NSDomainName, DnsResourceRecordType.AAAA);
+ if (nsAnswersAAAA != null)
+ additionalList.AddRange(nsAnswersAAAA);
+ }
+ }
+
+ additional = additionalList.ToArray();
+ }
+
+ return new DnsDatagram(null, null, answer, authority, additional);
+ }
+
+ public static DnsDatagram Query(Zone rootZone, DnsDatagram request, bool enableIPv6)
+ {
+ bool authoritativeAnswer = false;
+ DnsResponseCode RCODE = DnsResponseCode.Refused;
+ List answerList = new List();
+ List authorityList = new List();
+ List additionalList = new List();
+
+ foreach (DnsQuestionRecord question in request.Question)
+ {
+ DnsDatagram response = Zone.Query(rootZone, question.Name, question.Type, enableIPv6);
+
+ if (response != null)
+ {
+ #region zone found
+
+ authoritativeAnswer = rootZone._authoritativeZone;
+
+ if (response.Answer == null)
+ {
+ if (authoritativeAnswer)
+ RCODE = DnsResponseCode.NameError; //domain does not exists in authoritative zone
+ else
+ RCODE = DnsResponseCode.Refused; //domain does not exists in cache
+ }
+ else
+ {
+ #region domain exists
+
+ RCODE = DnsResponseCode.NoError;
+
+ if (response.Answer.Length > 0)
+ {
+ if (!authoritativeAnswer && (response.Answer[0] == null))
+ {
+ //name error set in cache
+ RCODE = DnsResponseCode.NameError;
+ }
+ else
+ {
+ answerList.AddRange(response.Answer);
+
+ if ((response.Answer[0].Type == DnsResourceRecordType.CNAME) && (question.Type != DnsResourceRecordType.CNAME))
+ {
+ //resolve CNAME domain name
+ DnsCNAMERecord cnameRecord = response.Answer[0].RDATA as DnsCNAMERecord;
+
+ DnsDatagram cnameResponse = Zone.Query(rootZone, cnameRecord.CNAMEDomainName, question.Type, enableIPv6);
+ if ((cnameResponse != null) && (cnameResponse.Answer != null))
+ {
+ answerList.AddRange(cnameResponse.Answer);
+
+ if (cnameResponse.Authority != null)
+ authorityList.AddRange(cnameResponse.Authority);
+ }
+ }
+ }
+ }
+
+ #endregion
+ }
+
+ if ((response.Authority != null) && (response.Authority.Length > 0))
+ authorityList.AddRange(response.Authority);
+
+ if ((response.Additional != null) && (response.Additional.Length > 0))
+ additionalList.AddRange(response.Additional);
+
+ #endregion
+ }
+ }
+
+ return new DnsDatagram(new DnsHeader(request.Header.Identifier, true, DnsOpcode.StandardQuery, authoritativeAnswer, false, request.Header.RecursionDesired, !rootZone._authoritativeZone, false, false, RCODE, Convert.ToUInt16(request.Question.Length), Convert.ToUInt16(answerList.Count), Convert.ToUInt16(authorityList.Count), Convert.ToUInt16(additionalList.Count)), request.Question, answerList.ToArray(), authorityList.ToArray(), additionalList.ToArray());
+ }
+
+ public static void CacheResponse(Zone rootZone, DnsDatagram response)
+ {
+ if (rootZone._authoritativeZone)
+ throw new DnsServerException("Cannot cache response into authoritative zone.");
+
+ if (!response.Header.IsResponse)
+ return;
+
+ //combine all records in the response
+ List allRecords = new List();
+
+ switch (response.Header.RCODE)
+ {
+ case DnsResponseCode.NameError:
+ string authorityZone = null;
+ uint ttl = 60;
+
+ if ((response.Authority.Length > 0) && (response.Authority[0].Type == DnsResourceRecordType.SOA))
+ {
+ authorityZone = response.Authority[0].Name;
+ ttl = (response.Authority[0].RDATA as DnsSOARecord).Minimum;
+ }
+
+ foreach (DnsQuestionRecord question in response.Question)
+ {
+ if (authorityZone == null)
+ authorityZone = question.Name;
+
+ Zone zone = CreateZone(rootZone, authorityZone);
+ zone.SetRecord(new DnsResourceRecord[] { new DnsResourceRecord(question.Name, question.Type, DnsClass.Internet, ttl, null) });
+ }
+ break;
+
+ case DnsResponseCode.NoError:
+ allRecords.AddRange(response.Answer);
+ break;
+
+ default:
+ return; //nothing to do
+ }
+
+ allRecords.AddRange(response.Authority);
+ allRecords.AddRange(response.Additional);
+
+ //group all records by domain and type
+ Dictionary>> cacheEntries = new Dictionary>>();
+
+ foreach (DnsResourceRecord record in allRecords)
+ {
+ Dictionary> cacheTypeEntries;
+
+ if (cacheEntries.ContainsKey(record.Name))
+ {
+ cacheTypeEntries = cacheEntries[record.Name];
+ }
+ else
+ {
+ cacheTypeEntries = new Dictionary>();
+ cacheEntries.Add(record.Name, cacheTypeEntries);
+ }
+
+ List cacheRREntries;
+
+ if (cacheTypeEntries.ContainsKey(record.Type))
+ {
+ cacheRREntries = cacheTypeEntries[record.Type];
+ }
+ else
+ {
+ cacheRREntries = new List();
+ cacheTypeEntries.Add(record.Type, cacheRREntries);
+ }
+
+ cacheRREntries.Add(record);
+ }
+
+ //add grouped entries into cache zone
+ foreach (KeyValuePair>> cacheEntry in cacheEntries)
+ {
+ string domain = cacheEntry.Key;
+ Zone zone = CreateZone(rootZone, domain);
+
+ foreach (KeyValuePair> cacheTypeEntry in cacheEntry.Value)
+ {
+ DnsResourceRecord[] records = cacheTypeEntry.Value.ToArray();
+
+ foreach (DnsResourceRecord record in records)
+ record.SetExpiry();
+
+ zone.SetRecord(records);
+ }
+ }
+ }
+
+ #endregion
+
+ #region public
+
+ public void SetRecord(string domain, DnsResourceRecordType type, uint ttl, DnsResourceRecordData[] records)
+ {
+ DnsResourceRecord[] resourceRecords = new DnsResourceRecord[records.Length];
+
+ for (int i = 0; i < resourceRecords.Length; i++)
+ resourceRecords[i] = new DnsResourceRecord(domain, type, DnsClass.Internet, ttl, records[i]);
+
+ SetRecord(resourceRecords);
+ }
+
+ public void SetRecord(DnsResourceRecord[] resourceRecords)
+ {
+ if (resourceRecords.Length < 1)
+ return;
+
+ string domain = resourceRecords[0].Name;
+ DnsResourceRecordType type = resourceRecords[0].Type;
+
+ _zoneEntriesLock.EnterWriteLock();
+ try
+ {
+ Dictionary zoneTypeEntries;
+
+ if (_zoneEntries.ContainsKey(domain))
+ {
+ zoneTypeEntries = _zoneEntries[domain];
+ }
+ else
+ {
+ zoneTypeEntries = new Dictionary();
+ _zoneEntries.Add(domain, zoneTypeEntries);
+ }
+
+ if (zoneTypeEntries.ContainsKey(type))
+ zoneTypeEntries[type] = new ZoneEntry(resourceRecords);
+ else
+ zoneTypeEntries.Add(type, new ZoneEntry(resourceRecords));
+ }
+ finally
+ {
+ _zoneEntriesLock.ExitWriteLock();
+ }
+ }
+
+ public DnsResourceRecord[] GetRecord(string domain, DnsResourceRecordType type)
+ {
+ _zoneEntriesLock.EnterReadLock();
+ try
+ {
+ Dictionary zoneTypeEntries;
+
+ if (_zoneEntries.ContainsKey(domain))
+ zoneTypeEntries = _zoneEntries[domain];
+ else
+ return null;
+
+ if (zoneTypeEntries.ContainsKey(DnsResourceRecordType.CNAME))
+ {
+ ZoneEntry zoneEntry = zoneTypeEntries[DnsResourceRecordType.CNAME];
+
+ if (!_authoritativeZone && zoneEntry.IsExpired())
+ return null; //domain does not exists in cache since expired
+ else
+ return zoneEntry.ResourceRecords; //return CNAME record
+ }
+ else if (_authoritativeZone && (type == DnsResourceRecordType.ANY))
+ {
+ List records = new List(5);
+
+ foreach (KeyValuePair entry in zoneTypeEntries)
+ {
+ records.AddRange(entry.Value.ResourceRecords);
+ }
+
+ return records.ToArray(); //all authoritative records
+ }
+ else if (zoneTypeEntries.ContainsKey(type))
+ {
+ ZoneEntry zoneEntry = zoneTypeEntries[type];
+
+ if (_authoritativeZone || (_name == ""))
+ {
+ return zoneEntry.ResourceRecords; //records found in authoritative zone or root hints from cache
+ }
+ else
+ {
+ if (zoneEntry.IsExpired())
+ return null; //domain does not exists in cache since expired
+ else if (zoneEntry.IsNameErrorEntry())
+ return new DnsResourceRecord[] { null }; //name error set in cache
+ else
+ return zoneEntry.ResourceRecords; //records found in cache
+ }
+ }
+ else
+ {
+ if (_authoritativeZone)
+ return new DnsResourceRecord[] { }; //no records in authoritative zone
+ else
+ return null; //domain does not exists in cache
+ }
+ }
+ finally
+ {
+ _zoneEntriesLock.ExitReadLock();
+ }
+ }
+
+ public void DeleteRecord(string domain, DnsResourceRecordType type)
+ {
+ _zoneEntriesLock.EnterWriteLock();
+ try
+ {
+ Dictionary zoneTypeEntries;
+
+ if (_zoneEntries.ContainsKey(domain))
+ {
+ zoneTypeEntries = _zoneEntries[domain];
+
+ zoneTypeEntries.Remove(type);
+
+ if (zoneTypeEntries.Count < 1)
+ _zoneEntries.Remove(domain);
+ }
+ }
+ finally
+ {
+ _zoneEntriesLock.ExitWriteLock();
+ }
+ }
+
+ public override string ToString()
+ {
+ return _name;
+ }
+
+ #endregion
+
+ #region properties
+
+ public string Name
+ { get { return _name; } }
+
+ #endregion
+ }
+
+ public class ZoneEntry
+ {
+ #region variables
+
+ DnsResourceRecord[] _resourceRecords;
+
+ #endregion
+
+ #region constructor
+
+ public ZoneEntry(DnsResourceRecord[] resourceRecords)
+ {
+ _resourceRecords = resourceRecords;
+ }
+
+ #endregion
+
+ #region public
+
+ public bool IsExpired()
+ {
+ return (_resourceRecords[0].TTLValue < 1);
+ }
+
+ public bool IsNameErrorEntry()
+ {
+ return (_resourceRecords[0].RDATA == null);
+ }
+
+ #endregion
+
+ #region properties
+
+ public DnsResourceRecord[] ResourceRecords
+ { get { return _resourceRecords; } }
+
+ #endregion
+ }
+}