namespace Swan.Net { using Net.Dns; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; /// /// Provides miscellaneous network utilities such as a Public IP finder, /// a DNS client to query DNS records of any kind, and an NTP client. /// public static class Network { /// /// The DNS default port. /// public const int DnsDefaultPort = 53; /// /// The NTP default port. /// public const int NtpDefaultPort = 123; /// /// Gets the name of the host. /// /// /// The name of the host. /// public static string HostName => IPGlobalProperties.GetIPGlobalProperties().HostName; /// /// Gets the name of the network domain. /// /// /// The name of the network domain. /// public static string DomainName => IPGlobalProperties.GetIPGlobalProperties().DomainName; #region IP Addresses and Adapters Information Methods /// /// Gets the active IPv4 interfaces. /// Only those interfaces with a valid unicast address and a valid gateway will be returned in the collection. /// /// /// A collection of NetworkInterface/IPInterfaceProperties pairs /// that represents the active IPv4 interfaces. /// public static Dictionary GetIPv4Interfaces() { // zero conf ip address var zeroConf = new IPAddress(0); var adapters = NetworkInterface.GetAllNetworkInterfaces() .Where(network => network.OperationalStatus == OperationalStatus.Up && network.NetworkInterfaceType != NetworkInterfaceType.Unknown && network.NetworkInterfaceType != NetworkInterfaceType.Loopback) .ToArray(); var result = new Dictionary(); foreach (var adapter in adapters) { var properties = adapter.GetIPProperties(); if (properties == null || properties.GatewayAddresses.Count == 0 || properties.GatewayAddresses.All(gateway => Equals(gateway.Address, zeroConf)) || properties.UnicastAddresses.Count == 0 || properties.GatewayAddresses.All(address => Equals(address.Address, zeroConf)) || properties.UnicastAddresses.Any(a => a.Address.AddressFamily == AddressFamily.InterNetwork) == false) continue; result[adapter] = properties; } return result; } /// /// Retrieves the local ip addresses. /// /// if set to true [include loopback]. /// An array of local ip addresses. public static IPAddress[] GetIPv4Addresses(bool includeLoopback = true) => GetIPv4Addresses(NetworkInterfaceType.Unknown, true, includeLoopback); /// /// Retrieves the local ip addresses. /// /// Type of the interface. /// if set to true [skip type filter]. /// if set to true [include loopback]. /// An array of local ip addresses. public static IPAddress[] GetIPv4Addresses( NetworkInterfaceType interfaceType, bool skipTypeFilter = false, bool includeLoopback = false) { var addressList = new List(); var interfaces = NetworkInterface.GetAllNetworkInterfaces() .Where(ni => #if NET461 ni.IsReceiveOnly == false && #endif (skipTypeFilter || ni.NetworkInterfaceType == interfaceType) && ni.OperationalStatus == OperationalStatus.Up) .ToArray(); foreach (var networkInterface in interfaces) { var properties = networkInterface.GetIPProperties(); if (properties.GatewayAddresses.All(g => g.Address.AddressFamily != AddressFamily.InterNetwork)) continue; addressList.AddRange(properties.UnicastAddresses .Where(i => i.Address.AddressFamily == AddressFamily.InterNetwork) .Select(i => i.Address)); } if (includeLoopback || interfaceType == NetworkInterfaceType.Loopback) addressList.Add(IPAddress.Loopback); return addressList.ToArray(); } /// /// Gets the public IP address using ipify.org. /// /// The cancellation token. /// A public IP address of the result produced by this Task. public static async Task GetPublicIPAddressAsync(CancellationToken cancellationToken = default) { using var client = new HttpClient(); var response = await client.GetAsync("https://api.ipify.org", cancellationToken).ConfigureAwait(false); return IPAddress.Parse(await response.Content.ReadAsStringAsync().ConfigureAwait(false)); } /// /// Gets the configured IPv4 DNS servers for the active network interfaces. /// /// /// A collection of NetworkInterface/IPInterfaceProperties pairs /// that represents the active IPv4 interfaces. /// public static IPAddress[] GetIPv4DnsServers() => GetIPv4Interfaces() .Select(a => a.Value.DnsAddresses.Where(d => d.AddressFamily == AddressFamily.InterNetwork)) .SelectMany(d => d) .ToArray(); #endregion #region DNS and NTP Clients /// /// Gets the DNS host entry (a list of IP addresses) for the domain name. /// /// The FQDN. /// An array of local ip addresses of the result produced by this task. public static Task GetDnsHostEntryAsync(string fqdn) { var dnsServer = GetIPv4DnsServers().FirstOrDefault() ?? IPAddress.Parse("8.8.8.8"); return GetDnsHostEntryAsync(fqdn, dnsServer, DnsDefaultPort); } /// /// Gets the DNS host entry (a list of IP addresses) for the domain name. /// /// The FQDN. /// The DNS server. /// The port. /// /// An array of local ip addresses of the result produced by this task. /// /// fqdn. public static async Task GetDnsHostEntryAsync(string fqdn, IPAddress dnsServer, int port) { if (fqdn == null) throw new ArgumentNullException(nameof(fqdn)); if (fqdn.IndexOf(".", StringComparison.Ordinal) == -1) { fqdn += "." + IPGlobalProperties.GetIPGlobalProperties().DomainName; } while (true) { if (!fqdn.EndsWith(".", StringComparison.OrdinalIgnoreCase)) break; fqdn = fqdn.Substring(0, fqdn.Length - 1); } var client = new DnsClient(dnsServer, port); var result = await client.Lookup(fqdn).ConfigureAwait(false); return result.ToArray(); } /// /// Gets the reverse lookup FQDN of the given IP Address. /// /// The query. /// The DNS server. /// The port. /// A that represents the current object. public static Task GetDnsPointerEntryAsync(IPAddress query, IPAddress dnsServer, int port) { var client = new DnsClient(dnsServer, port); return client.Reverse(query); } /// /// Gets the reverse lookup FQDN of the given IP Address. /// /// The query. /// A that represents the current object. public static Task GetDnsPointerEntryAsync(IPAddress query) { var client = new DnsClient(GetIPv4DnsServers().FirstOrDefault()); return client.Reverse(query); } /// /// Queries the DNS server for the specified record type. /// /// The query. /// Type of the record. /// The DNS server. /// The port. /// Queries the DNS server for the specified record type of the result produced by this Task. public static async Task QueryDnsAsync(string query, DnsRecordType recordType, IPAddress dnsServer, int port) { if (query == null) throw new ArgumentNullException(nameof(query)); var client = new DnsClient(dnsServer, port); var response = await client.Resolve(query, recordType).ConfigureAwait(false); return new DnsQueryResult(response); } /// /// Queries the DNS server for the specified record type. /// /// The query. /// Type of the record. /// Queries the DNS server for the specified record type of the result produced by this Task. public static Task QueryDnsAsync(string query, DnsRecordType recordType) => QueryDnsAsync(query, recordType, GetIPv4DnsServers().FirstOrDefault(), DnsDefaultPort); /// /// Gets the UTC time by querying from an NTP server. /// /// The NTP server address. /// The port. /// The UTC time by querying from an NTP server of the result produced by this Task. public static async Task GetNetworkTimeUtcAsync(IPAddress ntpServerAddress, int port = NtpDefaultPort) { if (ntpServerAddress == null) throw new ArgumentNullException(nameof(ntpServerAddress)); // NTP message size - 16 bytes of the digest (RFC 2030) var ntpData = new byte[48]; // Setting the Leap Indicator, Version Number and Mode values ntpData[0] = 0x1B; // LI = 0 (no warning), VN = 3 (IPv4 only), Mode = 3 (Client Mode) // The UDP port number assigned to NTP is 123 var endPoint = new IPEndPoint(ntpServerAddress, port); var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); #if !NET461 await socket.ConnectAsync(endPoint).ConfigureAwait(false); #else socket.Connect(endPoint); #endif socket.ReceiveTimeout = 3000; // Stops code hang if NTP is blocked socket.Send(ntpData); socket.Receive(ntpData); socket.Dispose(); // Offset to get to the "Transmit Timestamp" field (time at which the reply // departed the server for the client, in 64-bit timestamp format." const byte serverReplyTime = 40; // Get the seconds part ulong intPart = BitConverter.ToUInt32(ntpData, serverReplyTime); // Get the seconds fraction ulong fractPart = BitConverter.ToUInt32(ntpData, serverReplyTime + 4); // Convert From big-endian to little-endian to match the platform if (BitConverter.IsLittleEndian) { intPart = intPart.SwapEndianness(); fractPart = intPart.SwapEndianness(); } var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L); // The time is given in UTC return new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds((long) milliseconds); } /// /// Gets the UTC time by querying from an NTP server. /// /// The NTP server, by default pool.ntp.org. /// The port, by default NTP 123. /// The UTC time by querying from an NTP server of the result produced by this Task. public static async Task GetNetworkTimeUtcAsync(string ntpServerName = "pool.ntp.org", int port = NtpDefaultPort) { var addresses = await GetDnsHostEntryAsync(ntpServerName).ConfigureAwait(false); return await GetNetworkTimeUtcAsync(addresses.First(), port).ConfigureAwait(false); } #endregion } }