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; }
}
}