grizzled.net.inet.scala Maven / Gradle / Ivy
/*
---------------------------------------------------------------------------
This software is released under a BSD license, adapted from
http://opensource.org/licenses/bsd-license.php
Copyright © 2009-2016, Brian M. Clapper
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the names "clapper.org", "Grizzled Scala Library", nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
package grizzled.net
import java.net.InetAddress
import scala.language.implicitConversions
import scala.util.{Failure, Success, Try}
import grizzled.net.Implicits._
/** Represents an IP address. This class is similar to `java.net.InetAddress`,
* but it's designed to be more intuitive and easier to use from Scala.
* This package provides implicit converters to make this class compatible
* with `java.net.InetAddress`. The converters ensure that all the
* (non-static) methods defined in the `java.net.InetAddress` class are
* directly callable from an instance of the Scala `IPAddress` class. For
* instance, the following code is perfectly legal:
*
* {{{
* val ip = IPAddress(192, 168, 2, 5)
*
* // Test if the address is reachable within 1 second.
* println(ip + " is reachable? " + ip.isReachable(1000))
*
* // Get the canonical host name for (i.e., do a reverse lookup on) the
* // address.
* println(ip + " -> " + ip.getCanonicalHostName)
*
* // Determine whether it's the loopback address.
* println(ip + " == loopback? " + ip.isLoopbackAddress)
* }}}
*
* Here's an IPv6 example:
*
* {{{
* val ip = IPAddress("fe80::21d:9ff:fea7:53e3")
*
* // Test if the address is reachable within 1 second.
* println(ip + " is reachable? " + ip.isReachable(1000))
*
* // Get the canonical host name for (i.e., do a reverse lookup on) the
* // address.
* println(ip + " -> " + ip.getCanonicalHostName)
*
* // Determine whether it's the loopback address.
* println(ip + " == loopback? " + ip.isLoopbackAddress)
* }}}
*
* @param address the IPv4 or IPv6 address, as bytes
*/
class IPAddress(val address: Array[Byte]) {
require((address.length == 4) || (address.length == 16))
/** Convert the IP address to a number, suitable for numeric comparisons
* against other IP addresses. The number is returned as a `BigInt` to
* allow for both IPv4 and IPv6 addresses.
*
* @return the number
*/
def toNumber: BigInt = {
address.length match {
case 4 /* IPv4 */ =>
(((address(0) << 24) & 0xff000000) |
((address(1) << 16) & 0x00ff0000) |
((address(2) << 8) & 0x0000ff00) |
(address(3) & 0x000000ff)).
toLong & 0x00000000ffffffffL
case 16 /* IPv6 */ =>
val j: InetAddress = this
BigInt(j.getAddress)
}
}
/** Convert this `IPAddress` to its corresponding `java.net.InetAddress`
* object. Note that you can _also_ use the implicit conversions provided
* by `grizzled.net.Implicits`.
*
* @return the corresponding `InetAddress`
*/
def toInetAddress = InetAddress.getByAddress(this.address)
/** Return a printable version of this IP address.
*
* @return the printable version
*/
override val toString: String = {
val j: InetAddress = this
j.getHostAddress
}
/** Overloaded "==" method to test for equality.
*
* @param other object against which to test this object
*
* @return `true` if equal, `false` if not
*/
override def equals(other: Any): Boolean = {
other match {
case that: IPAddress =>
that.address.toList == this.address.toList
case _ =>
false
}
}
/** Overloaded hash method: Ensures that two `IPAddress` objects
* that represent the same IP address have the same hash code.
*
* @return the hash code
*/
override def hashCode: Int = address.toList.hashCode
}
/** Companion object to `IPAddress` class.
*/
object IPAddress {
/** Singleton `IPAddress` for the local loop address.
*/
final val Localhost = IPAddress(Array(127, 0, 0, 1))
private val MaxIPv4 = BigInt("4294967295")
// This is one butt-ugly regular expression. See
// http://stackoverflow.com/a/17871737
private val IPv6Pattern = """^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(%.*)?$""".r
private val IPv4Pattern = """^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$""".r
/** Create an `IPAddress`, given an array of bytes representing
* the address. The array must contain between 1 and 16 byte values.
*
* - If the array has fewer than four values, it is assumed to be
* an IPv4 address, and it will be padded with 0s to 4 bytes.
* - If the array has between four and 16 values, it is assumed to be
* an IPv6 address, and it will be padded with 0s to 16 bytes.
* - Anything else will cause an `IllegalArgumentException` to be thrown.
*
* @param addr the address
*
* @return the `IPAddress` in a `Success`, or `Failure(exception)` on error.
*/
def apply(addr: Array[Byte]): Try[IPAddress] = {
IPAddress(addr.toList)
}
/** Create an `IPAddress`, given an array of integers representing
* the address. The array must contain between 1 and 16 integer values.
* The integers will be truncated to 8-bit bytes.
*
* - If the array has fewer than four values, it is assumed to be
* an IPv4 address, and it will be padded with 0s to 4 bytes.
* - If the array has between four and 16 values, it is assumed to be
* an IPv6 address, and it will be padded with 0s to 16 bytes.
* - Anything else will cause an `IllegalArgumentException` to be thrown.
*
* Example of use:
*
* {{{
* val ip = IPAddress(Array(192, 168, 1, 100))
* }}}
*
* @param addr the address
*
* @return the `IPAddress` in a `Success`, or `Failure(exception)` on error.
*/
def apply(addr: Array[Int]): Try[IPAddress] = {
IPAddress(addr.map(_.toByte))
}
/** Create an `IPAddress`, given 1 to 16 integer arguments.
* The integers will be truncated to 8-bit bytes.
*
* - If the array has fewer than four values, it is assumed to be
* an IPv4 address, and it will be padded with 0s to 4 bytes.
* - If the array has between four and 16 values, it is assumed to be
* an IPv6 address, and it will be padded with 0s to 16 bytes.
* - Anything else will cause an `IllegalArgumentException` to be thrown.
*
* Example of use:
*
* {{{
* val ip = IPAddress(Array(192, 168, 1, 100))
* }}}
*
* @param addr the bytes (as integers) of the address
*
* @return the `IPAddress` in a `Success`, or `Failure(exception)` on error.
*/
def apply(addr: Int*): Try[IPAddress] = {
IPAddress(addr.map(_.toByte).toList)
}
/** Create an `IPAddress`, given a list of bytes representing the
* address
*
* - If the array has fewer than four values, it is assumed to be
* an IPv4 address, and it will be padded with 0s to 4 bytes.
* - If the array has between four and 16 values, it is assumed to be
* an IPv6 address, and it will be padded with 0s to 16 bytes.
* - Anything else will cause an `IllegalArgumentException` to be thrown.
* Example of use:
*
* {{{
* val ip = IPAddress(List(192, 168, 1, 100))
* }}}
*
* @param address the list of address values
*
* @return the `IPAddress` in a `Success`, or `Failure(exception)` on error.
*/
def apply(address: List[Byte]): Try[IPAddress] = {
val zeroByte = 0.toByte
val fullAddressRes = address.length match {
case 4 => Success(address)
case 16 => Success(address)
case 0 => Failure(new IllegalArgumentException("Empty IP address."))
case n if n > 16 =>
Failure(new IllegalArgumentException(
s"IP address ${address.mkString(".")} is too long."
))
case n =>
val upper = if (n < 4) 4 else 16
Success(address ++ n.until(upper).map(_ => zeroByte))
}
fullAddressRes.map { bytes => new IPAddress(bytes.toArray) }
}
/** Create an `IPAddress` from a `java.net.InetAddress`. You can also
* use the implicit conversions in `grizzled.net.Implicits`.
*
* @param inetAddress the `InetAddress`
*
* @return the `IPAddress`
*/
def apply(inetAddress: InetAddress): IPAddress = {
new IPAddress(inetAddress.getAddress)
}
/** Create an `IPAddress`, given a host name. Note that this function may
* do a DNS lookup if the host name is not an address string. To parse
* _just_ an address string, ignoring host names, use `parseAddress()`.
*
* @param host the host name or address string
*
* @return the `IPAddress` in a `Success`, or `Failure(exception)` on error.
*/
def apply(host: String): Try[IPAddress] = {
Try { InetAddress.getByName(host).getAddress }.flatMap(IPAddress(_))
}
/** Create an `IPAddress` from a number.
*
* @param address the numeric IP address
*
* @return the `IPAddress` in a `Success`, or `Failure(exception)` on error.
*/
def apply(address: BigInt): Try[IPAddress] = {
if (address <= MaxIPv4) {
// IPv4. Do it manually, because, sometimes, BigInt.toByteArray will
// produce a byte array with one or more leading zero bytes, which
// causes InetAddress.getByAddress() to throw an exception.
val bytes = Array(
((address >> 24) & 0xff).toByte,
((address >> 16) & 0xff).toByte,
((address >> 8) & 0xff).toByte,
(address & 0xff).toByte
)
IPAddress(bytes)
}
else {
// IPv6.
IPAddress(address.toByteArray)
}
}
/** Parse an address string (IPv4 or IPv6) only. Host names will result
* in an error return value. NOTE: This function strips any IPv6 interface
* names, if they exist. That is, an address such as
* "fe80::cab3:73ff:fe19:c600%en5" is parsed as "fe80::cab3:73ff:fe19:c600".
*
* @param addressString The address string
*
* @return the `IPAddress` in a `Success`, or `Failure(exception)` on error.
*/
def parseAddress(addressString: String): Try[IPAddress] = {
addressString match {
case IPv4Pattern(_*) => IPAddress(addressString)
case IPv6Pattern(a, _*) => IPAddress(a)
case _ => Failure(new IllegalArgumentException(
s"Invalid IP address: $addressString"
))
}
}
/** Get a list of all `IPAddress` objects for a given host
* name, based on whatever name service is configured for the running
* system.
*
* The host name can either be a machine name, such as
* "www.clapper.org", or a textual representation of its IP address. If
* a literal IP address is supplied, only the validity of the address
* format is checked.
*
* If the host is null, then this method return an `IPAddress`
* representing an address of the loopback interfaced. See RFC 3330
* section 2 and RFC 2373 section 2.5.3.
*
* This method corresponds to the `getAllByName()` method in
* the `java.net.InetAddress` class.
*
* @param hostname the host name
*
* @return a `Success` containing the list of resolved IP addresses, or
* `Failure(exception)` on error.
*/
def allForName(hostname: String): Try[List[IPAddress]] = {
val res = InetAddress.getAllByName(hostname)
.map((x: InetAddress) => IPAddress(x.getAddress))
.toList
res.filter(_.isFailure) match {
case failure :: rest => Try { List(failure.get) }
case Nil => Success(res.map(_.get))
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy