Skip to content

Commit

Permalink
Merge pull request #562 from Comcast/topic/private-addresses
Browse files Browse the repository at this point in the history
Add conveniences for testing ip addresses for known characteristics
  • Loading branch information
mpilquist authored Feb 5, 2024
2 parents f6ef862 + 01040d6 commit 3de9fe7
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 1 deletion.
5 changes: 4 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ ThisBuild / mimaBinaryIssueFilters ++= Seq(
ProblemFilters.exclude[DirectMissingMethodProblem]("com.comcast.ip4s.Multicast.ordinal"),
ProblemFilters.exclude[MissingTypesProblem]("com.comcast.ip4s.Multicast$"),
ProblemFilters.exclude[DirectMissingMethodProblem]("com.comcast.ip4s.SourceSpecificMulticast.ordinal"),
ProblemFilters.exclude[MissingTypesProblem]("com.comcast.ip4s.SourceSpecificMulticast$")
ProblemFilters.exclude[MissingTypesProblem]("com.comcast.ip4s.SourceSpecificMulticast$"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("com.comcast.ip4s.IpAddress.isPrivate"), // #562
ProblemFilters.exclude[ReversedMissingMethodProblem]("com.comcast.ip4s.IpAddress.isLoopback"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("com.comcast.ip4s.IpAddress.isLinkLocal")
)

lazy val root = tlCrossRootProject.aggregate(core, testKit)
Expand Down
78 changes: 78 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,15 @@ 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 a loopback address. */
def isLoopback: Boolean

/** Returns true if this address is a link local address. */
def isLinkLocal: Boolean

/** 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 +365,17 @@ final class Ipv4Address private (protected val bytes: Array[Byte]) extends IpAdd
override def isSourceSpecificMulticast: Boolean =
this >= Ipv4Address.SourceSpecificMulticastRangeStart && this <= Ipv4Address.SourceSpecificMulticastRangeEnd

override def isLoopback: Boolean =
Ipv4Address.Classes.Loopback.contains(this)

override def isLinkLocal: Boolean =
Ipv4Address.Classes.LinkLocal.contains(this)

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 +438,44 @@ 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)
}

/** Loopback: 127.0.0.0 - 127.255.255.255. */
val Loopback: Cidr[Ipv4Address] = Cidr(fromBytes(127, 0, 0, 0), 8)

/** Link local: 169.254.0.0 - 169.254.255.255. */
val LinkLocal: Cidr[Ipv4Address] = Cidr(fromBytes(169, 254, 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 +667,15 @@ final class Ipv6Address private (protected val bytes: Array[Byte]) extends IpAdd
override def isSourceSpecificMulticast: Boolean =
this >= Ipv6Address.SourceSpecificMulticastRangeStart && this <= Ipv6Address.SourceSpecificMulticastRangeEnd

override def isLoopback: Boolean =
this == Ipv6Address.Loopback || (isMappedV4 && collapseMappedV4.isLoopback)

override def isLinkLocal: Boolean =
Ipv6Address.LinkLocalBlock.contains(this) || (isMappedV4 && collapseMappedV4.isLinkLocal)

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 +720,17 @@ 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)

/** Alias for ::1. */
val Loopback: Ipv6Address = Ipv6Address.fromBytes(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)

/** CIDR which defines the 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)

/** CIDR which defines the linked scope unicast address block. */
val LinkLocalBlock: Cidr[Ipv6Address] =
Cidr(Ipv6Address.fromBytes(0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 10)

/** 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,34 @@ 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)
}

test("isLoopback") {
assert(ipv4"127.0.0.1".isLoopback)
assert(ipv4"127.255.255.255".isLoopback)
assert(!ipv4"128.0.0.0".isLoopback)
}

test("isLinkLocal") {
assert(!ipv4"127.0.0.1".isLinkLocal)
assert(ipv4"169.254.0.0".isLinkLocal)
assert(ipv4"169.254.255.255".isLinkLocal)
assert(!ipv4"169.254.255.255".next.isLinkLocal)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,23 @@ 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)
}

test("isLoopback") {
assert(ipv6"::1".isLoopback)
assert(ipv6"::ffff:127.0.0.1".isLoopback)
}

test("isLinkLocal") {
assert(ipv6"fe80::1".isLinkLocal)
assert(ipv6"::ffff:169.254.0.0".isLinkLocal)
}
}

0 comments on commit 3de9fe7

Please sign in to comment.