NetworkUtils/src/dorkbox/netUtil/Mac.kt

470 lines
14 KiB
Kotlin

@file:Suppress("unused")
package dorkbox.netUtil
import dorkbox.executor.Executor
import org.slf4j.Logger
import java.io.File
import java.io.Writer
import java.math.BigInteger
import java.net.InetAddress
import java.net.NetworkInterface
import java.util.*
import java.util.regex.Pattern
/**
*
*/
object Mac {
/**
* Gets the version number.
*/
const val version = "2.9.1"
enum class MacDelimiter(val delimiter: String) {
COLON(":"),
PERIOD("."),
SPACE(" ");
val regex = delimiter.toRegex()
}
private const val MAC_ADDRESS_LENGTH = 6
private val random = Random()
private val fakeMacsInUse: MutableSet<String> = HashSet()
private val MAC_ADDRESS_PATTERN by lazy {
Pattern.compile("^([a-fA-F0-9]{2}[:\\\\.-]?){5}[a-fA-F0-9]{2}$")
}
private val COLON_REGEX = ":".toRegex()
init {
synchronized(fakeMacsInUse) {}
}
fun getMacAddress(ip: String, logger: Logger? = null): String {
try {
val mac = getMacAddressByte(ip, logger)
if (mac == null) {
logger?.error("Unable to get MAC address for IP '{}'", ip)
return ""
}
val s = StringBuilder(18)
for (b in mac) {
if (s.isNotEmpty()) {
s.append(':')
}
s.append(String.format("%02x", b))
}
return s.toString()
} catch (e: Exception) {
if (logger != null) {
logger.error("Unable to get MAC address for IP '{}'", ip, e)
}
else {
e.printStackTrace()
}
}
logger?.error("Unable to get MAC address for IP '{}'", ip)
return ""
}
fun getMacAddressByte(ip: String, logger: Logger? = null): ByteArray? {
try {
val addr = InetAddress.getByName(ip)
val networkInterface = NetworkInterface.getByInetAddress(addr)
if (networkInterface == null) {
logger?.error("Unable to get MAC address for IP '{}'", ip)
return null
}
val mac = networkInterface.hardwareAddress
if (mac == null) {
logger?.error("Unable to get MAC address for IP '{}'", ip)
return null
}
return mac
} catch (e: Exception) {
if (logger != null) {
logger.error("Unable to get MAC address for IP '{}'", ip, e)
}
else {
e.printStackTrace()
}
}
return null
}
/**
* Removes a mac that was in use, to free it's use later on
*/
fun freeFakeMac(fakeMac: String) {
synchronized(fakeMacsInUse) { fakeMacsInUse.remove(fakeMac) }
}
/**
* Removes a mac that was in use, to free it's use later on
*/
fun freeFakeMac(vararg fakeMacs: String) {
synchronized(fakeMacsInUse) {
fakeMacs.forEach {
fakeMacsInUse.remove(it)
}
}
}
/**
* Removes a mac that was in use, to free it's use later on
*/
fun freeFakeMac(fakeMacs: Iterable<String>) {
synchronized(fakeMacsInUse) {
fakeMacs.forEach {
fakeMacsInUse.remove(it)
}
}
}
/**
* will also make sure that this mac doesn't already exist
*
* @return a unique MAC that can be used for VPN devices + setup. This is a LOWERCASE string!
*/
fun fakeMac(): String {
synchronized(fakeMacsInUse) {
var mac = fakeVpnMacUnsafe()
// gotta make sure it doesn't already exist
while (fakeMacsInUse.contains(mac)) {
mac = fakeVpnMacUnsafe()
}
fakeMacsInUse.add(mac)
return mac
}
}
/**
* will also make sure that this mac doesn't already exist
*
* @return a unique MAC that can be used for VPN devices + setup. This is a LOWERCASE string!
*/
fun fakeDockerMac(): String {
synchronized(fakeMacsInUse) {
var mac = fakeDockerMacUnsafe()
// gotta make sure it doesn't already exist
while (fakeMacsInUse.contains(mac)) {
mac = fakeDockerMacUnsafe()
}
fakeMacsInUse.add(mac)
return mac
}
}
/**
* http://serverfault.com/questions/40712/what-range-of-mac-addresses-can-i-safely-use-for-my-virtual-machines
*
* @return a mac that is safe to use for fake interfaces. THIS IS LOWERCASE!
*/
fun fakeVpnMac(): String {
var mac = fakeVpnMacUnsafe()
synchronized(fakeMacsInUse) {
// gotta make sure it doesn't already exist
while (fakeMacsInUse.contains(mac)) {
mac = fakeVpnMacUnsafe()
}
fakeMacsInUse.add(mac)
}
return mac
}
/**
* http://serverfault.com/questions/40712/what-range-of-mac-addresses-can-i-safely-use-for-my-virtual-machines
*
* @return a mac that is safe to use for fake interfaces. THIS IS LOWERCASE!
*/
fun fakeVpnMacUnsafe(): String {
var vpnID = randHex
while (vpnID == "d0") {
// this is what is used for docker. NEVER assign it to a VPN mac!!
vpnID = randHex
}
return "02:$vpnID:$randHex:$randHex:$randHex:$randHex"
}
/**
* http://serverfault.com/questions/40712/what-range-of-mac-addresses-can-i-safely-use-for-my-virtual-machines
*
* @return a mac that is safe to use for fake interfaces. THIS IS LOWERCASE!
*/
fun fakeDockerMacUnsafe(): String {
return "02:d0:$randHex:$randHex:$randHex:$randHex"
}
/**
* will also make sure that this mac doesn't already exist
*
* @return a unique MAC that can be used for VPN devices + setup
*/
fun fakeVpnMacWithDir(vpnKeyDirForExistingVpnMacs: String): String {
synchronized(fakeMacsInUse) {
var mac = fakeVpnMacUnsafe()
// gotta make sure it doesn't already exist
while (fakeMacsInUse.contains(mac) || File(vpnKeyDirForExistingVpnMacs, "$mac.crt").exists()) {
mac = fakeVpnMacUnsafe()
}
fakeMacsInUse.add(mac)
return mac
}
}
private val randHex: String
get() {
val i = random.nextInt(255)
return if (i < 16) {
"0" + Integer.toHexString(i)
}
else {
Integer.toHexString(i)
}
}
/**
* Converts a long into a properly formatted lower-case string
*/
fun toStringLowerCase(mac: Long): String {
val macBytes = toBytes(mac)
// mac should have AT LEAST bytes.
val buf = StringBuilder()
// we only use the right-most 6 bytes (of 8 bytes).
(2..7).forEach { index ->
val byte = macBytes[index]
if (buf.isNotEmpty()) {
buf.append(':')
}
if (byte in 0..15) {
buf.append('0')
}
@Suppress("EXPERIMENTAL_API_USAGE")
buf.append(Integer.toHexString(byte.toUByte().toInt()))
}
return buf.toString()
}
@Throws(Exception::class)
fun writeStringLowerCase(mac: Long, writer: Writer) {
// we only use the right-most 6 bytes.
val macBytes = toBytes(mac)
// we only use the right-most 6 bytes (of 8 bytes).
var bytesWritten = 0
(2..7).forEach { index ->
val byte = macBytes[index]
if (bytesWritten != 0) {
writer.write(":")
bytesWritten++
}
if (byte in 0..15) {
writer.append('0')
}
writer.write(Integer.toHexString(byte.toInt() and 0xFF))
bytesWritten += 2
}
}
private fun toBytes(x: Long): ByteArray {
return byteArrayOf((x shr 56).toByte(),
(x shr 48).toByte(),
(x shr 40).toByte(),
(x shr 32).toByte(),
(x shr 24).toByte(),
(x shr 16).toByte(),
(x shr 8).toByte(),
(x shr 0).toByte())
}
fun toStringLowerCase(mac: ByteArray): String {
// mac should have 6 bytes.
val buf = StringBuilder()
for (b in mac) {
if (buf.isNotEmpty()) {
buf.append(':')
}
if (b in 0..15) {
buf.append('0')
}
buf.append(Integer.toHexString(if (b < 0) b + 256 else b.toInt()))
}
return buf.toString()
}
fun toStringUpperCase(mac: ByteArray): String {
val buf = StringBuilder()
for (b in mac) {
if (buf.isNotEmpty()) {
buf.append(':')
}
if (b in 0..15) {
buf.append('0')
}
buf.append(Integer.toHexString(b.toInt() and 0xFF)
.toUpperCase())
}
return buf.toString()
}
fun toBytes(mac: String): ByteArray {
val s = mac.replace(COLON_REGEX, "")
return BigInteger(s, 16).toByteArray()
}
fun toLongSafe(macAsString: String): Long {
return toLong(macAsString, getMacDelimiter(macAsString))
}
fun toLong(mac: String): Long {
return toLong(mac, MacDelimiter.COLON)
}
fun toLong(mac: ByteArray): Long {
return ((mac[5].toLong() and 0xff) +
(mac[4].toLong() and 0xff shl 8) +
(mac[3].toLong() and 0xff shl 16) +
(mac[2].toLong() and 0xff shl 24) +
(mac[1].toLong() and 0xff shl 32) +
(mac[0].toLong() and 0xff shl 40))
}
fun toLong(mac: String, delimiter: MacDelimiter? = MacDelimiter.COLON): Long {
val addressInBytes = ByteArray(MAC_ADDRESS_LENGTH)
try {
val elements: Array<String?>
if (delimiter != null) {
elements = mac.split(delimiter.regex).toTypedArray()
}
else {
elements = arrayOfNulls(MAC_ADDRESS_LENGTH)
var index = 0
var substringPos = 0
while (index < MAC_ADDRESS_LENGTH) {
elements[index] = mac.substring(substringPos, substringPos + 2)
index++
substringPos += 2
}
}
for (i in 0 until MAC_ADDRESS_LENGTH) {
val element = elements[i]
addressInBytes[i] = element!!.toInt(16).toByte()
}
} catch (e: Exception) {
Common.logger.error("Error parsing MAC address '{}'", mac, e)
}
return toLong(addressInBytes)
}
fun isValid(macAsString: String): Boolean {
if (macAsString.isEmpty()) {
return false
}
// Check whether mac is separated by a colon, period, space, or nothing at all.
// Must standardize to a colon.
var normalizedMac: String = macAsString
if (normalizedMac.split(COLON_REGEX)
.toTypedArray().size != 6) {
// Does not already use colons, must modify the mac to use colons.
when {
normalizedMac.contains(".") -> {
// Period notation
normalizedMac = normalizedMac.replace(".", ":")
}
normalizedMac.contains(" ") -> {
// Space notation
normalizedMac = normalizedMac.replace(" ", ":")
}
else -> {
// No delimiter, manually add colons.
normalizedMac = ""
var index = 0
var substringPos = 0
// We ensure that the substring function will not exceed the length of the given string if the string is not at least
//(MAC_ADDRESS_LENGTH * 2) characters long.
while (index < MAC_ADDRESS_LENGTH && macAsString.length >= substringPos + 2) {
// Reconstruct the string, adding colons.
normalizedMac = if (index != MAC_ADDRESS_LENGTH - 1) {
normalizedMac + macAsString.substring(substringPos, substringPos + 2) + ":"
}
else {
normalizedMac + macAsString.substring(substringPos, substringPos + 2)
}
index++
substringPos += 2
}
}
}
}
val matcher = MAC_ADDRESS_PATTERN.matcher(normalizedMac)
return matcher.matches()
}
/**
* Returns the type of delmiter based on how a mac address is constructed. Returns ":" if no delimiter could be detected.
*
* @return a MacDelimiter if there is one, or null if the mac address is one long string.
*/
fun getMacDelimiter(macAsString: String): MacDelimiter? {
return when {
macAsString.contains(":") -> {
MacDelimiter.COLON
}
macAsString.contains(".") -> {
MacDelimiter.PERIOD
}
macAsString.contains(" ") -> {
MacDelimiter.SPACE
}
else -> MacDelimiter.COLON
}
}
fun assign(interfaceName: String, macAddress: String) {
if (Common.OS_LINUX) {
Executor.run("/sbin/ifconfig", interfaceName, "hw", "ether", macAddress)
}
else {
throw RuntimeException("NOT IMPL.")
}
}
}