Last active
June 10, 2022 10:46
-
-
Save Stanback/1d96985ebfd61e9e66ef55cd46b114fc to your computer and use it in GitHub Desktop.
Scala example for converting an IPv4 or IPv6 address to a base 10 decimal/long/BigInt w/ netmask range calculation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.net.{Inet4Address, Inet6Address, InetAddress} | |
import scala.util.Try | |
sealed trait IPAddressException { | |
self: Throwable => val message: String | |
} | |
case class IPAddressParseException(message: String) extends Exception(message) with IPAddressException | |
trait IPAddress { | |
val ip: String | |
val netmask: Int | |
def asBase10: String | |
def asRange: (String, String) | |
} | |
case class IPV4Address( | |
ip: String, | |
netmask: Int, | |
bytes: Array[Byte], | |
base10: Long, | |
range: (Long, Long) | |
) extends IPAddress { | |
def asBase10: String = base10.toString | |
def asRange: (String, String) = (range._1.toString -> range._2.toString) | |
} | |
object IPV4Address { | |
val MAX_SIZE = 32 | |
def bytes2Long(bytes: Array[Byte]): Long = bytes.foldLeft(0L)((acc, b) => (acc << 8) + (b & 0xff)) | |
def long2Bytes(base10: Long): Array[Byte] = (for (i <- 0 to 3) yield (base10 >> (i * 8)).toByte).reverse.toArray | |
def getRange(base10: Long, netmask: Int): (Long, Long) = { | |
val mask = 0xffffffff | |
val nm = if (netmask == 0) 0 else (mask << (MAX_SIZE - netmask)) | |
val min = base10 & mask | |
val max = base10 | (~nm & mask) | |
(min -> max) | |
} | |
def apply(ia: Inet4Address, netmask: Option[Int]): IPV4Address = { | |
val bytes = ia.getAddress | |
val base10 = bytes2Long(bytes) | |
val mask = netmask.getOrElse(MAX_SIZE) | |
new IPV4Address( | |
ip = ia.getHostAddress, | |
netmask = mask, | |
bytes = bytes, | |
base10 = base10, | |
range = getRange(base10, mask) | |
) | |
} | |
@throws(classOf[IPAddressParseException]) | |
def apply(base10: Long, netmask: Option[Int]): IPV4Address = { | |
val bytes = long2Bytes(base10) | |
Try(InetAddress.getByAddress(bytes)).toOption match { | |
case Some(ia: Inet4Address) => apply(ia, netmask) | |
case _ => throw IPAddressParseException(s"Unable to convert base10 to Inet4Address: $base10") | |
} | |
} | |
} | |
case class IPV6Address( | |
ip: String, | |
netmask: Int, | |
bytes: Array[Byte], | |
base10: BigInt, | |
range: (BigInt, BigInt) | |
) extends IPAddress { | |
def asBase10: String = base10.toString | |
def asRange: (String, String) = (range._1.toString -> range._2.toString) | |
} | |
object IPV6Address { | |
val MAX_SIZE = 128 | |
def bytes2BigInt(bytes: Array[Byte]): BigInt = BigInt(1, bytes) | |
def bigInt2Bytes(base10: BigInt): Array[Byte] = { | |
val bytes = base10.toByteArray | |
if (bytes(0) == 0) bytes.slice(1, bytes.length) else bytes | |
} | |
def getRange(base10: BigInt, netmask: Int): (BigInt, BigInt) = { | |
val mask = BigInt("00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16) | |
val nm = mask << (MAX_SIZE - netmask) | |
val min = base10 & nm | |
val max = base10 | (~nm & mask) | |
(min -> max) | |
} | |
def apply(ia: Inet6Address, netmask: Option[Int]): IPV6Address = { | |
val bytes = ia.getAddress | |
val base10 = bytes2BigInt(bytes) | |
val mask = netmask.getOrElse(MAX_SIZE) | |
new IPV6Address( | |
ip = ia.getHostAddress, | |
netmask = mask, | |
bytes = bytes, | |
base10 = base10, | |
range = getRange(base10, mask) | |
) | |
} | |
@throws(classOf[IPAddressParseException]) | |
def apply(base10: BigInt, netmask: Option[Int]): IPV6Address = { | |
val bytes = bigInt2Bytes(base10) | |
Try(InetAddress.getByAddress(bytes)).toOption match { | |
case Some(ia: Inet6Address) => apply(ia, netmask) | |
case _ => throw IPAddressParseException(s"Unable to convert base10 to Inet6Address: $base10") | |
} | |
} | |
} | |
object IPAddress { | |
@throws(classOf[IPAddressParseException]) | |
def apply(ip: String, netmask: Option[Int] = None): IPAddress = { | |
Try(InetAddress.getByName(ip)).toOption match { | |
case Some(ia: Inet4Address) => IPV4Address(ia, netmask) | |
case Some(ia: Inet6Address) => IPV6Address(ia, netmask) | |
case _ => throw IPAddressParseException(s"Unknown IP format: $ip") | |
} | |
} | |
} | |
/* | |
* Example usage: | |
*/ | |
val myIPV4 = IPAddress("127.0.0.1") | |
println(s"IPv4 ${myIPV4.ip} to base 10 is: ${myIPV4.asBase10}") | |
val myIPV6 = IPAddress("2001:4860:4001:803::1011") | |
println(s"IPv6 ${myIPV6.ip} to base 10 is: ${myIPV6.asBase10}") | |
val myIPV4Network = IPAddress("192.168.1.1", Some(24)) | |
println(s"Decimal range of ${myIPV4Network.ip}/${myIPV4Network.netmask} is: ${myIPV4Network.asRange._1} through ${myIPV4Network.asRange._2}") | |
val myIPV6Network = IPAddress("fd86:ae29:179b:9ef5::", Some(64)).asInstanceOf[IPV6Address] | |
val ip6Start = IPV6Address(myIPV6Network.range._1, None) | |
val ip6End = IPV6Address(myIPV6Network.range._2, None) | |
println(s"Range of ${myIPV6Network.ip}/${myIPV6Network.netmask} spans from ${ip6Start.ip} to ${ip6End.ip}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example output: