Skip to content

Instantly share code, notes, and snippets.

@roundrop
Last active March 25, 2025 08:36
Show Gist options
  • Save roundrop/050fbf6c01c6c6c62196161c9f3250f0 to your computer and use it in GitHub Desktop.
Save roundrop/050fbf6c01c6c6c62196161c9f3250f0 to your computer and use it in GitHub Desktop.
import java.net.InetAddress
object CIDRChecker {
/**
* Checks if an IP address is within any of the specified CIDR ranges.
* This method replicates the behavior of PostgreSQL's `<<= ANY` operator for IP addresses.
*
* @param cidrs Array of IP addresses or CIDR notation strings (e.g., "192.168.1.0/24")
* @param address The IP address to check
* @return true if the address is within any of the CIDR ranges, or if the cidrs array is empty.
* For a non-empty cidrs array, returns true if the address matches any of the following:
* - Exactly matches a plain IP address in the array
* - Falls within any of the CIDR ranges specified in the array
* @note This implementation only supports IPv4 addresses. For IPv6 support, additional logic would be required.
* @example {{{
* // Check if IP is in a specific subnet
* isInRange(Array("192.168.1.0/24"), "192.168.1.100") // returns true
*
* // Check if IP matches any of multiple networks
* isInRange(Array("10.0.0.0/8", "172.16.0.0/12"), "10.1.2.3") // returns true
*
* // Check exact IP match
* isInRange(Array("192.168.1.1"), "192.168.1.1") // returns true
* }}}
*/
def isInRange(cidrs: Array[String], address: String): Boolean = {
if (cidrs.isEmpty) return true // Return TRUE for empty array (same behavior as SQL)
val targetAddress = InetAddress.getByName(address).getAddress
val targetLong = ipToLong(targetAddress)
cidrs.exists { cidr =>
if (!cidr.contains("/")) {
// Simple IP address comparison
val cidrAddress = InetAddress.getByName(cidr).getAddress
java.util.Arrays.equals(cidrAddress, targetAddress)
} else {
// Process CIDR notation
val parts = cidr.split("/")
val networkAddr = InetAddress.getByName(parts(0)).getAddress
val maskBits = parts(1).toInt
// Calculate the mask
val mask = if (maskBits == 0) 0L else ~((1L << (32 - maskBits)) - 1)
// Compare network address and target address with mask
val networkLong = ipToLong(networkAddr)
(targetLong & mask) == (networkLong & mask)
}
}
}
// Helper method to convert IPv4 address to long
private def ipToLong(ipBytes: Array[Byte]): Long = {
var result = 0L
for (i <- 0 until ipBytes.length) {
result = (result << 8) | (ipBytes(i) & 0xff)
}
result
}
}
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
class CIDRCheckerTest extends AnyFunSuite with Matchers {
// Import the method to test
import CIDRChecker.isInRange
test("Should return true for empty CIDR array") {
isInRange(Array.empty[String], "192.168.1.1") shouldBe true
}
test("Simple IP address comparison") {
isInRange(Array("192.168.1.1"), "192.168.1.1") shouldBe true
isInRange(Array("192.168.1.1"), "192.168.1.2") shouldBe false
isInRange(Array("192.168.1.1", "10.0.0.1"), "10.0.0.1") shouldBe true
isInRange(Array("192.168.1.1", "10.0.0.1"), "8.8.8.8") shouldBe false
}
test("Basic CIDR notation cases") {
isInRange(Array("192.168.1.0/24"), "192.168.1.1") shouldBe true
isInRange(Array("192.168.1.0/24"), "192.168.2.1") shouldBe false
isInRange(Array("10.0.0.0/8"), "10.1.2.3") shouldBe true
isInRange(Array("10.0.0.0/8"), "11.1.2.3") shouldBe false
}
test("CIDR address is not a network address") {
isInRange(Array("192.168.1.10/24"), "192.168.1.1") shouldBe true
isInRange(Array("192.168.1.10/24"), "192.168.1.254") shouldBe true
isInRange(Array("192.168.1.10/24"), "192.168.2.1") shouldBe false
}
test("Multiple CIDR ranges") {
isInRange(Array("192.168.1.0/24", "10.0.0.0/8"), "192.168.1.100") shouldBe true
isInRange(Array("192.168.1.0/24", "10.0.0.0/8"), "10.10.10.10") shouldBe true
isInRange(Array("192.168.1.0/24", "10.0.0.0/8"), "172.16.0.1") shouldBe false
}
test("Special CIDR masks") {
// /32 represents a single IP address
isInRange(Array("192.168.1.1/32"), "192.168.1.1") shouldBe true
isInRange(Array("192.168.1.1/32"), "192.168.1.2") shouldBe false
// /0 represents all IP addresses
isInRange(Array("0.0.0.0/0"), "192.168.1.1") shouldBe true
isInRange(Array("0.0.0.0/0"), "8.8.8.8") shouldBe true
}
test("Boundary value tests") {
isInRange(Array("192.168.1.0/24"), "192.168.1.0") shouldBe true // Network address
isInRange(Array("192.168.1.0/24"), "192.168.1.255") shouldBe true // Broadcast address
isInRange(Array("192.168.1.128/25"), "192.168.1.128") shouldBe true // Subnet start
isInRange(Array("192.168.1.128/25"), "192.168.1.255") shouldBe true // Subnet end
isInRange(Array("192.168.1.128/25"), "192.168.1.127") shouldBe false // Outside subnet
}
test("Private IP address ranges") {
// RFC1918 private address ranges
val privateRanges = Array(
"10.0.0.0/8", // Class A
"172.16.0.0/12", // Class B
"192.168.0.0/16" // Class C
)
isInRange(privateRanges, "10.0.0.1") shouldBe true
isInRange(privateRanges, "172.16.0.1") shouldBe true
isInRange(privateRanges, "192.168.1.1") shouldBe true
isInRange(privateRanges, "8.8.8.8") shouldBe false // Google's public DNS
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment