Skip to content

Commit

Permalink
Add IpAddress.isPrivate, Ipv4Address.Classes.{A-E}, Ipv4Address.Class…
Browse files Browse the repository at this point in the history
…es.Private.{A-C}, and Ipv6Address.UniqueLocalBlock
  • Loading branch information
mpilquist committed Feb 5, 2024
1 parent f6ef862 commit 4a99fde
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 0 deletions.
47 changes: 47 additions & 0 deletions shared/src/main/scala/com/comcast/ip4s/Host.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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] = {
Expand Down Expand Up @@ -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 {{{
Expand Down Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

0 comments on commit 4a99fde

Please sign in to comment.