using DnsClient.Protocol; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; namespace DnsClient { /// /// Extension methods for the contract. /// /// The methods implement common queries which are more complex and have some business logic. /// /// public static class DnsQueryExtensions { /// /// The GetHostEntry method queries a DNS server for the IP addresses and aliases associated with the . /// In case is an , GetHostEntry does a reverse lookup on that first to determine the hostname. /// /// IP addresses found are returned in . /// records are used to populate the .
/// The property will be set to the resolved hostname or . ///
///
/// /// The following code example uses the method to resolve an IP address or hostname to an instance. /// /// /// /// /// /// The method has some logic to populate the list: /// /// /// /// In case of sub-domain queries or similar, there might be multiple records for one , /// /// /// /// If only one is in the result set, all the aliases found will be returned. /// /// /// /// If more than one is in the result set, aliases are returned only if at least one doesn't match the queried hostname. /// /// /// /// /// The instance. /// The or host name to query for. /// /// An instance that contains address information about the host specified in . /// In case the could not be resolved to a domain name, this method returns null, /// unless is set to true, then it might throw a . /// /// If is null. /// In case is set to true and a DNS error occurs. public static IPHostEntry GetHostEntry(this IDnsQuery query, string hostNameOrAddress) { if (query == null) { throw new ArgumentNullException(nameof(query)); } if (string.IsNullOrWhiteSpace(hostNameOrAddress)) { throw new ArgumentNullException(nameof(hostNameOrAddress)); } if (IPAddress.TryParse(hostNameOrAddress, out IPAddress address)) { return query.GetHostEntry(address); } return GetHostEntryFromName(query, hostNameOrAddress); } /// /// The GetHostEntryAsync method queries a DNS server for the IP addresses and aliases associated with the . /// In case is an , GetHostEntry does a reverse lookup on that first to determine the hostname. /// /// IP addresses found are returned in . /// records are used to populate the .
/// The property will be set to the resolved hostname or . ///
///
/// /// The following code example uses the method to resolve an IP address or hostname to an instance. /// /// /// /// /// /// The method has some logic to populate the list: /// /// /// /// In case of sub-domain queries or similar, there might be multiple records for one , /// /// /// /// If only one is in the result set, all the aliases found will be returned. /// /// /// /// If more than one is in the result set, aliases are returned only if at least one doesn't match the queried hostname. /// /// /// /// /// The instance. /// The or host name to query for. /// /// An instance that contains address information about the host specified in . /// In case the could not be resolved to a domain name, this method returns null, /// unless is set to true, then it might throw a . /// /// If is null. /// In case is set to true and a DNS error occurs. public static Task GetHostEntryAsync(this IDnsQuery query, string hostNameOrAddress) { if (query == null) { throw new ArgumentNullException(nameof(query)); } if (string.IsNullOrWhiteSpace(hostNameOrAddress)) { throw new ArgumentNullException(nameof(hostNameOrAddress)); } if (IPAddress.TryParse(hostNameOrAddress, out IPAddress address)) { return query.GetHostEntryAsync(address); } return GetHostEntryFromNameAsync(query, hostNameOrAddress); } /// /// The GetHostEntry method does a reverse lookup on the IP , /// and queries a DNS server for the IP addresses and aliases associated with the resolved hostname. /// /// IP addresses found are returned in . /// records are used to populate the .
/// The property will be set to the resolved hostname of the . ///
///
/// /// The following code example uses the method to resolve an IP address to an instance. /// /// /// /// /// /// The method has some logic to populate the list: /// /// /// /// In case of sub-domain queries or similar, there might be multiple records for one , /// /// /// /// If only one is in the result set, all the aliases found will be returned. /// /// /// /// If more than one is in the result set, aliases are returned only if at least one doesn't match the queried hostname. /// /// /// /// /// The instance. /// The to query for. /// /// An instance that contains address information about the host specified in . /// In case the could not be resolved to a domain name, this method returns null, /// unless is set to true, then it might throw a . /// /// If is null. /// In case is set to true and a DNS error occurs. public static IPHostEntry GetHostEntry(this IDnsQuery query, IPAddress address) { if (query == null) { throw new ArgumentNullException(nameof(query)); } if (address == null) { throw new ArgumentNullException(nameof(address)); } var hostName = query.GetHostName(address); if (string.IsNullOrWhiteSpace(hostName)) { return null; } return GetHostEntryFromName(query, hostName); } /// /// The GetHostEntryAsync method does a reverse lookup on the IP , /// and queries a DNS server for the IP addresses and aliases associated with the resolved hostname. /// /// IP addresses found are returned in . /// records are used to populate the .
/// The property will be set to the resolved hostname of the . ///
///
/// /// The following code example uses the method to resolve an IP address to an instance. /// /// /// /// /// /// The method has some logic to populate the list: /// /// /// /// In case of sub-domain queries or similar, there might be multiple records for one , /// /// /// /// If only one is in the result set, all the aliases found will be returned. /// /// /// /// If more than one is in the result set, aliases are returned only if at least one doesn't match the queried hostname. /// /// /// /// /// The instance. /// The to query for. /// /// An instance that contains address information about the host specified in . /// In case the could not be resolved to a domain name, this method returns null, /// unless is set to true, then it might throw a . /// /// If is null. /// In case is set to true and a DNS error occurs. public static async Task GetHostEntryAsync(this IDnsQuery query, IPAddress address) { if (query == null) { throw new ArgumentNullException(nameof(query)); } if (address == null) { throw new ArgumentNullException(nameof(address)); } var hostName = await query.GetHostNameAsync(address).ConfigureAwait(false); if (string.IsNullOrWhiteSpace(hostName)) { return null; } return await GetHostEntryFromNameAsync(query, hostName).ConfigureAwait(false); } private static IPHostEntry GetHostEntryFromName(IDnsQuery query, string hostName) { if (string.IsNullOrWhiteSpace(hostName)) { throw new ArgumentNullException(nameof(hostName)); } var hostString = DnsString.FromResponseQueryString(hostName); var ipv4Result = query.Query(hostString, QueryType.A); var ipv6Result = query.Query(hostString, QueryType.AAAA); var allRecords = ipv4Result .Answers.Concat(ipv6Result.Answers) .ToArray(); return GetHostEntryProcessResult(hostString, allRecords); } private static async Task GetHostEntryFromNameAsync(IDnsQuery query, string hostName) { if (string.IsNullOrWhiteSpace(hostName)) { throw new ArgumentNullException(nameof(hostName)); } var hostString = DnsString.FromResponseQueryString(hostName); var ipv4Result = query.QueryAsync(hostString, QueryType.A); var ipv6Result = query.QueryAsync(hostString, QueryType.AAAA); await Task.WhenAll(ipv4Result, ipv6Result).ConfigureAwait(false); var allRecords = ipv4Result.Result .Answers.Concat(ipv6Result.Result.Answers) .ToArray(); return GetHostEntryProcessResult(hostString, allRecords); } private static IPHostEntry GetHostEntryProcessResult(DnsString hostString, DnsResourceRecord[] allRecords) { var addressRecords = allRecords .OfType() .Select(p => new { Address = p.Address, Alias = DnsString.FromResponseQueryString(p.DomainName) }) .ToArray(); var hostEntry = new IPHostEntry() { Aliases = new string[0], AddressList = addressRecords .Select(p => p.Address) .ToArray() }; if (addressRecords.Length > 1) { if (addressRecords.Any(p => !p.Alias.Equals(hostString))) { hostEntry.Aliases = addressRecords .Select(p => p.Alias.ToString()) .Select(p => p.Substring(0, p.Length - 1)) .Distinct() .ToArray(); } } else if (addressRecords.Length == 1) { if (allRecords.Any(p => !DnsString.FromResponseQueryString(p.DomainName).Equals(hostString))) { hostEntry.Aliases = allRecords .Select(p => p.DomainName.ToString()) .Select(p => p.Substring(0, p.Length - 1)) .Distinct() .ToArray(); } } hostEntry.HostName = hostString.Value.Substring(0, hostString.Value.Length - 1); return hostEntry; } /// /// The GetHostName method queries a DNS server to resolve the hostname of the via reverse lookup. /// /// The instance. /// The to resolve. /// /// The hostname if the reverse lookup was successful or null, in case the host was not found. /// If is set to true, this method will throw an instead of returning null! /// /// If is null. /// If no host has been found and is true. public static string GetHostName(this IDnsQuery query, IPAddress address) { if (query == null) { throw new ArgumentNullException(nameof(query)); } if (address == null) { throw new ArgumentNullException(nameof(address)); } var result = query.QueryReverse(address); return GetHostNameAsyncProcessResult(result); } /// /// The GetHostNameAsync method queries a DNS server to resolve the hostname of the via reverse lookup. /// /// The instance. /// The to resolve. /// /// The hostname if the reverse lookup was successful or null, in case the host was not found. /// If is set to true, this method will throw an instead of returning null! /// /// If is null. /// If no host has been found and is true. public static async Task GetHostNameAsync(this IDnsQuery query, IPAddress address) { if (query == null) { throw new ArgumentNullException(nameof(query)); } if (address == null) { throw new ArgumentNullException(nameof(address)); } var result = await query.QueryReverseAsync(address).ConfigureAwait(false); return GetHostNameAsyncProcessResult(result); } private static string GetHostNameAsyncProcessResult(IDnsQueryResponse result) { if (result.HasError) { return null; } var hostName = result.Answers.PtrRecords().FirstOrDefault()?.PtrDomainName; if (string.IsNullOrWhiteSpace(hostName)) { return null; } // removing the . at the end return hostName.Value.Substring(0, hostName.Value.Length - 1); } /// /// The ResolveService method does a lookup for _{}[._{}].{} /// and aggregates the result (hostname, port and list of s) to a . /// /// This method expects matching A or AAAA records to populate the , /// and/or a record to populate the property of the result. /// /// /// /// The returned list of s and/or the hostname can be empty if no matching additional records are found. /// /// The instance. /// The base domain, which will be appended to the end of the query string. /// The name of the service to look for. Must not have any _ prefix. /// /// The protocol of the service to query for. /// Set it to or to not pass any protocol. /// /// A collection of s. /// If or are null. /// RFC 2782 public static ServiceHostEntry[] ResolveService(this IDnsQuery query, string baseDomain, string serviceName, ProtocolType protocol) { if (protocol == ProtocolType.Unspecified || protocol == ProtocolType.Unknown) { return ResolveService(query, baseDomain, serviceName, null); } return ResolveService(query, baseDomain, serviceName, protocol.ToString()); } /// /// The ResolveServiceAsync method does a lookup for _{}[._{}].{} /// and aggregates the result (hostname, port and list of s) to a . /// /// This method expects matching A or AAAA records to populate the , /// and/or a record to populate the property of the result. /// /// /// /// The returned list of s and/or the hostname can be empty if no matching additional records are found. /// /// The instance. /// The base domain, which will be appended to the end of the query string. /// The name of the service to look for. Must not have any _ prefix. /// /// The protocol of the service to query for. /// Set it to or to not pass any protocol. /// /// A collection of s. /// If or are null. /// RFC 2782 public static Task ResolveServiceAsync(this IDnsQuery query, string baseDomain, string serviceName, ProtocolType protocol) { if (protocol == ProtocolType.Unspecified || protocol == ProtocolType.Unknown) { return ResolveServiceAsync(query, baseDomain, serviceName, null); } return ResolveServiceAsync(query, baseDomain, serviceName, protocol.ToString()); } /// /// The ResolveService method does a lookup for _{}[._{}].{} /// and aggregates the result (hostname, port and list of s) to a . /// /// This method expects matching A or AAAA records to populate the , /// and/or a record to populate the property of the result. /// /// /// /// The returned list of s and/or the hostname can be empty if no matching additional records are found. /// /// The instance. /// The base domain, which will be appended to the end of the query string. /// The name of the service to look for. Must not have any _ prefix. /// An optional tag. Must not have any _ prefix. /// A collection of s. /// If or are null. /// RFC 2782 public static ServiceHostEntry[] ResolveService(this IDnsQuery query, string baseDomain, string serviceName, string tag = null) { if (query == null) { throw new ArgumentNullException(nameof(query)); } if (baseDomain == null) { throw new ArgumentNullException(nameof(baseDomain)); } if (string.IsNullOrWhiteSpace(serviceName)) { throw new ArgumentNullException(nameof(serviceName)); } var queryString = ConcatResolveServiceName(baseDomain, serviceName, tag); var result = query.Query(queryString, QueryType.SRV); return ResolveServiceProcessResult(result); } /// /// The ResolveServiceAsync method does a lookup for _{}[._{}].{} /// and aggregates the result (hostname, port and list of s) to a . /// /// This method expects matching A or AAAA records to populate the , /// and/or a record to populate the property of the result. /// /// /// /// The returned list of s and/or the hostname can be empty if no matching additional records are found. /// /// The instance. /// The base domain, which will be appended to the end of the query string. /// The name of the service to look for. Must not have any _ prefix. /// An optional tag. Must not have any _ prefix. /// A collection of s. /// If or are null. /// RFC 2782 public static async Task ResolveServiceAsync(this IDnsQuery query, string baseDomain, string serviceName, string tag = null) { if (query == null) { throw new ArgumentNullException(nameof(query)); } if (baseDomain == null) { throw new ArgumentNullException(nameof(baseDomain)); } if (string.IsNullOrWhiteSpace(serviceName)) { throw new ArgumentNullException(nameof(serviceName)); } var queryString = ConcatResolveServiceName(baseDomain, serviceName, tag); var result = await query.QueryAsync(queryString, QueryType.SRV).ConfigureAwait(false); return ResolveServiceProcessResult(result); } private static string ConcatResolveServiceName(string baseDomain, string serviceName, string tag) { return string.IsNullOrWhiteSpace(tag) ? $"{serviceName}.{baseDomain}." : $"_{serviceName}._{tag}.{baseDomain}."; } private static ServiceHostEntry[] ResolveServiceProcessResult(IDnsQueryResponse result) { var hosts = new List(); if (result.HasError) { return hosts.ToArray(); } foreach (var entry in result.Answers.SrvRecords()) { var addresses = result.Additionals .OfType() .Where(p => p.DomainName.Equals(entry.Target)) .Select(p => p.Address); var hostName = result.Additionals .OfType() .Where(p => p.DomainName.Equals(entry.Target)) .Select(p => p.CanonicalName).FirstOrDefault(); hosts.Add(new ServiceHostEntry() { AddressList = addresses.ToArray(), HostName = hostName, Port = entry.Port }); } return hosts.ToArray(); } } /// /// Extends by the property. /// /// public class ServiceHostEntry : IPHostEntry { /// /// Gets or sets the port. /// /// /// The port. /// public int Port { get; set; } } }