You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

600 lines
19 KiB

1 month ago
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Ubiety.Dns.Core.Common;
namespace Ubiety.Dns.Core
{
/// <summary>
/// DNS resolver runs querys against a server
/// </summary>
public class Resolver
{
/// <summary>
/// Default DNS port
/// </summary>
public const int DefaultPort = 53;
private readonly List<IPEndPoint> _dnsServers;
private readonly Dictionary<string, Response> _responseCache;
private int _retries;
private int _timeout;
private ushort _unique;
private bool _useCache;
/// <summary>
/// Initializes a new instance of the <see cref="Resolver" /> class
/// </summary>
/// <param name="dnsServers">Set of DNS servers</param>
public Resolver(IEnumerable<IPEndPoint> dnsServers)
{
_responseCache = new Dictionary<string, Response>();
_dnsServers = new List<IPEndPoint>();
_dnsServers.AddRange(dnsServers);
_unique = (ushort)new Random().Next();
_retries = 3;
_timeout = 1;
Recursion = true;
_useCache = true;
TransportType = TransportType.Udp;
}
/// <summary>
/// Initializes a new instance of the <see cref="Resolver" /> class
/// </summary>
/// <param name="dnsServer">DNS server to use</param>
public Resolver(IPEndPoint dnsServer)
: this(new[] { dnsServer })
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Resolver" /> class
/// </summary>
/// <param name="serverIpAddress">DNS server to use</param>
/// <param name="serverPortNumber">DNS port to use</param>
public Resolver(IPAddress serverIpAddress, int serverPortNumber)
: this(new IPEndPoint(serverIpAddress, serverPortNumber))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Resolver" /> class
/// </summary>
/// <param name="serverIpAddress">DNS server address to use</param>
/// <param name="serverPortNumber">DNS port to use</param>
public Resolver(string serverIpAddress, int serverPortNumber)
: this(IPAddress.Parse(serverIpAddress), serverPortNumber)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Resolver" /> class
/// </summary>
/// <param name="serverIpAddress">DNS server address to use</param>
public Resolver(string serverIpAddress)
: this(IPAddress.Parse(serverIpAddress), DefaultPort)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Resolver" /> class
/// </summary>
public Resolver()
: this(GetSystemDnsServers())
{
}
/// <summary>
/// Verbose event handler
/// </summary>
/// <param name="sender">Object sending the event</param>
/// <param name="e">Event arguments</param>
public delegate void VerboseEventHandler(object sender, VerboseEventArgs e);
/// <summary>
/// Verbose messages from internal operations
/// </summary>
public event VerboseEventHandler OnVerbose;
/// <summary>
/// Gets the current version of the library
/// </summary>
public static string Version => Assembly.GetExecutingAssembly().GetName().Version.ToString();
/// <summary>
/// Gets the default OpenDNS servers
/// </summary>
public static List<IPEndPoint> DefaultDnsServers => new List<IPEndPoint>
{
new IPEndPoint(IPAddress.Parse("208.67.222.222"), DefaultPort),
new IPEndPoint(IPAddress.Parse("208.67.220.220"), DefaultPort)
};
/// <summary>
/// Gets or sets timeout in milliseconds
/// </summary>
public int Timeout
{
get => _timeout;
set => _timeout = value * 1000;
}
/// <summary>
/// Gets or sets the number of retries before giving up
/// </summary>
public int Retries
{
get => _retries;
set
{
if (value >= 1)
{
_retries = value;
}
}
}
/// <summary>
/// Gets or sets a value indicating whether recursion is enabled for doing queries
/// </summary>
public bool Recursion { get; set; }
/// <summary>
/// Gets or sets protocol to use
/// </summary>
public TransportType TransportType { get; set; }
/// <summary>
/// Gets or sets list of DNS servers to use
/// </summary>
public List<IPEndPoint> DnsServers
{
get => _dnsServers;
set
{
_dnsServers.Clear();
_dnsServers.AddRange(value);
}
}
/// <summary>
/// Gets or sets the first DNS server address or sets single DNS server to use
/// </summary>
public string DnsServer
{
get => _dnsServers[0].Address.ToString();
set
{
IPAddress ip;
if (IPAddress.TryParse(value, out ip))
{
_dnsServers.Clear();
_dnsServers.Add(new IPEndPoint(ip, DefaultPort));
return;
}
var response = Query(value, QuestionType.A);
if (response.RecordA.Count <= 0)
{
return;
}
_dnsServers.Clear();
_dnsServers.Add(new IPEndPoint(response.RecordA[0].Address, DefaultPort));
}
}
/// <summary>
/// Gets or sets a value indicating whether to use the cache
/// </summary>
public bool UseCache
{
get => _useCache;
set
{
_useCache = value;
if (!_useCache)
{
_responseCache.Clear();
}
}
}
/// <summary>
/// Gets a list of default DNS servers used on the Windows machine.
/// </summary>
/// <returns>Array of DNS servers</returns>
public static IEnumerable<IPEndPoint> GetSystemDnsServers()
{
var list = new List<IPEndPoint>();
var adapters = NetworkInterface.GetAllNetworkInterfaces();
foreach (var n in adapters)
{
if (n.OperationalStatus != OperationalStatus.Up)
{
continue;
}
var ipProps = n.GetIPProperties();
// thanks to Jon Webster on May 20, 2008
foreach (var ipAddr in ipProps.DnsAddresses)
{
var entry = new IPEndPoint(ipAddr, DefaultPort);
if (!list.Contains(entry))
{
list.Add(entry);
}
}
}
return list.ToArray();
}
/// <summary>
/// Translates the IPV4 or IPV6 address into an arpa address
/// </summary>
/// <param name="ip">IP address to get the arpa address form</param>
/// <returns>The 'mirrored' IPV4 or IPV6 arpa address</returns>
public static string GetArpaFromIp(IPAddress ip)
{
switch (ip.AddressFamily)
{
case AddressFamily.InterNetwork:
{
var sb = new StringBuilder();
sb.Append("in-addr.arpa.");
foreach (var b in ip.GetAddressBytes())
{
sb.Insert(0, $"{b}.");
}
return sb.ToString();
}
case AddressFamily.InterNetworkV6:
{
var sb = new StringBuilder();
sb.Append("ip6.arpa.");
foreach (var b in ip.GetAddressBytes())
{
sb.Insert(0, $"{(b >> 4) & 0xf:x}.");
sb.Insert(0, $"{(b >> 0) & 0xf:x}.");
}
return sb.ToString();
}
}
return "?";
}
/// <summary>
/// Get ARPA address from enum
/// </summary>
/// <param name="enumerator">Enum for the address</param>
/// <returns>String of the ARPA address</returns>
public static string GetArpaFromEnum(string enumerator)
{
var sb = new StringBuilder();
var number = Regex.Replace(enumerator, "[^0-9]", string.Empty);
sb.Append("e164.arpa.");
foreach (var c in number)
{
sb.Insert(0, $"{c}.");
}
return sb.ToString();
}
/// <summary>
/// Clear the resolver cache
/// </summary>
public void ClearCache()
{
_responseCache.Clear();
}
/// <summary>
/// Do Query on specified DNS servers
/// </summary>
/// <param name="name">Name to query</param>
/// <param name="qtype">Question type</param>
/// <param name="qclass">Class type</param>
/// <returns>Response of the query</returns>
public Response Query(string name, QuestionType qtype, QuestionClass qclass)
{
var question = new Question(name, qtype, qclass);
var response = SearchInCache(question);
if (response != null)
{
return response;
}
var request = new Request();
request.AddQuestion(question);
return GetResponse(request);
}
/// <summary>
/// Do an QClass=IN Query on specified DNS servers
/// </summary>
/// <param name="name">Name to query</param>
/// <param name="qtype">Question type</param>
/// <returns>Response of the query</returns>
public Response Query(string name, QuestionType qtype)
{
var question = new Question(name, qtype, QuestionClass.IN);
var response = SearchInCache(question);
if (response != null)
{
return response;
}
var request = new Request();
request.AddQuestion(question);
return GetResponse(request);
}
private static void WriteRequest(Stream stream, Request request)
{
var data = request.GetData();
stream.WriteByte((byte)((data.Length >> 8) & 0xFF));
stream.WriteByte((byte)(data.Length & 0xFF));
stream.Write(data, 0, data.Length);
stream.Flush();
}
private Response GetResponse(Request request)
{
request.Header.Id = _unique;
request.Header.Recursion = Recursion;
switch (TransportType)
{
case TransportType.Udp:
return UdpRequest(request);
case TransportType.Tcp:
return TcpRequest(request).Result;
}
var response = new Response { Error = "Unknown TransportType" };
return response;
}
private void Verbose(string format, params object[] args)
{
OnVerbose?.Invoke(this, new VerboseEventArgs(string.Format(CultureInfo.CurrentCulture, format, args)));
}
private Response SearchInCache(Question question)
{
if (!_useCache)
{
return null;
}
var strKey = question.QuestionClass + "-" + question.QuestionType + "-" + question.QuestionName;
Response response;
lock (_responseCache)
{
if (!_responseCache.ContainsKey(strKey))
{
return null;
}
response = _responseCache[strKey];
}
var timeLived = (int)((DateTime.Now.Ticks - response.TimeStamp.Ticks) / TimeSpan.TicksPerSecond);
foreach (var rr in response.ResourceRecords)
{
rr.TimeLived = timeLived;
// The TTL property calculates its actual time to live
if (rr.Ttl == 0)
{
return null; // out of date
}
}
return response;
}
private void AddToCache(Response response)
{
if (!_useCache)
{
return;
}
// No question, no caching
if (response.Questions.Count == 0)
{
return;
}
// Only cached non-error responses
if (response.Header.ResponseCode != ResponseCode.NoError)
{
return;
}
var question = response.Questions[0];
var strKey = question.QuestionClass + "-" + question.QuestionType + "-" + question.QuestionName;
lock (_responseCache)
{
if (_responseCache.ContainsKey(strKey))
{
_responseCache.Remove(strKey);
}
_responseCache.Add(strKey, response);
}
}
private Response UdpRequest(Request request)
{
// RFC1035 max. size of a UDP datagram is 512 bytes
var responseMessage = new byte[512];
for (var intAttempts = 0; intAttempts < _retries; intAttempts++)
{
for (var intDnsServer = 0; intDnsServer < _dnsServers.Count; intDnsServer++)
{
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, Timeout);
try
{
socket.SendTo(request.GetData(), _dnsServers[intDnsServer]);
var intReceived = socket.Receive(responseMessage);
var data = new byte[intReceived];
Array.Copy(responseMessage, data, intReceived);
var response = new Response(_dnsServers[intDnsServer], data);
AddToCache(response);
return response;
}
catch (SocketException)
{
Verbose($";; Connection to nameserver {intDnsServer + 1} failed");
}
finally
{
_unique++;
// close the socket
socket.Close();
}
}
}
var responseTimeout = new Response { Error = "Timeout Error" };
return responseTimeout;
}
private async Task<Response> TcpRequest(Request request)
{
for (var intAttempts = 0; intAttempts < _retries; intAttempts++)
{
foreach (var server in _dnsServers)
{
var client = new TcpClient { ReceiveTimeout = _timeout };
try
{
await client.ConnectAsync(server.Address, server.Port).ConfigureAwait(false);
if (!client.Connected)
{
client.Close();
Verbose($";; Connection to nameserver {server.Address} failed");
continue;
}
var stream = new BufferedStream(client.GetStream());
WriteRequest(stream, request);
return ReceiveResponse(stream, server);
}
catch (SocketException)
{
}
finally
{
_unique++;
client.Close();
}
}
}
var responseTimeout = new Response { Error = "Timeout Error" };
return responseTimeout;
}
private Response ReceiveResponse(Stream stream, IPEndPoint server)
{
var transferResponse = new Response();
var soa = 0;
var messageSize = 0;
while (true)
{
var length = (stream.ReadByte() << 8) | stream.ReadByte();
if (length <= 0)
{
Verbose($"Connection to nameserver {server.Address} failed");
throw new SocketException();
}
messageSize += length;
var data = new byte[length];
stream.Read(data, 0, length);
var response = new Response(server, data);
if (response.Header.ResponseCode != ResponseCode.NoError)
{
return response;
}
if (response.Questions[0].QuestionType != QuestionType.AXFR)
{
AddToCache(response);
return response;
}
if (transferResponse.Questions.Count == 0)
{
transferResponse.Questions.AddRange(response.Questions);
}
transferResponse.Answers.AddRange(response.Answers);
transferResponse.Authorities.AddRange(response.Authorities);
transferResponse.Additionals.AddRange(response.Additionals);
if (response.Answers[0].Type == RecordType.SOA)
{
soa++;
}
if (soa != 2)
{
continue;
}
transferResponse.Header.QuestionCount = (ushort)transferResponse.Questions.Count;
transferResponse.Header.AnswerCount = (ushort)transferResponse.Answers.Count;
transferResponse.Header.NameserverCount = (ushort)transferResponse.Authorities.Count;
transferResponse.Header.AdditionalRecordsCount = (ushort)transferResponse.Additionals.Count;
transferResponse.MessageSize = messageSize;
return transferResponse;
}
}
} // class
}