2020-08-08 20:23:27 +02:00
package dorkbox.netUtil
import dorkbox.netUtil.Common.logger
2020-09-08 23:02:34 +02:00
import java.net.Inet4Address
import java.net.Inet6Address
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.NetworkInterface
import java.net.SocketException
2020-08-08 20:23:27 +02:00
/ * *
* 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
init {
2020-09-09 00:30:56 +02:00
logger . trace {
" -Djava.net.preferIPv4Stack: ${IPv4.isPreferred} "
}
logger . trace {
2020-08-08 20:23:27 +02:00
" -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.
2020-09-09 00:30:56 +02:00
logger . trace {
2020-08-08 20:23:27 +02:00
" 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 ) {
2020-09-08 23:53:45 +02:00
return IPv4 . toString ( ip )
2020-08-08 20:23:27 +02:00
}
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 ) {
2020-09-08 23:02:34 +02:00
val hostname = addr . hostString
2020-08-08 20:23:27 +02:00
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 .
* /
2020-09-18 03:01:51 +02:00
fun fromString ( ip : String ) : InetAddress ? {
2020-08-08 20:23:27 +02:00
return if ( IPv4 . isValid ( ip ) ) {
2020-09-18 03:01:51 +02:00
IPv4 . fromStringUnsafe ( ip )
2020-08-08 20:23:27 +02:00
} else {
2020-09-18 03:01:51 +02:00
IPv6 . fromString ( ip )
2020-08-08 20:23:27 +02:00
}
}
}