diff --git a/shared/src/main/scala/com/comcast/ip4s/Host.scala b/shared/src/main/scala/com/comcast/ip4s/Host.scala index 2045fc5..e0680fa 100644 --- a/shared/src/main/scala/com/comcast/ip4s/Host.scala +++ b/shared/src/main/scala/com/comcast/ip4s/Host.scala @@ -215,6 +215,9 @@ sealed abstract class IpAddress extends IpAddressPlatform with Host with Seriali def asSourceSpecificMulticastLenient: Option[SourceSpecificMulticast[this.type]] = SourceSpecificMulticast.fromIpAddressLenient(this) + /** Returns true if this address is in a private range. */ + def isPrivate: Boolean + /** Narrows this address to an Ipv4Address if that is the underlying type. */ def asIpv4: Option[Ipv4Address] = collapseMappedV4.fold(Some(_), _ => None) @@ -356,6 +359,11 @@ final class Ipv4Address private (protected val bytes: Array[Byte]) extends IpAdd override def isSourceSpecificMulticast: Boolean = this >= Ipv4Address.SourceSpecificMulticastRangeStart && this <= Ipv4Address.SourceSpecificMulticastRangeEnd + override def isPrivate: Boolean = + Ipv4Address.Classes.Private.A.contains(this) || + Ipv4Address.Classes.Private.B.contains(this) || + Ipv4Address.Classes.Private.C.contains(this) + /** Converts this V4 address to a compat V6 address, where the first 12 bytes are all zero and the last 4 bytes * contain the bytes of this V4 address. */ @@ -418,6 +426,38 @@ object Ipv4Address extends Ipv4AddressCompanionPlatform { val SourceSpecificMulticastRangeEnd: Ipv4Address = fromBytes(232, 255, 255, 255) + /** IPv4 address classes represented as CIDRs. */ + object Classes { + + /** Class A: 0.0.0.0 - 127.255.255.255 */ + val A: Cidr[Ipv4Address] = Cidr(fromBytes(0, 0, 0, 0), 1) + + /** Class B: 128.0.0.0 - 191.255.255.255 */ + val B: Cidr[Ipv4Address] = Cidr(fromBytes(128, 0, 0, 0), 2) + + /** Class C: 192.0.0.0 - 223.255.255.255 */ + val C: Cidr[Ipv4Address] = Cidr(fromBytes(192, 0, 0, 0), 3) + + /** Class D: 224.0.0.0 - 239.255.255.255 */ + val D: Cidr[Ipv4Address] = Cidr(fromBytes(224, 0, 0, 0), 4) + + /** Class E: 240.0.0.0 - 255.255.255.255 */ + val E: Cidr[Ipv4Address] = Cidr(fromBytes(240, 0, 0, 0), 5) + + /** Private address ranges. */ + object Private { + + /** Class A: 10.0.0.0 - 10.255.255.255 */ + val A: Cidr[Ipv4Address] = Cidr(fromBytes(10, 0, 0, 0), 8) + + /** Class B: 172.16.0.0 - 172.31.255.255 */ + val B: Cidr[Ipv4Address] = Cidr(fromBytes(172, 16, 0, 0), 12) + + /** Class A: 192.168.0.0 - 192.168.255.255 */ + val C: Cidr[Ipv4Address] = Cidr(fromBytes(192, 168, 0, 0), 16) + } + } + /** Parses an IPv4 address from a dotted-decimal string, returning `None` if the string is not a valid IPv4 address. */ def fromString(value: String): Option[Ipv4Address] = { @@ -609,6 +649,9 @@ final class Ipv6Address private (protected val bytes: Array[Byte]) extends IpAdd override def isSourceSpecificMulticast: Boolean = this >= Ipv6Address.SourceSpecificMulticastRangeStart && this <= Ipv6Address.SourceSpecificMulticastRangeEnd + override def isPrivate: Boolean = + Ipv6Address.UniqueLocalBlock.contains(this) || (isMappedV4 && collapseMappedV4.isPrivate) + /** Applies the supplied mask to this address. * * @example {{{ @@ -653,6 +696,10 @@ object Ipv6Address extends Ipv6AddressCompanionPlatform { val MappedV4Block: Cidr[Ipv6Address] = Cidr(Ipv6Address.fromBytes(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0), 96) + /** CIDR which defines unique local address block. */ + val UniqueLocalBlock: Cidr[Ipv6Address] = + Cidr(Ipv6Address.fromBytes(0xfc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 7) + /** Parses an IPv6 address from a string in RFC4291 notation, returning `None` if the string is not a valid IPv6 * address. */ diff --git a/test-kit/shared/src/test/scala/com/comcast/ip4s/Ipv4AddressTest.scala b/test-kit/shared/src/test/scala/com/comcast/ip4s/Ipv4AddressTest.scala index a955cd6..a190ff8 100644 --- a/test-kit/shared/src/test/scala/com/comcast/ip4s/Ipv4AddressTest.scala +++ b/test-kit/shared/src/test/scala/com/comcast/ip4s/Ipv4AddressTest.scala @@ -67,4 +67,21 @@ class Ipv4AddressTest extends BaseTestSuite { assertEquals(Ipv4Address.fromString("0.0.0.0").map(_.previous), Ipv4Address.fromString("255.255.255.255")) forAll { (ip: Ipv4Address) => assertEquals(ip.previous, Ipv4Address.fromLong(ip.toLong - 1)) } } + + test("isPrivate") { + assert(!ipv4"10.0.0.0".previous.isPrivate) + assert(ipv4"10.0.0.0".isPrivate) + assert(ipv4"10.255.255.255".isPrivate) + assert(!ipv4"10.255.255.255".next.isPrivate) + + assert(!ipv4"172.16.0.0".previous.isPrivate) + assert(ipv4"172.16.0.0".isPrivate) + assert(ipv4"172.31.255.255".isPrivate) + assert(!ipv4"172.31.255.255".next.isPrivate) + + assert(!ipv4"192.168.0.0".previous.isPrivate) + assert(ipv4"192.168.0.0".isPrivate) + assert(ipv4"192.168.255.255".isPrivate) + assert(!ipv4"192.168.255.255".next.isPrivate) + } } diff --git a/test-kit/shared/src/test/scala/com/comcast/ip4s/Ipv6AddressTest.scala b/test-kit/shared/src/test/scala/com/comcast/ip4s/Ipv6AddressTest.scala index 4cab33c..4d845f7 100644 --- a/test-kit/shared/src/test/scala/com/comcast/ip4s/Ipv6AddressTest.scala +++ b/test-kit/shared/src/test/scala/com/comcast/ip4s/Ipv6AddressTest.scala @@ -131,4 +131,13 @@ class Ipv6AddressTest extends BaseTestSuite { assertEquals[Any, Any](addr.asIpv6, Some(addr)) assertEquals[Any, Any](addr.asIpv4, Some(ip"0.15.0.15")) } + + test("isPrivate") { + assert(!ipv6"fc00::".previous.isPrivate) + assert(ipv6"fc00::".isPrivate) + assert(ipv6"fe00::".previous.isPrivate) + assert(!ipv6"fe00::".isPrivate) + // mapped v4 + assert(ipv6"::ffff:10.1.1.1".isPrivate) + } }