mirror of
https://github.com/fergalmoran/DnsServer.git
synced 2026-02-18 22:05:12 +00:00
first dns server working version commited.
This commit is contained in:
@@ -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
491
DnsServerCore/DnsServer.cs
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
61
DnsServerCore/DnsServerCore.csproj
Normal file
61
DnsServerCore/DnsServerCore.csproj
Normal 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>
|
||||
36
DnsServerCore/Properties/AssemblyInfo.cs
Normal file
36
DnsServerCore/Properties/AssemblyInfo.cs
Normal 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
654
DnsServerCore/Zone.cs
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user