first dns server working version commited.

This commit is contained in:
Shreyas Zare
2017-01-14 00:29:51 +05:30
parent 8debcdf2ae
commit f4f1b82fea
5 changed files with 1260 additions and 0 deletions

View File

@@ -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

491
DnsServerCore/DnsServer.cs Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<DnsDatagram> responses = new List<DnsDatagram>(1);
while (true)
{
DnsDatagram response = Resolve(request);
responses.Add(response);
if (response.Header.RCODE != DnsResponseCode.NoError)
break;
if (response.Answer.Length == 0)
break;
List<DnsQuestionRecord> newQuestions = new List<DnsQuestionRecord>();
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<DnsDatagram> responses = new List<DnsDatagram>();
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<DnsDatagram> 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<DnsResourceRecord> responseAnswer = new List<DnsResourceRecord>();
List<DnsResourceRecord> responseAuthority = new List<DnsResourceRecord>();
List<DnsResourceRecord> responseAdditional = new List<DnsResourceRecord>();
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
}
}

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A561CF13-FE21-40A1-BF8D-BD242304187A}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>DnsServerCore</RootNamespace>
<AssemblyName>DnsServerCore</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup>
<Compile Include="DnsServer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Zone.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\TechnitiumLibrary\TechnitiumLibrary.IO\TechnitiumLibrary.IO.csproj">
<Project>{e0ba5456-feaa-4380-92bb-6b1c4bc3dc70}</Project>
<Name>TechnitiumLibrary.IO</Name>
</ProjectReference>
<ProjectReference Include="..\..\TechnitiumLibrary\TechnitiumLibrary.Net\TechnitiumLibrary.Net.csproj">
<Project>{c8293a12-5a6a-4f53-bebe-35a6d37bd891}</Project>
<Name>TechnitiumLibrary.Net</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -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")]

654
DnsServerCore/Zone.cs Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Threading;
using TechnitiumLibrary.Net;
namespace DnsServerCore
{
public class Zone
{
#region variables
string _name;
bool _authoritativeZone;
Dictionary<string, Zone> _subZone = new Dictionary<string, Zone>();
ReaderWriterLockSlim _subZoneLock = new ReaderWriterLockSlim();
Dictionary<string, Dictionary<DnsResourceRecordType, ZoneEntry>> _zoneEntries = new Dictionary<string, Dictionary<DnsResourceRecordType, ZoneEntry>>();
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<DnsResourceRecord> additionalList = new List<DnsResourceRecord>();
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<DnsResourceRecord> answerList = new List<DnsResourceRecord>();
List<DnsResourceRecord> authorityList = new List<DnsResourceRecord>();
List<DnsResourceRecord> additionalList = new List<DnsResourceRecord>();
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<DnsResourceRecord> allRecords = new List<DnsResourceRecord>();
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<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> cacheEntries = new Dictionary<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>>();
foreach (DnsResourceRecord record in allRecords)
{
Dictionary<DnsResourceRecordType, List<DnsResourceRecord>> cacheTypeEntries;
if (cacheEntries.ContainsKey(record.Name))
{
cacheTypeEntries = cacheEntries[record.Name];
}
else
{
cacheTypeEntries = new Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>();
cacheEntries.Add(record.Name, cacheTypeEntries);
}
List<DnsResourceRecord> cacheRREntries;
if (cacheTypeEntries.ContainsKey(record.Type))
{
cacheRREntries = cacheTypeEntries[record.Type];
}
else
{
cacheRREntries = new List<DnsResourceRecord>();
cacheTypeEntries.Add(record.Type, cacheRREntries);
}
cacheRREntries.Add(record);
}
//add grouped entries into cache zone
foreach (KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> cacheEntry in cacheEntries)
{
string domain = cacheEntry.Key;
Zone zone = CreateZone(rootZone, domain);
foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> 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<DnsResourceRecordType, ZoneEntry> zoneTypeEntries;
if (_zoneEntries.ContainsKey(domain))
{
zoneTypeEntries = _zoneEntries[domain];
}
else
{
zoneTypeEntries = new Dictionary<DnsResourceRecordType, ZoneEntry>();
_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<DnsResourceRecordType, ZoneEntry> 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<DnsResourceRecord> records = new List<DnsResourceRecord>(5);
foreach (KeyValuePair<DnsResourceRecordType, ZoneEntry> 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<DnsResourceRecordType, ZoneEntry> 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
}
}