using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; namespace TWASys_App.Models.ServerInfo { public class NetUtils { public static string? GetPrimaryPrivateIPv4String() { if (!Socket.OSSupportsIPv4) return null; try { using var s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); s.Connect(new IPEndPoint(IPAddress.Parse("8.8.8.8"), 53)); return (s.LocalEndPoint as IPEndPoint)?.Address.ToString(); } catch { // fallback: liệt kê NIC return GetAllPrivateIPsStrings().FirstOrDefault(ip => ip.Contains('.')); } } public static string? GetPrimaryPrivateIPv6String() { if (!Socket.OSSupportsIPv6) return null; try { using var s = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); s.Connect(new IPEndPoint(IPAddress.Parse("2001:4860:4860::8888"), 53)); var ip = (s.LocalEndPoint as IPEndPoint)?.Address; return ip is null ? null : FormatIPv6WithScope(ip); } catch { var ip = FirstIPv6_ByEnum(); return ip is null ? null : FormatIPv6WithScope(ip); } } public static IEnumerable GetAllPrivateIPsStrings() { foreach (var ni in NetworkInterface.GetAllNetworkInterfaces()) { if (ni.OperationalStatus != OperationalStatus.Up) continue; if (ni.NetworkInterfaceType is NetworkInterfaceType.Loopback or NetworkInterfaceType.Tunnel) continue; var idx = ni.GetIPProperties().GetIPv6Properties()?.Index; foreach (var ua in ni.GetIPProperties().UnicastAddresses) { var ip = ua.Address; if (IPAddress.IsLoopback(ip)) continue; if (ip.AddressFamily == AddressFamily.InterNetwork) { if (IsPrivateIPv4(ip)) yield return ip.ToString(); } else if (ip.AddressFamily == AddressFamily.InterNetworkV6) { if (IsPrivateIPv6(ip)) yield return FormatIPv6WithScope(ip, idx); } } } } // ---------- PUBLIC IP (string) ---------- public static async Task GetPublicIPv4Async(CancellationToken ct = default) => await FetchFirstAsync(new[] { "https://api.ipify.org", "https://checkip.amazonaws.com", "https://ipv4.icanhazip.com" }, ct); public static async Task GetPublicIPv6Async(CancellationToken ct = default) => await FetchFirstAsync(new[] { "https://api6.ipify.org", "https://ipv6.icanhazip.com", "https://ifconfig.co/ip" }, ct); public static async Task GetPublicIpAsync(bool preferIPv6 = false, CancellationToken ct = default) => preferIPv6 ? await GetPublicIPv6Async(ct) ?? await GetPublicIPv4Async(ct) : await GetPublicIPv4Async(ct) ?? await GetPublicIPv6Async(ct); // ---------- helpers ---------- static async Task FetchFirstAsync(IEnumerable urls, CancellationToken ct) { using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(3) }; foreach (var u in urls) try { var s = (await http.GetStringAsync(u, ct)).Trim(); if (!string.IsNullOrWhiteSpace(s)) return s; } catch { /* thử URL kế */ } return null; } static IPAddress? FirstIPv6_ByEnum() => NetworkInterface.GetAllNetworkInterfaces() .Where(n => n.OperationalStatus == OperationalStatus.Up && n.NetworkInterfaceType != NetworkInterfaceType.Loopback && n.NetworkInterfaceType != NetworkInterfaceType.Tunnel) .SelectMany(n => n.GetIPProperties().UnicastAddresses) .Select(u => u.Address) .FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetworkV6); static bool IsPrivateIPv4(IPAddress ip) { var b = ip.GetAddressBytes(); return b[0] == 10 || (b[0] == 172 && b[1] is >= 16 and <= 31) || (b[0] == 192 && b[1] == 168); } static bool IsPrivateIPv6(IPAddress ip) { var b = ip.GetAddressBytes(); return ((b[0] & 0xFE) == 0xFC) || (b[0] == 0xFE && (b[1] & 0xC0) == 0x80); // fc00::/7, fe80::/10 } // KHÔNG đụng IPAddress.ScopeId. Nếu link-local, thêm scope = index của NIC. static string FormatIPv6WithScope(IPAddress ip, int? nicIndex = null) { if (ip.AddressFamily != AddressFamily.InterNetworkV6) return ip.ToString(); var b = ip.GetAddressBytes(); bool linkLocal = b[0] == 0xFE && (b[1] & 0xC0) == 0x80; if (linkLocal && nicIndex is int idx) return $"{ip}%{idx}"; return ip.ToString(); } } }