276 lines
10 KiB
Kotlin
276 lines
10 KiB
Kotlin
package dorkbox.netUtil
|
|
|
|
import dorkbox.netUtil.Common.logger
|
|
import java.net.*
|
|
|
|
/**
|
|
* A class that holds a number of network-related constants, also from:
|
|
* (Netty, apache 2.0 license)
|
|
* https://github.com/netty/netty/blob/4.1/common/src/main/java/io/netty/util/NetUtil.java
|
|
*
|
|
* This class borrowed some of its methods from a modified fork of the
|
|
* [Inet6Util class]
|
|
* (http://svn.apache.org/repos/asf/harmony/enhanced/java/branches/java6/classlib/modules/luni/src/main/java/org/apache/harmony/luni/util/Inet6Util.java) which was part of Apache Harmony.
|
|
*/
|
|
object IP {
|
|
|
|
/**
|
|
* The [InetAddress] that represents the loopback address. If IPv6 stack is available, it will refer to
|
|
* [.LOCALHOST6]. Otherwise, [.LOCALHOST4].
|
|
*/
|
|
val LOCALHOST: InetAddress
|
|
|
|
/**
|
|
* The loopback [NetworkInterface] of the current machine
|
|
*/
|
|
val LOOPBACK_IF: NetworkInterface
|
|
|
|
/**
|
|
* The loopback address
|
|
*/
|
|
const val LOOPBACK = "127.0.0.1"
|
|
|
|
init {
|
|
logger.debug {
|
|
"-Djava.net.preferIPv4Stack: ${IPv4.isPreferred}\n" +
|
|
"-Djava.net.preferIPv6Addresses: ${IPv6.isPreferred}"
|
|
}
|
|
|
|
// Retrieve the list of available network interfaces.
|
|
val netInterfaces = mutableListOf<NetworkInterface>()
|
|
try {
|
|
val interfaces = NetworkInterface.getNetworkInterfaces()
|
|
if (interfaces != null) {
|
|
while (interfaces.hasMoreElements()) {
|
|
val iface: NetworkInterface = interfaces.nextElement()
|
|
// Use the interface with proper INET addresses only.
|
|
if (SocketUtils.addressesFromNetworkInterface(iface).hasMoreElements()) {
|
|
netInterfaces.add(iface)
|
|
}
|
|
}
|
|
}
|
|
} catch (e: SocketException) {
|
|
logger.warn("Failed to retrieve the list of available network interfaces", e)
|
|
}
|
|
|
|
|
|
// Find the first loopback interface available from its INET address (127.0.0.1 or ::1)
|
|
// Note that we do not use NetworkInterface.isLoopback() in the first place because it takes long time
|
|
// on a certain environment. (e.g. Windows with -Djava.net.preferIPv4Stack=true)
|
|
|
|
var loopbackIface: NetworkInterface? = null
|
|
var loopbackAddr: InetAddress? = null
|
|
|
|
loop@ for (iface in netInterfaces) {
|
|
val i = SocketUtils.addressesFromNetworkInterface(iface)
|
|
while (i.hasMoreElements()) {
|
|
val addr: InetAddress = i.nextElement()
|
|
if (addr.isLoopbackAddress) {
|
|
// Found
|
|
loopbackIface = iface
|
|
loopbackAddr = addr
|
|
break@loop
|
|
}
|
|
}
|
|
}
|
|
|
|
// If failed to find the loopback interface from its INET address, fall back to isLoopback().
|
|
if (loopbackIface == null) {
|
|
try {
|
|
for (iface in netInterfaces) {
|
|
if (iface.isLoopback) {
|
|
val i = SocketUtils.addressesFromNetworkInterface(iface)
|
|
if (i.hasMoreElements()) {
|
|
// Found the one with INET address.
|
|
loopbackIface = iface
|
|
loopbackAddr = i.nextElement()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if (loopbackIface == null) {
|
|
logger.warn("Failed to find the loopback interface")
|
|
}
|
|
} catch (e: SocketException) {
|
|
logger.warn("Failed to find the loopback interface", e)
|
|
}
|
|
}
|
|
|
|
if (loopbackIface != null) {
|
|
// Found the loopback interface with an INET address.
|
|
logger.debug {
|
|
"Loopback interface: ${loopbackIface.name} (${loopbackIface.displayName}, ${loopbackAddr!!.hostAddress})"
|
|
}
|
|
} else {
|
|
// Could not find the loopback interface, but we can't leave LOCALHOST as null.
|
|
// Use LOCALHOST6 or LOCALHOST4, preferably the IPv6 one.
|
|
if (loopbackAddr == null) {
|
|
try {
|
|
if (NetworkInterface.getByInetAddress(IPv6.LOCALHOST) != null) {
|
|
logger.debug {
|
|
"Using hard-coded IPv6 localhost address: ${IPv6.LOCALHOST}"
|
|
}
|
|
|
|
loopbackAddr = IPv6.LOCALHOST
|
|
}
|
|
} catch (ignore: Exception) {
|
|
|
|
}
|
|
if (loopbackAddr == null) {
|
|
logger.debug {
|
|
"Using hard-coded IPv4 localhost address: ${IPv4.LOCALHOST}"
|
|
}
|
|
loopbackAddr = IPv4.LOCALHOST
|
|
}
|
|
}
|
|
}
|
|
|
|
LOOPBACK_IF = loopbackIface!!
|
|
LOCALHOST = loopbackAddr!!
|
|
}
|
|
|
|
/**
|
|
* Creates an byte[] based on an ipAddressString. No error handling is performed here.
|
|
*/
|
|
fun toBytes(ipAddressString: String): ByteArray {
|
|
if (IPv4.isValid(ipAddressString)) {
|
|
return IPv4.toBytes(ipAddressString)
|
|
}
|
|
|
|
return IPv6.toBytes(ipAddressString)
|
|
}
|
|
|
|
/**
|
|
* Converts 4-byte or 16-byte data into an IPv4 or IPv6 string respectively.
|
|
*
|
|
* @throws IllegalArgumentException
|
|
* if `length` is not `4` nor `16`
|
|
*/
|
|
fun toString(bytes: ByteArray, offset: Int = 0, length: Int = bytes.size): String {
|
|
return when (length) {
|
|
4 -> {
|
|
StringBuilder(15)
|
|
.append(bytes[offset].toInt())
|
|
.append('.')
|
|
.append(bytes[offset + 1].toInt())
|
|
.append('.')
|
|
.append(bytes[offset + 2].toInt())
|
|
.append('.')
|
|
.append(bytes[offset + 3].toInt()).toString()
|
|
}
|
|
16 -> IPv6.toString(bytes, offset)
|
|
else -> throw IllegalArgumentException("length: $length (expected: 4 or 16)")
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the [String] representation of an [InetAddress].
|
|
*
|
|
* * Inet4Address results are identical to [InetAddress.getHostAddress]
|
|
* * Inet6Address results adhere to
|
|
* [rfc 5952 section 4](http://tools.ietf.org/html/rfc5952#section-4) if
|
|
* `ipv4Mapped` is false. If `ipv4Mapped` is true then "IPv4 mapped" format
|
|
* from [rfc 4291 section 2](http://tools.ietf.org/html/rfc4291#section-2.5.5) will be supported.
|
|
* The compressed result will always obey the compression rules defined in
|
|
* [rfc 5952 section 4](http://tools.ietf.org/html/rfc5952#section-4)
|
|
*
|
|
*
|
|
*
|
|
* The output does not include Scope ID.
|
|
*
|
|
* @param ip [InetAddress] to be converted to an address string
|
|
* @param ipv4Mapped
|
|
*
|
|
* * `true` to stray from strict rfc 5952 and support the "IPv4 mapped" format
|
|
* defined in [rfc 4291 section 2](http://tools.ietf.org/html/rfc4291#section-2.5.5) while still
|
|
* following the updated guidelines in
|
|
* [rfc 5952 section 4](http://tools.ietf.org/html/rfc5952#section-4)
|
|
*
|
|
* * `false` to strictly follow rfc 5952
|
|
*
|
|
* @return `String` containing the text-formatted IP address
|
|
*/
|
|
fun toString(ip: InetAddress, ipv4Mapped: Boolean = false): String {
|
|
if (ip is Inet4Address) {
|
|
return ip.getHostAddress()
|
|
}
|
|
|
|
require(ip is Inet6Address) { "Unhandled type: $ip" }
|
|
|
|
return IPv6.toString(ip, ipv4Mapped)
|
|
}
|
|
|
|
/**
|
|
* Returns the [String] representation of an [InetSocketAddress].
|
|
*
|
|
*
|
|
* The output does not include Scope ID.
|
|
* @param addr [InetSocketAddress] to be converted to an address string
|
|
* @return `String` containing the text-formatted IP address
|
|
*/
|
|
fun toString(addr: InetSocketAddress): String {
|
|
val port = addr.port.toString()
|
|
val sb: StringBuilder
|
|
|
|
sb = if (addr.isUnresolved) {
|
|
val hostname = getHostname(addr)
|
|
newSocketAddressStringBuilder(hostname, port, !IPv6.isValid(hostname))
|
|
} else {
|
|
val address = addr.address
|
|
val hostString = toString(address)
|
|
newSocketAddressStringBuilder(hostString, port, address is Inet4Address)
|
|
}
|
|
return sb.append(':').append(port).toString()
|
|
}
|
|
|
|
/**
|
|
* Returns the [String] representation of a host port combo.
|
|
*/
|
|
fun toString(host: String, port: Int): String {
|
|
val portStr = port.toString()
|
|
return newSocketAddressStringBuilder(host, portStr, !IPv6.isValid(host)).append(':').append(portStr).toString()
|
|
}
|
|
|
|
private fun newSocketAddressStringBuilder(host: String, port: String, ipv4: Boolean): StringBuilder {
|
|
val hostLen = host.length
|
|
if (ipv4) {
|
|
// Need to include enough space for hostString:port.
|
|
return StringBuilder(hostLen + 1 + port.length).append(host)
|
|
}
|
|
|
|
// Need to include enough space for [hostString]:port.
|
|
val stringBuilder = StringBuilder(hostLen + 3 + port.length)
|
|
return if (hostLen > 1 && host[0] == '[' && host[hostLen - 1] == ']') {
|
|
stringBuilder.append(host)
|
|
} else {
|
|
stringBuilder.append('[').append(host).append(']')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the [InetAddress] representation of a [CharSequence] IP address.
|
|
*
|
|
* This method will treat all IPv4 type addresses as "IPv4 mapped" (see [.getByName])
|
|
*
|
|
* @param ip [CharSequence] IP address to be converted to a [InetAddress]
|
|
* @return [InetAddress] representation of the `ip` or `null` if not a valid IP address.
|
|
*/
|
|
fun getByName(ip: String): InetAddress? {
|
|
return if (IPv4.isValid(ip)) {
|
|
IPv4.getByName(ip)
|
|
} else {
|
|
IPv6.getByName(ip)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns [InetSocketAddress.getHostString] if Java >= 7,
|
|
* or [InetSocketAddress.getHostName] otherwise.
|
|
* @param addr The address
|
|
* @return the host string
|
|
*/
|
|
fun getHostname(addr: InetSocketAddress): String {
|
|
return addr.hostString
|
|
}
|
|
}
|