using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.InteropServices;
namespace DnsClient
{
///
/// Represents a name server instance used by .
/// Also, comes with some static methods to resolve name servers from the local network configuration.
///
public class NameServer : IEquatable
{
///
/// The default DNS server port.
///
public const int DefaultPort = 53;
///
/// The public google DNS IPv4 endpoint.
///
public static readonly NameServer GooglePublicDns = new IPEndPoint(IPAddress.Parse("8.8.8.8"), DefaultPort);
///
/// The second public google DNS IPv6 endpoint.
///
public static readonly NameServer GooglePublicDns2 = new IPEndPoint(IPAddress.Parse("8.8.4.4"), DefaultPort);
///
/// The public google DNS IPv6 endpoint.
///
public static readonly NameServer GooglePublicDnsIPv6 = new IPEndPoint(IPAddress.Parse("2001:4860:4860::8888"), DefaultPort);
///
/// The second public google DNS IPv6 endpoint.
///
public static readonly NameServer GooglePublicDns2IPv6 = new IPEndPoint(IPAddress.Parse("2001:4860:4860::8844"), DefaultPort);
internal const string EtcResolvConfFile = "/etc/resolv.conf";
///
/// Initializes a new instance of the class.
///
/// The name server endpoint.
public NameServer(IPAddress endPoint)
: this(new IPEndPoint(endPoint, DefaultPort))
{
}
///
/// Initializes a new instance of the class.
///
/// The name server endpoint.
/// The name server port.
public NameServer(IPAddress endPoint, int port)
: this(new IPEndPoint(endPoint, port))
{
}
///
/// Initializes a new instance of the class.
///
/// The name server endpoint.
/// If is null.
public NameServer(IPEndPoint endPoint)
{
IPEndPoint = endPoint ?? throw new ArgumentNullException(nameof(endPoint));
}
///
/// Initializes a new instance of the class from a .
///
/// The endpoint.
public static implicit operator NameServer(IPEndPoint endPoint)
{
if (endPoint == null)
{
return null;
}
return new NameServer(endPoint);
}
///
/// Initializes a new instance of the class from a .
///
/// The address.
public static implicit operator NameServer(IPAddress address)
{
if (address == null)
{
return null;
}
return new NameServer(address);
}
///
/// Gets a value indicating whether this is enabled.
///
/// The instance might get disabled if encounters problems to connect to it.
///
///
///
/// true if enabled; otherwise, false.
///
public bool Enabled { get; internal set; } = true;
///
/// Gets the string representation of the configured .
///
public string Address => IPEndPoint.Address.ToString();
///
/// Gets the port.
///
public int Port => IPEndPoint.Port;
///
/// Gets the address family.
///
public AddressFamily AddressFamily => IPEndPoint.AddressFamily;
///
/// Gets the size of the supported UDP payload.
///
/// This value might get updated by by reading the options records returned by a query.
///
///
///
/// The size of the supported UDP payload.
///
public int? SupportedUdpPayloadSize { get; internal set; }
// for tracking if we can re-enable the server...
internal DnsRequestMessage LastSuccessfulRequest { get; set; }
internal IPEndPoint IPEndPoint { get; }
///
/// Returns a that represents this instance.
///
///
/// A that represents this instance.
///
public override string ToString()
{
return $"{Address}:{Port} (Udp: {SupportedUdpPayloadSize ?? 512})";
}
internal NameServer Clone()
{
return this;
// TODO: maybe not needed
////return new NameServer(IPEndPoint)
////{
//// Enabled = Enabled,
//// SupportedUdpPayloadSize = SupportedUdpPayloadSize
////};
}
///
public override bool Equals(object obj)
{
return Equals(obj as NameServer);
}
///
public bool Equals(NameServer other)
{
return other != null
&& EqualityComparer.Default.Equals(IPEndPoint, other.IPEndPoint);
}
///
public override int GetHashCode()
{
return EqualityComparer.Default.GetHashCode(IPEndPoint);
}
///
/// Gets a list of name servers by iterating over the available network interfaces.
///
/// If is enabled, this method will return the google public dns endpoints if no
/// local DNS server was found.
///
///
/// If set to true local IPv6 sites are skiped.
/// If set to true the public Google DNS servers are returned if no other servers could be found.
///
/// The list of name servers.
///
public static IReadOnlyCollection ResolveNameServers(bool skipIPv6SiteLocal = true, bool fallbackToGooglePublicDns = true)
{
IReadOnlyCollection endPoints = new NameServer[0];
List exceptions = new List();
try
{
endPoints = QueryNetworkInterfaces(skipIPv6SiteLocal);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
#if !NET45
if (exceptions.Count > 0)
{
try
{
endPoints = ResolveNameServersNative();
exceptions.Clear();
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
#endif
if (exceptions.Count > 0)
{
if (exceptions.Count > 1)
{
throw new AggregateException("Error resolving name servers", exceptions);
}
else
{
throw exceptions.First();
}
}
if (endPoints.Count == 0 && fallbackToGooglePublicDns)
{
return new NameServer[]
{
GooglePublicDnsIPv6,
GooglePublicDns2IPv6,
GooglePublicDns,
GooglePublicDns2,
};
}
return endPoints;
}
#if !NET45
///
/// Using my custom native implementation to support UWP apps and such until
/// gets an implementation in netstandard2.1.
///
///
/// DnsClient has been changed in version 1.1.0.
/// It will not invoke this when resolving default DNS servers. It is up to the user to decide what to do based on what platform the code is running on.
///
///
/// The list of name servers.
///
public static IReadOnlyCollection ResolveNameServersNative()
{
IPAddress[] addresses = null;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var fixedInfo = Windows.IpHlpApi.FixedNetworkInformation.GetFixedInformation();
addresses = fixedInfo.DnsAddresses.ToArray();
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
addresses = Linux.StringParsingHelpers.ParseDnsAddressesFromResolvConfFile(EtcResolvConfFile).ToArray();
}
return addresses?.Select(p => new NameServer(p, DefaultPort)).ToArray();
}
#endif
private static NameServer[] QueryNetworkInterfaces(bool skipIPv6SiteLocal)
{
var result = new HashSet();
var adapters = NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface networkInterface in
adapters
.Where(p => p.OperationalStatus == OperationalStatus.Up
&& p.NetworkInterfaceType != NetworkInterfaceType.Loopback))
{
foreach (IPAddress dnsAddress in networkInterface
.GetIPProperties()
.DnsAddresses
.Where(i =>
i.AddressFamily == AddressFamily.InterNetwork
|| i.AddressFamily == AddressFamily.InterNetworkV6))
{
if (dnsAddress.AddressFamily == AddressFamily.InterNetworkV6)
{
if (skipIPv6SiteLocal && dnsAddress.IsIPv6SiteLocal)
{
continue;
}
}
result.Add(new IPEndPoint(dnsAddress, DefaultPort));
}
}
return result.ToArray();
}
}
}