io.netty.util.internal.MacAddressUtil Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2016 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 io.netty.util.internal;
import io.netty.util.NetUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import static io.netty.util.internal.EmptyArrays.EMPTY_BYTES;
public final class MacAddressUtil {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(MacAddressUtil.class);
private static final int EUI64_MAC_ADDRESS_LENGTH = 8;
private static final int EUI48_MAC_ADDRESS_LENGTH = 6;
/**
* Obtains the best MAC address found on local network interfaces.
* Generally speaking, an active network interface used on public
* networks is better than a local network interface.
*
* @return byte array containing a MAC. null if no MAC can be found.
*/
public static byte[] bestAvailableMac() {
// Find the best MAC address available.
byte[] bestMacAddr = EMPTY_BYTES;
InetAddress bestInetAddr = NetUtil.LOCALHOST4;
// Retrieve the list of available network interfaces.
Map ifaces = new LinkedHashMap();
try {
Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
if (interfaces != null) {
while (interfaces.hasMoreElements()) {
NetworkInterface iface = interfaces.nextElement();
// Use the interface with proper INET addresses only.
Enumeration addrs = SocketUtils.addressesFromNetworkInterface(iface);
if (addrs.hasMoreElements()) {
InetAddress a = addrs.nextElement();
if (!a.isLoopbackAddress()) {
ifaces.put(iface, a);
}
}
}
}
} catch (SocketException e) {
logger.warn("Failed to retrieve the list of available network interfaces", e);
}
for (Entry entry: ifaces.entrySet()) {
NetworkInterface iface = entry.getKey();
InetAddress inetAddr = entry.getValue();
if (iface.isVirtual()) {
continue;
}
byte[] macAddr;
try {
macAddr = SocketUtils.hardwareAddressFromNetworkInterface(iface);
} catch (SocketException e) {
logger.debug("Failed to get the hardware address of a network interface: {}", iface, e);
continue;
}
boolean replace = false;
int res = compareAddresses(bestMacAddr, macAddr);
if (res < 0) {
// Found a better MAC address.
replace = true;
} else if (res == 0) {
// Two MAC addresses are of pretty much same quality.
res = compareAddresses(bestInetAddr, inetAddr);
if (res < 0) {
// Found a MAC address with better INET address.
replace = true;
} else if (res == 0) {
// Cannot tell the difference. Choose the longer one.
if (bestMacAddr.length < macAddr.length) {
replace = true;
}
}
}
if (replace) {
bestMacAddr = macAddr;
bestInetAddr = inetAddr;
}
}
if (bestMacAddr == EMPTY_BYTES) {
return null;
}
switch (bestMacAddr.length) {
case EUI48_MAC_ADDRESS_LENGTH: // EUI-48 - convert to EUI-64
byte[] newAddr = new byte[EUI64_MAC_ADDRESS_LENGTH];
System.arraycopy(bestMacAddr, 0, newAddr, 0, 3);
newAddr[3] = (byte) 0xFF;
newAddr[4] = (byte) 0xFE;
System.arraycopy(bestMacAddr, 3, newAddr, 5, 3);
bestMacAddr = newAddr;
break;
default: // Unknown
bestMacAddr = Arrays.copyOf(bestMacAddr, EUI64_MAC_ADDRESS_LENGTH);
}
return bestMacAddr;
}
/**
* Returns the result of {@link #bestAvailableMac()} if non-{@code null} otherwise returns a random EUI-64 MAC
* address.
*/
public static byte[] defaultMachineId() {
byte[] bestMacAddr = bestAvailableMac();
if (bestMacAddr == null) {
bestMacAddr = new byte[EUI64_MAC_ADDRESS_LENGTH];
PlatformDependent.threadLocalRandom().nextBytes(bestMacAddr);
logger.warn(
"Failed to find a usable hardware address from the network interfaces; using random bytes: {}",
formatAddress(bestMacAddr));
}
return bestMacAddr;
}
/**
* Parse a EUI-48, MAC-48, or EUI-64 MAC address from a {@link String} and return it as a {@code byte[]}.
* @param value The string representation of the MAC address.
* @return The byte representation of the MAC address.
*/
public static byte[] parseMAC(String value) {
final byte[] machineId;
final char separator;
switch (value.length()) {
case 17:
separator = value.charAt(2);
validateMacSeparator(separator);
machineId = new byte[EUI48_MAC_ADDRESS_LENGTH];
break;
case 23:
separator = value.charAt(2);
validateMacSeparator(separator);
machineId = new byte[EUI64_MAC_ADDRESS_LENGTH];
break;
default:
throw new IllegalArgumentException("value is not supported [MAC-48, EUI-48, EUI-64]");
}
final int end = machineId.length - 1;
int j = 0;
for (int i = 0; i < end; ++i, j += 3) {
final int sIndex = j + 2;
machineId[i] = StringUtil.decodeHexByte(value, j);
if (value.charAt(sIndex) != separator) {
throw new IllegalArgumentException("expected separator '" + separator + " but got '" +
value.charAt(sIndex) + "' at index: " + sIndex);
}
}
machineId[end] = StringUtil.decodeHexByte(value, j);
return machineId;
}
private static void validateMacSeparator(char separator) {
if (separator != ':' && separator != '-') {
throw new IllegalArgumentException("unsupported separator: " + separator + " (expected: [:-])");
}
}
/**
* @param addr byte array of a MAC address.
* @return hex formatted MAC address.
*/
public static String formatAddress(byte[] addr) {
StringBuilder buf = new StringBuilder(24);
for (byte b: addr) {
buf.append(String.format("%02x:", b & 0xff));
}
return buf.substring(0, buf.length() - 1);
}
/**
* @return positive - current is better, 0 - cannot tell from MAC addr, negative - candidate is better.
*/
// visible for testing
static int compareAddresses(byte[] current, byte[] candidate) {
if (candidate == null || candidate.length < EUI48_MAC_ADDRESS_LENGTH) {
return 1;
}
// Must not be filled with only 0 and 1.
boolean onlyZeroAndOne = true;
for (byte b: candidate) {
if (b != 0 && b != 1) {
onlyZeroAndOne = false;
break;
}
}
if (onlyZeroAndOne) {
return 1;
}
// Must not be a multicast address
if ((candidate[0] & 1) != 0) {
return 1;
}
// Prefer globally unique address.
if ((candidate[0] & 2) == 0) {
if (current.length != 0 && (current[0] & 2) == 0) {
// Both current and candidate are globally unique addresses.
return 0;
} else {
// Only candidate is globally unique.
return -1;
}
} else {
if (current.length != 0 && (current[0] & 2) == 0) {
// Only current is globally unique.
return 1;
} else {
// Both current and candidate are non-unique.
return 0;
}
}
}
/**
* @return positive - current is better, 0 - cannot tell, negative - candidate is better
*/
private static int compareAddresses(InetAddress current, InetAddress candidate) {
return scoreAddress(current) - scoreAddress(candidate);
}
private static int scoreAddress(InetAddress addr) {
if (addr.isAnyLocalAddress() || addr.isLoopbackAddress()) {
return 0;
}
if (addr.isMulticastAddress()) {
return 1;
}
if (addr.isLinkLocalAddress()) {
return 2;
}
if (addr.isSiteLocalAddress()) {
return 3;
}
return 4;
}
private MacAddressUtil() { }
}