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 { /// /// DNS resolver runs querys against a server /// public class Resolver { /// /// Default DNS port /// public const int DefaultPort = 53; private readonly List _dnsServers; private readonly Dictionary _responseCache; private int _retries; private int _timeout; private ushort _unique; private bool _useCache; /// /// Initializes a new instance of the class /// /// Set of DNS servers public Resolver(IEnumerable dnsServers) { _responseCache = new Dictionary(); _dnsServers = new List(); _dnsServers.AddRange(dnsServers); _unique = (ushort)new Random().Next(); _retries = 3; _timeout = 1; Recursion = true; _useCache = true; TransportType = TransportType.Udp; } /// /// Initializes a new instance of the class /// /// DNS server to use public Resolver(IPEndPoint dnsServer) : this(new[] { dnsServer }) { } /// /// Initializes a new instance of the class /// /// DNS server to use /// DNS port to use public Resolver(IPAddress serverIpAddress, int serverPortNumber) : this(new IPEndPoint(serverIpAddress, serverPortNumber)) { } /// /// Initializes a new instance of the class /// /// DNS server address to use /// DNS port to use public Resolver(string serverIpAddress, int serverPortNumber) : this(IPAddress.Parse(serverIpAddress), serverPortNumber) { } /// /// Initializes a new instance of the class /// /// DNS server address to use public Resolver(string serverIpAddress) : this(IPAddress.Parse(serverIpAddress), DefaultPort) { } /// /// Initializes a new instance of the class /// public Resolver() : this(GetSystemDnsServers()) { } /// /// Verbose event handler /// /// Object sending the event /// Event arguments public delegate void VerboseEventHandler(object sender, VerboseEventArgs e); /// /// Verbose messages from internal operations /// public event VerboseEventHandler OnVerbose; /// /// Gets the current version of the library /// public static string Version => Assembly.GetExecutingAssembly().GetName().Version.ToString(); /// /// Gets the default OpenDNS servers /// public static List DefaultDnsServers => new List { new IPEndPoint(IPAddress.Parse("208.67.222.222"), DefaultPort), new IPEndPoint(IPAddress.Parse("208.67.220.220"), DefaultPort) }; /// /// Gets or sets timeout in milliseconds /// public int Timeout { get => _timeout; set => _timeout = value * 1000; } /// /// Gets or sets the number of retries before giving up /// public int Retries { get => _retries; set { if (value >= 1) { _retries = value; } } } /// /// Gets or sets a value indicating whether recursion is enabled for doing queries /// public bool Recursion { get; set; } /// /// Gets or sets protocol to use /// public TransportType TransportType { get; set; } /// /// Gets or sets list of DNS servers to use /// public List DnsServers { get => _dnsServers; set { _dnsServers.Clear(); _dnsServers.AddRange(value); } } /// /// Gets or sets the first DNS server address or sets single DNS server to use /// 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)); } } /// /// Gets or sets a value indicating whether to use the cache /// public bool UseCache { get => _useCache; set { _useCache = value; if (!_useCache) { _responseCache.Clear(); } } } /// /// Gets a list of default DNS servers used on the Windows machine. /// /// Array of DNS servers public static IEnumerable GetSystemDnsServers() { var list = new List(); 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(); } /// /// Translates the IPV4 or IPV6 address into an arpa address /// /// IP address to get the arpa address form /// The 'mirrored' IPV4 or IPV6 arpa address 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 "?"; } /// /// Get ARPA address from enum /// /// Enum for the address /// String of the ARPA address 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(); } /// /// Clear the resolver cache /// public void ClearCache() { _responseCache.Clear(); } /// /// Do Query on specified DNS servers /// /// Name to query /// Question type /// Class type /// Response of the query 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); } /// /// Do an QClass=IN Query on specified DNS servers /// /// Name to query /// Question type /// Response of the query 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 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 }