879 lines
28 KiB
Kotlin
879 lines
28 KiB
Kotlin
/*
|
|
* Copyright 2020 Dorkbox, llc
|
|
* Copyright 2012 The Netty Project
|
|
*
|
|
* The Netty Project licenses this file to you under the Apache License,
|
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at:
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
package dorkbox.netUtil
|
|
|
|
import java.io.IOException
|
|
import java.io.Writer
|
|
import java.net.*
|
|
import kotlin.math.floor
|
|
import kotlin.math.ln
|
|
import kotlin.math.pow
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
@Suppress("EXPERIMENTAL_API_USAGE")
|
|
object IPv4 {
|
|
/**
|
|
* Gets the version number.
|
|
*/
|
|
const val version = "2.9.1"
|
|
|
|
/**
|
|
* Returns `true` if IPv4 should be used even if the system supports both IPv4 and IPv6. Setting this
|
|
* property to `true` will disable IPv6 support. The default value of this property is `false`.
|
|
*
|
|
* @see [Java SE networking properties](https://docs.oracle.com/javase/8/docs/api/java/net/doc-files/net-properties.html)
|
|
*/
|
|
val isPreferred = Common.getBoolean("java.net.preferIPv4Stack", false)
|
|
|
|
/**
|
|
* true if there is an IPv4 network interface available
|
|
*/
|
|
val isAvailable: Boolean by lazy {
|
|
// 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) {
|
|
Common.logger.warn("Failed to retrieve the list of available network interfaces", e)
|
|
}
|
|
|
|
// check if IPv4 is possible.
|
|
var ipv4Possible = false
|
|
loop@ for (iface in netInterfaces) {
|
|
val i = SocketUtils.addressesFromNetworkInterface(iface)
|
|
while (i.hasMoreElements()) {
|
|
val addr: InetAddress = i.nextElement()
|
|
if (addr is Inet4Address) {
|
|
ipv4Possible = true
|
|
break@loop
|
|
}
|
|
}
|
|
}
|
|
|
|
ipv4Possible
|
|
}
|
|
|
|
|
|
/**
|
|
* The [Inet4Address] that represents the IPv4 loopback address '127.0.0.1'
|
|
*/
|
|
val LOCALHOST: Inet4Address by lazy {
|
|
// Create IPv4 address, this will ALWAYS work
|
|
InetAddress.getByAddress("localhost", byteArrayOf(127, 0, 0, 1)) as Inet4Address
|
|
}
|
|
|
|
/**
|
|
* The [Inet4Address] that represents the IPv4 wildcard address '0.0.0.0'
|
|
*/
|
|
val WILDCARD: Inet4Address by lazy {
|
|
// Create IPv4 address, this will ALWAYS work
|
|
InetAddress.getByAddress(null, byteArrayOf(0, 0, 0, 0)) as Inet4Address
|
|
}
|
|
|
|
/**
|
|
* the length of an address in this particular family.
|
|
*/
|
|
val length = 4
|
|
|
|
private val SLASH_REGEX = "\\.".toRegex()
|
|
|
|
private val private10 = toInt("10.0.0.0")
|
|
private val private172 = toInt("172.16.0.0")
|
|
private val private192 = toInt("192.168.0.0")
|
|
private val loopback127 = toInt("127.0.0.0")
|
|
|
|
/**
|
|
* @return if the specified address is of this family type
|
|
*/
|
|
fun isFamily(address: InetAddress) : Boolean {
|
|
return address is Inet4Address
|
|
}
|
|
|
|
/**
|
|
* Determines if this IP address is a private address or not.
|
|
*
|
|
* Private addresses are defined as
|
|
* 10.0.0.0/8
|
|
* 172.16.0.0/12
|
|
* 192.168.0.0/16
|
|
*/
|
|
fun isSiteLocal(ipAsString: String): Boolean {
|
|
return isSiteLocal(toInt(ipAsString))
|
|
}
|
|
|
|
/**
|
|
* Determines if this IP address is a private address or not.
|
|
*
|
|
* Private addresses are defined as
|
|
* 10.0.0.0/8
|
|
* 172.16.0.0/12
|
|
* 192.168.0.0/16
|
|
*/
|
|
fun isSiteLocal(ipAsInt: Int): Boolean {
|
|
// check from smaller to larger
|
|
return isInRange(ipAsInt, private192, 16) || isInRange(ipAsInt, private172, 12) || isInRange(ipAsInt, private10, 8)
|
|
}
|
|
|
|
/**
|
|
* A loopback address is defined as
|
|
* 127.0.0.0/8
|
|
*/
|
|
fun isLoopback(ipAsString: String): Boolean {
|
|
return isInRange(toInt(ipAsString), loopback127, 8)
|
|
}
|
|
|
|
/**
|
|
* A loopback address is defined as
|
|
* 127.0.0.0/8
|
|
*/
|
|
fun isLoopback(ipAsInt: Int): Boolean {
|
|
return isInRange(ipAsInt, loopback127, 8)
|
|
}
|
|
|
|
/**
|
|
* Determine whether a given string is a valid CIDR IP address. Accepts only 1.2.3.4/24
|
|
*
|
|
* @param ipAsString The string that will be checked.
|
|
*
|
|
* @return return true if the string is a valid IP address, false if it is not.
|
|
*/
|
|
fun isValidCidr(ipAsString: String): Boolean {
|
|
if (ipAsString.isEmpty()) {
|
|
return false
|
|
}
|
|
|
|
val slashIndex = ipAsString.indexOf('/')
|
|
if (slashIndex < 6) {
|
|
// something is malformed.
|
|
return false
|
|
}
|
|
|
|
val ipOnly = ipAsString.substring(0, slashIndex)
|
|
if (!isValid(ipOnly)) {
|
|
return false
|
|
}
|
|
|
|
try {
|
|
val cidr = ipAsString.substring(slashIndex + 1).toInt()
|
|
if (cidr in 0..32) {
|
|
return true
|
|
}
|
|
} catch (ignored: Exception) {
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* Takes a [String] and parses it to see if it is a valid IPV4 address.
|
|
*
|
|
* @return true, if the string represents an IPV4 address in dotted notation, false otherwise
|
|
*/
|
|
fun isValid(ip: String): Boolean {
|
|
return isValidIpV4Address(ip, 0, ip.length)
|
|
}
|
|
|
|
internal fun isValidIpV4Address(ip: String, from: Int, toExcluded: Int): Boolean {
|
|
val len = toExcluded - from
|
|
if (len !in 7..15) {
|
|
return false
|
|
}
|
|
|
|
@Suppress("NAME_SHADOWING")
|
|
var from = from
|
|
var i = ip.indexOf('.', from)
|
|
|
|
if (i <= 0 || !isValidIpV4Word(ip, from, i)) {
|
|
return false
|
|
}
|
|
|
|
from = i + 1
|
|
i = ip.indexOf('.', from)
|
|
if (i <= 0 || !isValidIpV4Word(ip, from, i)) {
|
|
return false
|
|
}
|
|
|
|
from = i + 1
|
|
i = ip.indexOf('.', from)
|
|
if (i <= 0 || !isValidIpV4Word(ip, from, i)) {
|
|
return false
|
|
}
|
|
|
|
if (i <= 0 || !isValidIpV4Word(ip, i + 1, toExcluded)) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
private fun isValidIpV4Word(word: CharSequence, from: Int, toExclusive: Int): Boolean {
|
|
val len = toExclusive - from
|
|
var c0 = ' '
|
|
var c1: Char
|
|
var c2 = ' '
|
|
|
|
if (len < 1 || len > 3 || word[from].also { c0 = it } < '0') {
|
|
return false
|
|
}
|
|
return if (len == 3) {
|
|
word[from + 1].also { c1 = it } >= '0'
|
|
&& word[from + 2].also { c2 = it } >= '0'
|
|
&& (c0 <= '1' && c1 <= '9' && c2 <= '9' || c0 == '2' && c1 <= '5' && (c2 <= '5' || c1 < '5' && c2 <= '9'))
|
|
}
|
|
else c0 <= '9' && (len == 1 || isValidNumericChar(word[from + 1]))
|
|
}
|
|
|
|
internal fun isValidNumericChar(c: Char): Boolean {
|
|
return c in '0'..'9'
|
|
}
|
|
|
|
internal fun isValidIPv4MappedChar(c: Char): Boolean {
|
|
return c == 'f' || c == 'F'
|
|
}
|
|
|
|
|
|
fun toBytesOrNull(ip: String): ByteArray? {
|
|
if (!isValid(ip)) {
|
|
return null
|
|
}
|
|
|
|
var i: Int
|
|
return byteArrayOf(ipv4WordToByte(ip, 0, ip.indexOf('.', 1).also { i = it }),
|
|
ipv4WordToByte(ip, i + 1, ip.indexOf('.', i + 2).also { i = it }),
|
|
ipv4WordToByte(ip, i + 1, ip.indexOf('.', i + 2).also { i = it }),
|
|
ipv4WordToByte(ip, i + 1, ip.length))
|
|
}
|
|
|
|
/**
|
|
* Creates an byte[] based on an ip Address String. No error handling is performed here.
|
|
*/
|
|
fun toBytes(ip: String): ByteArray {
|
|
var i: Int
|
|
|
|
var index = ip.indexOf('.', 1)
|
|
val a = ipv4WordToByte(ip, 0, index)
|
|
i = index
|
|
|
|
index = ip.indexOf('.', index + 2)
|
|
val b = ipv4WordToByte(ip, i + 1, index)
|
|
i = index
|
|
|
|
index = ip.indexOf('.', index + 2)
|
|
val c = ipv4WordToByte(ip, i + 1, index)
|
|
i = index
|
|
|
|
return byteArrayOf(a, b, c, ipv4WordToByte(ip, i + 1, ip.length))
|
|
}
|
|
|
|
/**
|
|
* Creates an int[] based on an ip address String. No error handling is performed here.
|
|
*/
|
|
fun toInts(ip: String): IntArray {
|
|
var i: Int
|
|
|
|
var index = ip.indexOf('.', 1)
|
|
val a = ipv4WordToInt(ip, 0, index)
|
|
i = index
|
|
|
|
index = ip.indexOf('.', index + 2)
|
|
val b = ipv4WordToInt(ip, i + 1, index)
|
|
i = index
|
|
|
|
index = ip.indexOf('.', index + 2)
|
|
val c = ipv4WordToInt(ip, i + 1, index)
|
|
i = index
|
|
|
|
return intArrayOf(a, b, c, ipv4WordToInt(ip, i + 1, ip.length))
|
|
}
|
|
|
|
private fun decimalDigit(str: CharSequence, pos: Int): Int {
|
|
return str[pos] - '0'
|
|
}
|
|
|
|
private fun ipv4WordToByte(ip: CharSequence, from: Int, toExclusive: Int): Byte {
|
|
return ipv4WordToInt(ip, from, toExclusive).toByte()
|
|
}
|
|
|
|
private fun ipv4WordToInt(ip: CharSequence, from: Int, toExclusive: Int): Int {
|
|
var newFrom = from
|
|
|
|
var ret = decimalDigit(ip, newFrom)
|
|
newFrom++
|
|
|
|
if (newFrom == toExclusive) {
|
|
return ret
|
|
}
|
|
|
|
ret = ret * 10 + decimalDigit(ip, newFrom)
|
|
newFrom++
|
|
|
|
return if (newFrom == toExclusive) {
|
|
ret
|
|
} else {
|
|
ret * 10 + decimalDigit(ip, newFrom)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds the first available subnet (as CIDR) with a corresponding gateway
|
|
*/
|
|
fun findFreeSubnet24Cidr(): ByteArray? {
|
|
// have to find a free cidr
|
|
// start with 10.x.x.x /24 and just march through starting at 0 -> 200 for each, ping to see if there is a gateway (should be)
|
|
// and go from there.
|
|
|
|
|
|
// on linux, PING has the setuid bit set - so it runs "as root". isReachable() requires either java to have the setuid bit set
|
|
// (ie: sudo setcap cap_net_raw=ep /usr/lib/jvm/jdk/bin/java) or it requires to be run as root. We run as root in production, so it
|
|
// works.
|
|
val ip = byteArrayOf(10, 0, 0, 0)
|
|
var subnet24Counter = 0
|
|
|
|
while (true) {
|
|
ip[3]++
|
|
|
|
if (ip[3] > 255) {
|
|
ip[3] = 1
|
|
ip[2]++
|
|
subnet24Counter = 0
|
|
}
|
|
if (ip[2] > 255) {
|
|
ip[2] = 0
|
|
ip[1]++
|
|
}
|
|
if (ip[1] > 255) {
|
|
Common.logger.error("Exhausted all ip searches. FATAL ERROR.")
|
|
return null
|
|
}
|
|
|
|
try {
|
|
val address = InetAddress.getByAddress(ip)
|
|
val reachable = address.isReachable(100)
|
|
|
|
if (!reachable) {
|
|
subnet24Counter++
|
|
}
|
|
if (subnet24Counter == 250) {
|
|
// this means that we tried all /24 IPs, and ALL of them came back an "non-responsive". 100ms timeout is OK, because
|
|
// we are on a LAN, that should have MORE than one IP in the cidr, and it should be fairly responsive (ie: <10ms ping)
|
|
|
|
// we found an empty cidr
|
|
ip[3] = 1
|
|
return ip
|
|
}
|
|
} catch (e: IOException) {
|
|
e.printStackTrace()
|
|
return null
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scans for existing IP addresses on the network.
|
|
*
|
|
* @param startingIp the IP address to start scanning at
|
|
* @param numberOfHosts the number of hosts to scan for. A /28 is 14 hosts : 2^(32-28) - 2 = 14
|
|
*
|
|
* @return true if no hosts were reachable (pingable)
|
|
*/
|
|
fun scanHosts(startingIp: String, numberOfHosts: Int): Boolean {
|
|
Common.logger.info("Scanning {} hosts, starting at IP {}.", numberOfHosts, startingIp)
|
|
|
|
|
|
val split = startingIp.split(SLASH_REGEX).toTypedArray()
|
|
val a = split[0].toByte()
|
|
val b = split[1].toByte()
|
|
val c = split[2].toByte()
|
|
val d = split[3].toByte()
|
|
|
|
val ip = byteArrayOf(a, b, c, d)
|
|
var counter = numberOfHosts
|
|
|
|
while (counter >= 0) {
|
|
counter--
|
|
|
|
ip[3]++
|
|
if (ip[3] > 255) {
|
|
ip[3] = 1
|
|
ip[2]++
|
|
}
|
|
if (ip[2] > 255) {
|
|
ip[2] = 0
|
|
ip[1]++
|
|
}
|
|
if (ip[1] > 255) {
|
|
Common.logger.error("Exhausted all ip searches. FATAL ERROR.")
|
|
return false
|
|
}
|
|
|
|
try {
|
|
val address = InetAddress.getByAddress(ip)
|
|
val reachable = address.isReachable(100)
|
|
if (reachable) {
|
|
Common.logger.error("IP address {} is already reachable on the network. Unable to continue.", address.hostAddress)
|
|
return false
|
|
}
|
|
} catch (e: IOException) {
|
|
Common.logger.error("Error pinging the IP address", e)
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* @param cidr the CIDR notation, ie: 24, 16, etc. That we want to convert into a netmask, as a string
|
|
*
|
|
* @return the netmask or if there were errors, the default /0 netmask
|
|
*/
|
|
fun getCidrAsNetmask(cidr: Int): String {
|
|
return when (cidr) {
|
|
32 -> "255.255.255.255"
|
|
31 -> "255.255.255.254"
|
|
30 -> "255.255.255.252"
|
|
29 -> "255.255.255.248"
|
|
28 -> "255.255.255.240"
|
|
27 -> "255.255.255.224"
|
|
26 -> "255.255.255.192"
|
|
25 -> "255.255.255.128"
|
|
24 -> "255.255.255.0"
|
|
23 -> "255.255.254.0"
|
|
22 -> "255.255.252.0"
|
|
21 -> "255.255.248.0"
|
|
20 -> "255.255.240.0"
|
|
19 -> "255.255.224.0"
|
|
18 -> "255.255.192.0"
|
|
17 -> "255.255.128.0"
|
|
16 -> "255.255.0.0"
|
|
15 -> "255.254.0.0"
|
|
14 -> "255.252.0.0"
|
|
13 -> "255.248.0.0"
|
|
12 -> "255.240.0.0"
|
|
11 -> "255.224.0.0"
|
|
10 -> "255.192.0.0"
|
|
9 -> "255.128.0.0"
|
|
8 -> "255.0.0.0"
|
|
7 -> "254.0.0.0"
|
|
6 -> "252.0.0.0"
|
|
5 -> "248.0.0.0"
|
|
4 -> "240.0.0.0"
|
|
3 -> "224.0.0.0"
|
|
2 -> "192.0.0.0"
|
|
1 -> "128.0.0.0"
|
|
else -> "0.0.0.0"
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param cidrPrefix the CIDR notation, ie: /24, /16, etc that we want to convert into a netmask
|
|
*
|
|
* @return the netmask (as a signed int), or if there were errors, the default /0 netmask
|
|
*/
|
|
fun cidrPrefixToSubnetMask(cidrPrefix: Int): Int {
|
|
/**
|
|
* Perform the shift on a long and downcast it to int afterwards.
|
|
* This is necessary to handle a cidrPrefix of zero correctly.
|
|
* The left shift operator on an int only uses the five least
|
|
* significant bits of the right-hand operand. Thus -1 << 32 evaluates
|
|
* to -1 instead of 0. The left shift operator applied on a long
|
|
* uses the six least significant bits.
|
|
*
|
|
* Also see https://github.com/netty/netty/issues/2767
|
|
*/
|
|
return (-1L shl 32 - cidrPrefix and -0x1).toInt()
|
|
}
|
|
|
|
fun cidrPrefixFromSubnetMask(mask: String): Int {
|
|
return when (mask) {
|
|
"255.255.255.255" -> 32
|
|
"255.255.255.254" -> 31
|
|
"255.255.255.252" -> 30
|
|
"255.255.255.248" -> 29
|
|
"255.255.255.240" -> 28
|
|
"255.255.255.224" -> 27
|
|
"255.255.255.192" -> 26
|
|
"255.255.255.128" -> 25
|
|
"255.255.255.0" -> 24
|
|
"255.255.254.0" -> 23
|
|
"255.255.252.0" -> 22
|
|
"255.255.248.0" -> 21
|
|
"255.255.240.0" -> 20
|
|
"255.255.224.0" -> 19
|
|
"255.255.192.0" -> 18
|
|
"255.255.128.0" -> 17
|
|
"255.255.0.0" -> 16
|
|
"255.254.0.0" -> 15
|
|
"255.252.0.0" -> 14
|
|
"255.248.0.0" -> 13
|
|
"255.240.0.0" -> 12
|
|
"255.224.0.0" -> 11
|
|
"255.192.0.0" -> 10
|
|
"255.128.0.0" -> 9
|
|
"255.0.0.0" -> 8
|
|
"254.0.0.0" -> 7
|
|
"252.0.0.0" -> 6
|
|
"248.0.0.0" -> 5
|
|
"240.0.0.0" -> 4
|
|
"224.0.0.0" -> 3
|
|
"192.0.0.0" -> 2
|
|
"128.0.0.0" -> 1
|
|
else -> 0
|
|
}
|
|
}
|
|
|
|
private val CIDR2MASK = intArrayOf(0x00000000,
|
|
-0x80000000,
|
|
-0x40000000,
|
|
-0x20000000,
|
|
-0x10000000,
|
|
-0x8000000,
|
|
-0x4000000,
|
|
-0x2000000,
|
|
-0x1000000,
|
|
-0x800000,
|
|
-0x400000,
|
|
-0x200000,
|
|
-0x100000,
|
|
-0x80000,
|
|
-0x40000,
|
|
-0x20000,
|
|
-0x10000,
|
|
-0x8000,
|
|
-0x4000,
|
|
-0x2000,
|
|
-0x1000,
|
|
-0x800,
|
|
-0x400,
|
|
-0x200,
|
|
-0x100,
|
|
-0x80,
|
|
-0x40,
|
|
-0x20,
|
|
-0x10,
|
|
-0x8,
|
|
-0x4,
|
|
-0x2,
|
|
-0x1)
|
|
|
|
fun range2Cidr(startIp: String, endIp: String): List<String> {
|
|
var start = toInt(startIp).toLong()
|
|
val end = toInt(endIp).toLong()
|
|
|
|
val pairs: MutableList<String> = ArrayList()
|
|
|
|
while (end >= start) {
|
|
var maxsize = 32.toByte()
|
|
while (maxsize > 0) {
|
|
val mask = CIDR2MASK[maxsize - 1].toLong()
|
|
val maskedBase = start and mask
|
|
if (maskedBase != start) {
|
|
break
|
|
}
|
|
maxsize--
|
|
}
|
|
|
|
val x = ln(end - start + 1.toDouble()) / ln(2.0)
|
|
val maxDiff = (32 - floor(x)).toInt().toByte()
|
|
if (maxsize < maxDiff) {
|
|
maxsize = maxDiff
|
|
}
|
|
|
|
val ip = toString(start)
|
|
pairs.add("$ip/$maxsize")
|
|
start += 2.0.pow(32 - maxsize.toDouble()).toLong()
|
|
}
|
|
return pairs
|
|
}
|
|
|
|
/**
|
|
* Check if the IP address is in the range of a specific IP/CIDR
|
|
*
|
|
* a prefix of 0 will ALWAYS return true
|
|
*
|
|
* @param address the address to check
|
|
* @param networkAddress the network address that will have the other address checked against
|
|
* @param networkPrefix 0-32 the network prefix (subnet) to use for the network address
|
|
*
|
|
* @return true if it is in range
|
|
*/
|
|
fun isInRange(address: String, networkAddress: String, networkPrefix: Int): Boolean {
|
|
return isInRange(toInt(address), toInt(networkAddress), networkPrefix)
|
|
}
|
|
|
|
/**
|
|
* Check if the IP address is in the range of a specific IP/CIDR
|
|
*
|
|
* a prefix of 0 will ALWAYS return true
|
|
*
|
|
* @param address the address to check
|
|
* @param networkAddress the network address that will have the other address checked against
|
|
* @param networkPrefix 0-32 the network prefix (subnet) to use for the network address
|
|
*
|
|
* @return true if it is in range
|
|
*/
|
|
fun isInRange(address: Int, networkAddress: Int, networkPrefix: Int): Boolean {
|
|
// System.err.println(" ip: " + IP.toString(address));
|
|
// System.err.println(" networkAddress: " + IP.toString(networkAddress));
|
|
// System.err.println(" networkSubnetPrefix: " + networkPrefix);
|
|
if (networkPrefix == 0) {
|
|
// a prefix of 0 means it is always true (even though the broadcast address is '-1'). So we short-cut it here
|
|
return true
|
|
}
|
|
val netmask = ((1 shl 32 - networkPrefix) - 1).inv()
|
|
|
|
// Calculate base network address
|
|
val network = (networkAddress and netmask).toUInt()
|
|
// System.err.println(" network " + IPv4.toString(network.toInt()))
|
|
|
|
// Calculate broadcast address
|
|
val broadcast = network or netmask.inv().toUInt()
|
|
// System.err.println(" broadcast " + IPv4.toString(broadcast.toInt()))
|
|
|
|
// val addressLong = (address.toLong() and UNSIGNED_INT_MASK)
|
|
return address.toUInt() in network..broadcast
|
|
}
|
|
|
|
|
|
/**
|
|
* Converts a byte array into a 32-bit integer
|
|
*/
|
|
fun toInt(ipBytes: ByteArray): Int {
|
|
return ipBytes[0].toInt() shl 24 or
|
|
(ipBytes[1].toInt() shl 16) or
|
|
(ipBytes[2].toInt() shl 8) or
|
|
(ipBytes[3].toInt())
|
|
}
|
|
|
|
/**
|
|
* Converts an IP address into a 32-bit integer
|
|
*/
|
|
fun toInt(ipAsString: String): Int {
|
|
return if (isValid(ipAsString)) {
|
|
toIntUnsafe(ipAsString)
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts an IP address into a 32-bit integer, no validity checks are performed
|
|
*/
|
|
fun toIntUnsafe(ipAsString: String): Int {
|
|
val bytes = toBytes(ipAsString)
|
|
|
|
var address = 0
|
|
for (element in bytes) {
|
|
address = address shl 8
|
|
address = address or (element.toInt() and 0xff)
|
|
}
|
|
|
|
return address
|
|
}
|
|
|
|
/**
|
|
* Converts a 32-bit integer into a dotted-quad IPv4 address.
|
|
*/
|
|
fun toString(ipAddress: Int): String {
|
|
val buf = StringBuilder(15)
|
|
toString(ipAddress, buf)
|
|
return buf.toString()
|
|
}
|
|
|
|
/**
|
|
* Converts a 32-bit integer into a dotted-quad IPv4 address.
|
|
*/
|
|
fun toString(ipAddress: Int, buf: StringBuilder) {
|
|
buf.append(ipAddress shr 24 and 0xFF)
|
|
buf.append('.')
|
|
buf.append(ipAddress shr 16 and 0xFF)
|
|
buf.append('.')
|
|
buf.append(ipAddress shr 8 and 0xFF)
|
|
buf.append('.')
|
|
buf.append(ipAddress and 0xFF)
|
|
}
|
|
|
|
/**
|
|
* Converts a byte array into a dotted-quad IPv4 address.
|
|
*/
|
|
fun toString(ipBytes: ByteArray): String {
|
|
val buf = StringBuilder(15)
|
|
toString(ipBytes, buf)
|
|
return buf.toString()
|
|
}
|
|
|
|
/**
|
|
* Converts a byte array into a dotted-quad IPv4 address.
|
|
*/
|
|
fun toString(ipBytes: ByteArray, buf: StringBuilder) {
|
|
buf.append(ipBytes[0].toInt() and 0xFF)
|
|
buf.append('.')
|
|
buf.append(ipBytes[1].toInt() and 0xFF)
|
|
buf.append('.')
|
|
buf.append(ipBytes[2].toInt() and 0xFF)
|
|
buf.append('.')
|
|
buf.append(ipBytes[3].toInt() and 0xFF)
|
|
}
|
|
|
|
/**
|
|
* Converts an int array into a dotted-quad IPv4 address.
|
|
*/
|
|
fun toString(ipInts: IntArray): String {
|
|
val buf = StringBuilder(15)
|
|
toString(ipInts, buf)
|
|
return buf.toString()
|
|
}
|
|
|
|
/**
|
|
* Converts an int array into a dotted-quad IPv4 address.
|
|
*/
|
|
fun toString(ipInts: IntArray, buf: StringBuilder) {
|
|
buf.append(ipInts[0])
|
|
buf.append('.')
|
|
buf.append(ipInts[1])
|
|
buf.append('.')
|
|
buf.append(ipInts[2])
|
|
buf.append('.')
|
|
buf.append(ipInts[3])
|
|
}
|
|
|
|
/**
|
|
* Returns the [String] representation of an [InetAddress]. Results are identical to [InetAddress.getHostAddress]
|
|
*
|
|
* @param ip [InetAddress] to be converted to an address string
|
|
*
|
|
* @return `String` containing the text-formatted IP address
|
|
*/
|
|
fun toString(ip: Inet4Address): String {
|
|
return ip.hostAddress
|
|
}
|
|
|
|
|
|
@Throws(Exception::class)
|
|
fun writeString(ipAddress: Int, writer: Writer) {
|
|
writer.write((ipAddress shr 24 and 0xFF).toString())
|
|
writer.write('.'.toInt())
|
|
writer.write((ipAddress shr 16 and 0xFF).toString())
|
|
writer.write('.'.toInt())
|
|
writer.write((ipAddress shr 8 and 0xFF).toString())
|
|
writer.write('.'.toInt())
|
|
writer.write((ipAddress and 0xFF).toString())
|
|
}
|
|
|
|
fun toString(ipAddress: Long): String {
|
|
val ipString = StringBuilder(15)
|
|
ipString.append(ipAddress shr 24 and 0xFF)
|
|
ipString.append('.')
|
|
ipString.append(ipAddress shr 16 and 0xFF)
|
|
ipString.append('.')
|
|
ipString.append(ipAddress shr 8 and 0xFF)
|
|
ipString.append('.')
|
|
ipString.append(ipAddress and 0xFF)
|
|
return ipString.toString()
|
|
}
|
|
|
|
fun toBytes(bytes: Int): ByteArray {
|
|
return byteArrayOf((bytes shr 24 and 0xFF).toByte(),
|
|
(bytes shr 16 and 0xFF).toByte(),
|
|
(bytes shr 8 and 0xFF).toByte(),
|
|
(bytes and 0xFF).toByte())
|
|
}
|
|
|
|
/**
|
|
* Returns the [Inet4Address] representation of a [String] IP address.
|
|
*
|
|
* This method will treat all IPv4 type addresses as "IPv4 mapped" (see [.getByName])
|
|
*
|
|
* @param ip [String] IP address to be converted to a [Inet4Address]
|
|
* @return [Inet4Address] representation of the `ip` or an exception if not a valid IP address.
|
|
*/
|
|
@Throws(UnknownHostException::class)
|
|
fun toAddressUnsafe(ip: String): Inet4Address {
|
|
val asBytes = toBytes(ip)
|
|
return Inet4Address.getByAddress(null, asBytes) as Inet4Address
|
|
}
|
|
|
|
/**
|
|
* Returns the [Inet4Address] representation of a [String] IP address.
|
|
*
|
|
* This method will treat all IPv4 type addresses as "IPv4 mapped" (see [.getByName])
|
|
*
|
|
* @param ip [String] IP address to be converted to a [Inet4Address]
|
|
* @return [Inet4Address] representation of the `ip` or `null` if not a valid IP address.
|
|
*/
|
|
fun toAddress(ip: String): Inet4Address? {
|
|
return if (isValid(ip)) {
|
|
return toAddressUnsafe(ip)
|
|
} else {
|
|
null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Truncates an address to the specified number of bits. For example,
|
|
* truncating the address 10.1.2.3 to 8 bits would yield 10.0.0.0.
|
|
*
|
|
* @param address The source address
|
|
* @param maskLength The number of bits to truncate the address to.
|
|
*/
|
|
fun truncate(address: Inet4Address, maskLength: Int): InetAddress? {
|
|
val maxMaskLength = IPv6.length * 8
|
|
|
|
require(!(maskLength < 0 || maskLength > maxMaskLength)) { "invalid mask length" }
|
|
|
|
if (maskLength == maxMaskLength) {
|
|
return address
|
|
}
|
|
|
|
val bytes = address.address
|
|
for (i in maskLength / 8 + 1 until bytes.size) {
|
|
bytes[i] = 0
|
|
}
|
|
|
|
val maskBits = maskLength % 8
|
|
var bitmask = 0
|
|
for (i in 0 until maskBits) {
|
|
bitmask = bitmask or (1 shl 7 - i)
|
|
}
|
|
|
|
bytes[maskLength / 8] = (bytes[maskLength / 8].toInt() and bitmask).toByte()
|
|
|
|
return try {
|
|
InetAddress.getByAddress(bytes)
|
|
} catch (e: UnknownHostException) {
|
|
throw IllegalArgumentException("invalid address")
|
|
}
|
|
}
|
|
}
|