/* * Copyright (c) 2025 Proton AG * * This file is part of ProtonVPN. * * ProtonVPN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProtonVPN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProtonVPN. If not, see . */ using System.Net; using System.Net.Sockets; namespace ProtonVPN.Common.Core.Networking; public class IpAddress { // Prefix for 64:ff9b::/96 - Well-Known Prefix for NAT64 private static readonly byte[] _ipv6Nat64Prefix = [0x00, 0x64, 0xff, 0x9b]; // Prefix for 2001:2::/48 - Benchmarking private static readonly byte[] _ipv6BenchmarkPrefix = [0x20, 0x01, 0x00, 0x02]; // Prefix for 2001:db8::/32 - Documentation private static readonly byte[] _ipv6DocumentationPrefix = [0x20, 0x01, 0x0d, 0xb8]; // Prefix for 100::/64 - Discard-Only Address Block private static readonly byte[] _ipv6DiscardPrefix = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; private IPAddress _systemTypeIpAddress = IPAddress.None; public byte[] Bytes { get => _systemTypeIpAddress.GetAddressBytes(); set => _systemTypeIpAddress = new IPAddress(value); } public IpAddress() { } public IpAddress(IPAddress systemTypeIpAddress) { _systemTypeIpAddress = systemTypeIpAddress; } public IpAddress(byte[] bytes) { Bytes = bytes; } public override string ToString() { return _systemTypeIpAddress.ToString(); } public IPAddress GetSystemType() { return _systemTypeIpAddress; } /// /// Determines if the IP address is a public, globally routable address. /// This implementation checks against all IANA-reserved special-use IP ranges. /// /// True if the IP is public, false otherwise. public bool IsPublicIp() { return _systemTypeIpAddress.AddressFamily switch { AddressFamily.InterNetwork => IsPublicIpv4(), AddressFamily.InterNetworkV6 => IsPublicIpv6(), _ => false }; } /// /// Determines if an IPv4 address is public by checking against the IANA Special-Purpose Address Registry. /// See: https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml /// private bool IsPublicIpv4() { byte[] bytes = _systemTypeIpAddress.GetAddressBytes(); return !( // RFC 1918: Private-Use bytes[0] == 10 || // 10.0.0.0/8 (bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31) || // 172.16.0.0/12 (bytes[0] == 192 && bytes[1] == 168) || // 192.168.0.0/16 // RFC 5735, 6890: Special-Use bytes[0] == 0 || // 0.0.0.0/8 - "This network" bytes[0] == 127 || // 127.0.0.0/8 - Loopback (bytes[0] == 169 && bytes[1] == 254) || // 169.254.0.0/16 - Link-Local (APIPA) // RFC 6598: Shared Address Space (bytes[0] == 100 && bytes[1] >= 64 && bytes[1] <= 127) || // 100.64.0.0/10 - Carrier-grade NAT // RFC 5737: Documentation (TEST-NET-1, 2, 3) (bytes[0] == 192 && bytes[1] == 0 && bytes[2] == 2) || // 192.0.2.0/24 - TEST-NET-1 (bytes[0] == 198 && bytes[1] == 51 && bytes[2] == 100) || // 198.51.100.0/24 - TEST-NET-2 (bytes[0] == 203 && bytes[1] == 0 && bytes[2] == 113) || // 203.0.113.0/24 - TEST-NET-3 // RFC 3068, 5736: Other reserved ranges (bytes[0] == 192 && bytes[1] == 0 && bytes[2] == 0) || // 192.0.0.0/24 - IETF Protocol Assignments (bytes[0] == 192 && bytes[1] == 88 && bytes[2] == 99) || // 192.88.99.0/24 - 6to4 Relay Anycast (Deprecated) (bytes[0] == 198 && (bytes[1] >= 18 && bytes[1] <= 19)) || // 198.18.0.0/15 - Benchmarking // RFC 1112, 6890: Multicast and future use bytes[0] >= 224); // 224.0.0.0+ - Multicast, Reserved, and Broadcast } /// /// Determines if an IPv6 address is public by checking against the IANA Special-Purpose Address Registry. /// See: https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml /// private bool IsPublicIpv6() { if (IPAddress.IsLoopback(_systemTypeIpAddress) || // ::1/128 _systemTypeIpAddress.IsIPv6LinkLocal || // fe80::/10 _systemTypeIpAddress.IsIPv6Multicast || // ff00::/8 _systemTypeIpAddress.IsIPv6Teredo || // 2001::/32 _systemTypeIpAddress.IsIPv6SiteLocal) // fec0::/10 (Deprecated, but good to check) { return false; } byte[] bytes = _systemTypeIpAddress.GetAddressBytes(); ReadOnlySpan byteSpan = bytes; return !( _systemTypeIpAddress.Equals(IPAddress.IPv6None) || // ::/128 - Unspecified Address ((bytes[0] & 0xFE) == 0xFC) || // fc00::/7 - Unique Local Addresses (ULA) byteSpan.StartsWith(_ipv6DocumentationPrefix) || // 2001:db8::/32 byteSpan.StartsWith(_ipv6BenchmarkPrefix) || // 2001:2::/48 IsNat64(byteSpan) || // 64:ff9b::/96 byteSpan.StartsWith(_ipv6DiscardPrefix)); // 100::/64 } private static bool IsNat64(ReadOnlySpan bytes) { // Check for 64:ff9b::/96 and 64:ff9b:1::/48 return bytes.StartsWith(_ipv6Nat64Prefix) && // Check if the rest of the 96-bit prefix is all zeros (bytes.Slice(4, 8).SequenceEqual(stackalloc byte[8]) || // Or if it matches the /48 for local use (64:ff9b:1::/48) (bytes[4] == 0 && bytes[5] == 1)); } }