diff --git a/src/System.Net.IPNetwork/IPNetwork2IANAblock.cs b/src/System.Net.IPNetwork/IPNetwork2IANAblock.cs index 289da81..af77887 100644 --- a/src/System.Net.IPNetwork/IPNetwork2IANAblock.cs +++ b/src/System.Net.IPNetwork/IPNetwork2IANAblock.cs @@ -4,57 +4,166 @@ namespace System.Net; +using System.Net.Sockets; + /// /// IANA Blocks. /// public sealed partial class IPNetwork2 { + // IPv4 RFC 1918 Private Address Blocks private static readonly Lazy IanaAblockReserved = new (() => Parse("10.0.0.0/8")); private static readonly Lazy IanaBblockReserved = new (() => Parse("172.16.0.0/12")); private static readonly Lazy IanaCblockReserved = new (() => Parse("192.168.0.0/16")); + // IPv4 Additional IANA Reserved Blocks + private static readonly Lazy IanaThisNetwork = new (() => Parse("0.0.0.0/8")); + private static readonly Lazy IanaLoopback = new (() => Parse("127.0.0.0/8")); + private static readonly Lazy IanaLinkLocal = new (() => Parse("169.254.0.0/16")); + private static readonly Lazy IanaIetfProtocol = new (() => Parse("192.0.0.0/24")); + private static readonly Lazy IanaTestNet1 = new (() => Parse("192.0.2.0/24")); + private static readonly Lazy IanaBenchmark = new (() => Parse("198.18.0.0/15")); + private static readonly Lazy IanaTestNet2 = new (() => Parse("198.51.100.0/24")); + private static readonly Lazy IanaTestNet3 = new (() => Parse("203.0.113.0/24")); + private static readonly Lazy IanaMulticast = new (() => Parse("224.0.0.0/4")); + private static readonly Lazy IanaReserved = new (() => Parse("240.0.0.0/4")); + private static readonly Lazy IanaBroadcast = new (() => Parse("255.255.255.255/32")); + + // IPv6 IANA Reserved Blocks + private static readonly Lazy Iana6Unspecified = new (() => Parse("::/128")); + private static readonly Lazy Iana6Loopback = new (() => Parse("::1/128")); + private static readonly Lazy Iana6Ipv4Mapped = new (() => Parse("::ffff:0:0/96")); + private static readonly Lazy Iana6Ipv4Translation = new (() => Parse("64:ff9b::/96")); + private static readonly Lazy Iana6Teredo = new (() => Parse("2001::/32")); + private static readonly Lazy Iana6Documentation = new (() => Parse("2001:db8::/32")); + private static readonly Lazy Iana6UniqueLocal = new (() => Parse("fc00::/7")); + private static readonly Lazy Iana6LinkLocal = new (() => Parse("fe80::/10")); + private static readonly Lazy Iana6Multicast = new (() => Parse("ff00::/8")); + /// /// Gets 10.0.0.0/8. /// /// The IANA reserved IPNetwork 10.0.0.0/8. - public static IPNetwork2 IANA_ABLK_RESERVED1 - { - get - { - return IanaAblockReserved.Value; - } - } + public static IPNetwork2 IANA_ABLK_RESERVED1 => IanaAblockReserved.Value; /// - /// Gets 172.12.0.0/12. + /// Gets 172.16.0.0/12. /// - /// The IANA reserved IPNetwork 172.12.0.0/12. - public static IPNetwork2 IANA_BBLK_RESERVED1 - { - get - { - return IanaBblockReserved.Value; - } - } + /// The IANA reserved IPNetwork 172.16.0.0/12. + public static IPNetwork2 IANA_BBLK_RESERVED1 => IanaBblockReserved.Value; /// /// Gets 192.168.0.0/16. /// /// The IANA reserved IPNetwork 192.168.0.0/16. - public static IPNetwork2 IANA_CBLK_RESERVED1 - { - get - { - return IanaCblockReserved.Value; - } - } + public static IPNetwork2 IANA_CBLK_RESERVED1 => IanaCblockReserved.Value; + + /// + /// Gets 0.0.0.0/8 (This network). + /// + public static IPNetwork2 IANA_THIS_NETWORK => IanaThisNetwork.Value; + + /// + /// Gets 127.0.0.0/8 (Loopback). + /// + public static IPNetwork2 IANA_LOOPBACK => IanaLoopback.Value; + + /// + /// Gets 169.254.0.0/16 (Link-local). + /// + public static IPNetwork2 IANA_LINK_LOCAL => IanaLinkLocal.Value; + + /// + /// Gets 192.0.0.0/24 (IETF Protocol Assignments). + /// + public static IPNetwork2 IANA_IETF_PROTOCOL => IanaIetfProtocol.Value; + + /// + /// Gets 192.0.2.0/24 (Documentation TEST-NET-1). + /// + public static IPNetwork2 IANA_TEST_NET1 => IanaTestNet1.Value; + + /// + /// Gets 198.18.0.0/15 (Benchmarking). + /// + public static IPNetwork2 IANA_BENCHMARK => IanaBenchmark.Value; + + /// + /// Gets 198.51.100.0/24 (Documentation TEST-NET-2). + /// + public static IPNetwork2 IANA_TEST_NET2 => IanaTestNet2.Value; + + /// + /// Gets 203.0.113.0/24 (Documentation TEST-NET-3). + /// + public static IPNetwork2 IANA_TEST_NET3 => IanaTestNet3.Value; + + /// + /// Gets 224.0.0.0/4 (Multicast). + /// + public static IPNetwork2 IANA_MULTICAST => IanaMulticast.Value; + + /// + /// Gets 240.0.0.0/4 (Reserved). + /// + public static IPNetwork2 IANA_RESERVED => IanaReserved.Value; + + /// + /// Gets 255.255.255.255/32 (Broadcast). + /// + public static IPNetwork2 IANA_BROADCAST => IanaBroadcast.Value; + + /// + /// Gets ::/128 (Unspecified). + /// + public static IPNetwork2 IANA6_UNSPECIFIED => Iana6Unspecified.Value; + + /// + /// Gets ::1/128 (Loopback). + /// + public static IPNetwork2 IANA6_LOOPBACK => Iana6Loopback.Value; + + /// + /// Gets ::ffff:0:0/96 (IPv4-mapped). + /// + public static IPNetwork2 IANA6_IPV4_MAPPED => Iana6Ipv4Mapped.Value; + + /// + /// Gets 64:ff9b::/96 (IPv4/IPv6 translation). + /// + public static IPNetwork2 IANA6_IPV4_TRANSLATION => Iana6Ipv4Translation.Value; + + /// + /// Gets 2001::/32 (TEREDO). + /// + public static IPNetwork2 IANA6_TEREDO => Iana6Teredo.Value; + + /// + /// Gets 2001:db8::/32 (Documentation). + /// + public static IPNetwork2 IANA6_DOCUMENTATION => Iana6Documentation.Value; + + /// + /// Gets fc00::/7 (Unique local). + /// + public static IPNetwork2 IANA6_UNIQUE_LOCAL => Iana6UniqueLocal.Value; + + /// + /// Gets fe80::/10 (Link-local). + /// + public static IPNetwork2 IANA6_LINK_LOCAL => Iana6LinkLocal.Value; + + /// + /// Gets ff00::/8 (Multicast). + /// + public static IPNetwork2 IANA6_MULTICAST => Iana6Multicast.Value; /// /// return true if ipaddress is contained in - /// IANA_ABLK_RESERVED1, IANA_BBLK_RESERVED1, IANA_CBLK_RESERVED1. + /// any IANA reserved block (IPv4 or IPv6). /// - /// A string containing an ip address to convert. - /// true if ipaddress is a IANA reserverd IP Netowkr ; otherwise, false. + /// An IP address to check. + /// true if ipaddress is in an IANA reserved block; otherwise, false. public static bool IsIANAReserved(IPAddress ipaddress) { if (ipaddress == null) @@ -62,17 +171,20 @@ public static bool IsIANAReserved(IPAddress ipaddress) throw new ArgumentNullException(nameof(ipaddress)); } - return IANA_ABLK_RESERVED1.Contains(ipaddress) - || IANA_BBLK_RESERVED1.Contains(ipaddress) - || IANA_CBLK_RESERVED1.Contains(ipaddress); + if (ipaddress.AddressFamily == AddressFamily.InterNetworkV6) + { + return IsIANAReservedIPv6(ipaddress); + } + + return IsIANAReservedIPv4(ipaddress); } /// /// return true if ipnetwork is contained in - /// IANA_ABLK_RESERVED1, IANA_BBLK_RESERVED1, IANA_CBLK_RESERVED1. + /// any IANA reserved block (IPv4 or IPv6). /// /// The IPNetwork to test. - /// true if the ipnetwork is a IANA reserverd IP Netowkr ; otherwise, false. + /// true if the ipnetwork is in an IANA reserved block; otherwise, false. public static bool IsIANAReserved(IPNetwork2 ipnetwork) { if (ipnetwork == null) @@ -84,14 +196,79 @@ public static bool IsIANAReserved(IPNetwork2 ipnetwork) } /// - /// return true if ipnetwork is contained in - /// IANA_ABLK_RESERVED1, IANA_BBLK_RESERVED1, IANA_CBLK_RESERVED1. + /// return true if this ipnetwork is contained in + /// any IANA reserved block (IPv4 or IPv6). /// - /// true if the ipnetwork is a IANA reserverd IP Netowkr ; otherwise, false. + /// true if the ipnetwork is in an IANA reserved block; otherwise, false. public bool IsIANAReserved() { - return IANA_ABLK_RESERVED1.Contains(this) - || IANA_BBLK_RESERVED1.Contains(this) - || IANA_CBLK_RESERVED1.Contains(this); + if (this.family == AddressFamily.InterNetworkV6) + { + return IsIANAReservedIPv6(this); + } + + return IsIANAReservedIPv4(this); + } + + private static bool IsIANAReservedIPv4(IPAddress ipaddress) + { + return IANA_THIS_NETWORK.Contains(ipaddress) + || IANA_LOOPBACK.Contains(ipaddress) + || IANA_ABLK_RESERVED1.Contains(ipaddress) + || IANA_LINK_LOCAL.Contains(ipaddress) + || IANA_BBLK_RESERVED1.Contains(ipaddress) + || IANA_IETF_PROTOCOL.Contains(ipaddress) + || IANA_TEST_NET1.Contains(ipaddress) + || IANA_CBLK_RESERVED1.Contains(ipaddress) + || IANA_BENCHMARK.Contains(ipaddress) + || IANA_TEST_NET2.Contains(ipaddress) + || IANA_TEST_NET3.Contains(ipaddress) + || IANA_MULTICAST.Contains(ipaddress) + || IANA_RESERVED.Contains(ipaddress) + || IANA_BROADCAST.Contains(ipaddress); + } + + private static bool IsIANAReservedIPv4(IPNetwork2 ipnetwork) + { + return IANA_THIS_NETWORK.Contains(ipnetwork) + || IANA_LOOPBACK.Contains(ipnetwork) + || IANA_ABLK_RESERVED1.Contains(ipnetwork) + || IANA_LINK_LOCAL.Contains(ipnetwork) + || IANA_BBLK_RESERVED1.Contains(ipnetwork) + || IANA_IETF_PROTOCOL.Contains(ipnetwork) + || IANA_TEST_NET1.Contains(ipnetwork) + || IANA_CBLK_RESERVED1.Contains(ipnetwork) + || IANA_BENCHMARK.Contains(ipnetwork) + || IANA_TEST_NET2.Contains(ipnetwork) + || IANA_TEST_NET3.Contains(ipnetwork) + || IANA_MULTICAST.Contains(ipnetwork) + || IANA_RESERVED.Contains(ipnetwork) + || IANA_BROADCAST.Contains(ipnetwork); + } + + private static bool IsIANAReservedIPv6(IPAddress ipaddress) + { + return IANA6_UNSPECIFIED.Contains(ipaddress) + || IANA6_LOOPBACK.Contains(ipaddress) + || IANA6_IPV4_MAPPED.Contains(ipaddress) + || IANA6_IPV4_TRANSLATION.Contains(ipaddress) + || IANA6_TEREDO.Contains(ipaddress) + || IANA6_DOCUMENTATION.Contains(ipaddress) + || IANA6_UNIQUE_LOCAL.Contains(ipaddress) + || IANA6_LINK_LOCAL.Contains(ipaddress) + || IANA6_MULTICAST.Contains(ipaddress); + } + + private static bool IsIANAReservedIPv6(IPNetwork2 ipnetwork) + { + return IANA6_UNSPECIFIED.Contains(ipnetwork) + || IANA6_LOOPBACK.Contains(ipnetwork) + || IANA6_IPV4_MAPPED.Contains(ipnetwork) + || IANA6_IPV4_TRANSLATION.Contains(ipnetwork) + || IANA6_TEREDO.Contains(ipnetwork) + || IANA6_DOCUMENTATION.Contains(ipnetwork) + || IANA6_UNIQUE_LOCAL.Contains(ipnetwork) + || IANA6_LINK_LOCAL.Contains(ipnetwork) + || IANA6_MULTICAST.Contains(ipnetwork); } -} \ No newline at end of file +} diff --git a/src/TestProject/IPNetworkTest/IPNetworkIanaBlocksExtendedTests.cs b/src/TestProject/IPNetworkTest/IPNetworkIanaBlocksExtendedTests.cs new file mode 100644 index 0000000..5aa0a97 --- /dev/null +++ b/src/TestProject/IPNetworkTest/IPNetworkIanaBlocksExtendedTests.cs @@ -0,0 +1,184 @@ +// +// Copyright (c) IPNetwork. All rights reserved. +// + +namespace TestProject.IPNetworkTest; + +/// +/// Tests for extended IANA reserved blocks (IPv4 and IPv6). +/// +[TestClass] +public class IPNetworkIanaBlocksExtendedTests +{ + /// + /// IPv4 addresses that should be IANA reserved. + /// + [TestMethod] + [DataRow("0.0.0.1", DisplayName = "This network (0.0.0.0/8)")] + [DataRow("0.255.255.255", DisplayName = "This network last")] + [DataRow("127.0.0.1", DisplayName = "Loopback (127.0.0.0/8)")] + [DataRow("127.255.255.255", DisplayName = "Loopback last")] + [DataRow("10.0.0.1", DisplayName = "RFC1918 Class A (10.0.0.0/8)")] + [DataRow("169.254.1.1", DisplayName = "Link-local (169.254.0.0/16)")] + [DataRow("172.16.0.1", DisplayName = "RFC1918 Class B (172.16.0.0/12)")] + [DataRow("172.31.255.255", DisplayName = "RFC1918 Class B last")] + [DataRow("192.0.0.1", DisplayName = "IETF Protocol (192.0.0.0/24)")] + [DataRow("192.0.2.1", DisplayName = "TEST-NET-1 (192.0.2.0/24)")] + [DataRow("192.168.0.1", DisplayName = "RFC1918 Class C (192.168.0.0/16)")] + [DataRow("198.18.0.1", DisplayName = "Benchmarking (198.18.0.0/15)")] + [DataRow("198.19.255.255", DisplayName = "Benchmarking last")] + [DataRow("198.51.100.1", DisplayName = "TEST-NET-2 (198.51.100.0/24)")] + [DataRow("203.0.113.1", DisplayName = "TEST-NET-3 (203.0.113.0/24)")] + [DataRow("224.0.0.1", DisplayName = "Multicast (224.0.0.0/4)")] + [DataRow("239.255.255.255", DisplayName = "Multicast last")] + [DataRow("240.0.0.1", DisplayName = "Reserved (240.0.0.0/4)")] + [DataRow("255.255.255.255", DisplayName = "Broadcast")] + public void IsIANAReserved_IPv4_ReservedAddress_ReturnsTrue(string ipaddress) + { + var ip = IPAddress.Parse(ipaddress); + Assert.IsTrue(IPNetwork2.IsIANAReserved(ip), $"{ipaddress} should be IANA reserved"); + } + + /// + /// IPv4 addresses that should NOT be IANA reserved. + /// + [TestMethod] + [DataRow("1.0.0.1", DisplayName = "Public 1.0.0.1")] + [DataRow("8.8.8.8", DisplayName = "Google DNS")] + [DataRow("100.64.0.1", DisplayName = "Shared address space")] + [DataRow("185.199.108.153", DisplayName = "GitHub Pages")] + public void IsIANAReserved_IPv4_PublicAddress_ReturnsFalse(string ipaddress) + { + var ip = IPAddress.Parse(ipaddress); + Assert.IsFalse(IPNetwork2.IsIANAReserved(ip), $"{ipaddress} should not be IANA reserved"); + } + + /// + /// IPv4 networks that should be IANA reserved. + /// + [TestMethod] + [DataRow("127.0.0.0/8", DisplayName = "Loopback /8")] + [DataRow("10.10.0.0/16", DisplayName = "RFC1918 subnet")] + [DataRow("192.168.1.0/24", DisplayName = "RFC1918 Class C subnet")] + [DataRow("224.0.0.0/24", DisplayName = "Multicast subnet")] + [DataRow("169.254.0.0/24", DisplayName = "Link-local subnet")] + [DataRow("192.0.2.0/24", DisplayName = "TEST-NET-1")] + [DataRow("198.51.100.0/24", DisplayName = "TEST-NET-2")] + [DataRow("203.0.113.0/24", DisplayName = "TEST-NET-3")] + public void IsIANAReserved_IPv4_ReservedNetwork_ReturnsTrue(string network) + { + var ipnetwork = IPNetwork2.Parse(network); + Assert.IsTrue(ipnetwork.IsIANAReserved(), $"{network} should be IANA reserved"); + } + + /// + /// IPv4 networks that should NOT be IANA reserved. + /// + [TestMethod] + [DataRow("8.8.8.0/24", DisplayName = "Google DNS /24")] + [DataRow("1.0.0.0/8", DisplayName = "Public /8")] + public void IsIANAReserved_IPv4_PublicNetwork_ReturnsFalse(string network) + { + var ipnetwork = IPNetwork2.Parse(network); + Assert.IsFalse(ipnetwork.IsIANAReserved(), $"{network} should not be IANA reserved"); + } + + /// + /// IPv6 addresses that should be IANA reserved. + /// + [TestMethod] + [DataRow("::", DisplayName = "Unspecified ::/128")] + [DataRow("::1", DisplayName = "Loopback ::1/128")] + [DataRow("::ffff:192.168.1.1", DisplayName = "IPv4-mapped")] + [DataRow("64:ff9b::1", DisplayName = "IPv4/IPv6 translation")] + [DataRow("2001::1", DisplayName = "TEREDO")] + [DataRow("2001:db8::1", DisplayName = "Documentation")] + [DataRow("fd00::1", DisplayName = "Unique local (fd00::)")] + [DataRow("fc00::1", DisplayName = "Unique local (fc00::)")] + [DataRow("fe80::1", DisplayName = "Link-local")] + [DataRow("ff02::1", DisplayName = "Multicast")] + [DataRow("ff00::1", DisplayName = "Multicast (ff00::)")] + public void IsIANAReserved_IPv6_ReservedAddress_ReturnsTrue(string ipaddress) + { + var ip = IPAddress.Parse(ipaddress); + Assert.IsTrue(IPNetwork2.IsIANAReserved(ip), $"{ipaddress} should be IANA reserved"); + } + + /// + /// IPv6 addresses that should NOT be IANA reserved. + /// + [TestMethod] + [DataRow("2607:f8b0:4004:800::200e", DisplayName = "Google")] + [DataRow("2606:4700:4700::1111", DisplayName = "Cloudflare DNS")] + public void IsIANAReserved_IPv6_PublicAddress_ReturnsFalse(string ipaddress) + { + var ip = IPAddress.Parse(ipaddress); + Assert.IsFalse(IPNetwork2.IsIANAReserved(ip), $"{ipaddress} should not be IANA reserved"); + } + + /// + /// IPv6 networks that should be IANA reserved. + /// + [TestMethod] + [DataRow("fd00::/48", DisplayName = "Unique local /48")] + [DataRow("fe80::/64", DisplayName = "Link-local /64")] + [DataRow("2001:db8::/48", DisplayName = "Documentation /48")] + [DataRow("ff00::/12", DisplayName = "Multicast /12")] + public void IsIANAReserved_IPv6_ReservedNetwork_ReturnsTrue(string network) + { + var ipnetwork = IPNetwork2.Parse(network); + Assert.IsTrue(ipnetwork.IsIANAReserved(), $"{network} should be IANA reserved"); + } + + /// + /// IPv6 networks that should NOT be IANA reserved. + /// + [TestMethod] + [DataRow("2607:f8b0::/32", DisplayName = "Google /32")] + [DataRow("2606:4700::/32", DisplayName = "Cloudflare /32")] + public void IsIANAReserved_IPv6_PublicNetwork_ReturnsFalse(string network) + { + var ipnetwork = IPNetwork2.Parse(network); + Assert.IsFalse(ipnetwork.IsIANAReserved(), $"{network} should not be IANA reserved"); + } + + /// + /// Static properties return expected values. + /// + [TestMethod] + [DataRow("0.0.0.0/8", DisplayName = "IANA_THIS_NETWORK")] + [DataRow("127.0.0.0/8", DisplayName = "IANA_LOOPBACK")] + [DataRow("169.254.0.0/16", DisplayName = "IANA_LINK_LOCAL")] + [DataRow("192.0.0.0/24", DisplayName = "IANA_IETF_PROTOCOL")] + [DataRow("192.0.2.0/24", DisplayName = "IANA_TEST_NET1")] + [DataRow("198.18.0.0/15", DisplayName = "IANA_BENCHMARK")] + [DataRow("198.51.100.0/24", DisplayName = "IANA_TEST_NET2")] + [DataRow("203.0.113.0/24", DisplayName = "IANA_TEST_NET3")] + [DataRow("224.0.0.0/4", DisplayName = "IANA_MULTICAST")] + [DataRow("240.0.0.0/4", DisplayName = "IANA_RESERVED")] + [DataRow("255.255.255.255/32", DisplayName = "IANA_BROADCAST")] + public void StaticProperty_IPv4_ReturnsExpected(string expected) + { + var network = IPNetwork2.Parse(expected); + Assert.IsTrue(network.IsIANAReserved(), $"{expected} should be IANA reserved"); + } + + /// + /// Static IPv6 properties return expected values. + /// + [TestMethod] + [DataRow("::/128", DisplayName = "IANA6_UNSPECIFIED")] + [DataRow("::1/128", DisplayName = "IANA6_LOOPBACK")] + [DataRow("::ffff:0:0/96", DisplayName = "IANA6_IPV4_MAPPED")] + [DataRow("64:ff9b::/96", DisplayName = "IANA6_IPV4_TRANSLATION")] + [DataRow("2001::/32", DisplayName = "IANA6_TEREDO")] + [DataRow("2001:db8::/32", DisplayName = "IANA6_DOCUMENTATION")] + [DataRow("fc00::/7", DisplayName = "IANA6_UNIQUE_LOCAL")] + [DataRow("fe80::/10", DisplayName = "IANA6_LINK_LOCAL")] + [DataRow("ff00::/8", DisplayName = "IANA6_MULTICAST")] + public void StaticProperty_IPv6_ReturnsExpected(string expected) + { + var network = IPNetwork2.Parse(expected); + Assert.IsTrue(network.IsIANAReserved(), $"{expected} should be IANA reserved"); + } +}