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