Expose Helper to obtain the "best" mac address.
Motivation: The implementation of obtaining the best possible mac address is very good. There are many sub-par implementations proposed on stackoverflow. While not strictly a netty concern, it would be nice to offer this util also to netty users. Modifications: extract DefaultChannelId#defaultMachineId code obtaining the "best" mac into a new helper called MacAddress, keep the random bytes fallback in DefaultChannelID. Result: New helper available.
This commit is contained in:
parent
d9f938ca03
commit
a51e2c8769
221
common/src/main/java/io/netty/util/internal/MacAddressUtil.java
Normal file
221
common/src/main/java/io/netty/util/internal/MacAddressUtil.java
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* 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 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 io.netty.util.NetUtil;
|
||||||
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
|
public final class MacAddressUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Length of a valid MAC address.
|
||||||
|
*/
|
||||||
|
public static final int MAC_ADDRESS_LENGTH = 8;
|
||||||
|
|
||||||
|
private static final byte[] NOT_FOUND = { -1 };
|
||||||
|
|
||||||
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(MacAddressUtil.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = NOT_FOUND;
|
||||||
|
InetAddress bestInetAddr = NetUtil.LOCALHOST4;
|
||||||
|
|
||||||
|
// Retrieve the list of available network interfaces.
|
||||||
|
Map<NetworkInterface, InetAddress> ifaces = new LinkedHashMap<NetworkInterface, InetAddress>();
|
||||||
|
try {
|
||||||
|
for (Enumeration<NetworkInterface> i = NetworkInterface.getNetworkInterfaces(); i.hasMoreElements();) {
|
||||||
|
NetworkInterface iface = i.nextElement();
|
||||||
|
// Use the interface with proper INET addresses only.
|
||||||
|
Enumeration<InetAddress> addrs = iface.getInetAddresses();
|
||||||
|
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<NetworkInterface, InetAddress> entry: ifaces.entrySet()) {
|
||||||
|
NetworkInterface iface = entry.getKey();
|
||||||
|
InetAddress inetAddr = entry.getValue();
|
||||||
|
if (iface.isVirtual()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] macAddr;
|
||||||
|
try {
|
||||||
|
macAddr = iface.getHardwareAddress();
|
||||||
|
} 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 == NOT_FOUND) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (bestMacAddr.length) {
|
||||||
|
case 6: // EUI-48 - convert to EUI-64
|
||||||
|
byte[] newAddr = new byte[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, MAC_ADDRESS_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestMacAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
private static int compareAddresses(byte[] current, byte[] candidate) {
|
||||||
|
if (candidate == null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be EUI-48 or longer.
|
||||||
|
if (candidate.length < 6) {
|
||||||
|
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 ((current[0] & 2) == 0) {
|
||||||
|
if ((candidate[0] & 2) == 0) {
|
||||||
|
// Both current and candidate are globally unique addresses.
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
// Only current is globally unique.
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ((candidate[0] & 2) == 0) {
|
||||||
|
// Only candidate 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() { }
|
||||||
|
}
|
@ -17,6 +17,7 @@
|
|||||||
package io.netty.channel;
|
package io.netty.channel;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBufUtil;
|
import io.netty.buffer.ByteBufUtil;
|
||||||
|
import io.netty.util.internal.MacAddressUtil;
|
||||||
import io.netty.util.internal.EmptyArrays;
|
import io.netty.util.internal.EmptyArrays;
|
||||||
import io.netty.util.internal.PlatformDependent;
|
import io.netty.util.internal.PlatformDependent;
|
||||||
import io.netty.util.internal.SystemPropertyUtil;
|
import io.netty.util.internal.SystemPropertyUtil;
|
||||||
@ -25,15 +26,7 @@ import io.netty.util.internal.logging.InternalLogger;
|
|||||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.NetworkInterface;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -47,7 +40,7 @@ final class DefaultChannelId implements ChannelId {
|
|||||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelId.class);
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelId.class);
|
||||||
|
|
||||||
private static final Pattern MACHINE_ID_PATTERN = Pattern.compile("^(?:[0-9a-fA-F][:-]?){6,8}$");
|
private static final Pattern MACHINE_ID_PATTERN = Pattern.compile("^(?:[0-9a-fA-F][:-]?){6,8}$");
|
||||||
private static final int MACHINE_ID_LEN = 8;
|
private static final int MACHINE_ID_LEN = MacAddressUtil.MAC_ADDRESS_LENGTH;
|
||||||
private static final byte[] MACHINE_ID;
|
private static final byte[] MACHINE_ID;
|
||||||
private static final int PROCESS_ID_LEN = 4;
|
private static final int PROCESS_ID_LEN = 4;
|
||||||
// Maximal value for 64bit systems is 2^22. See man 5 proc.
|
// Maximal value for 64bit systems is 2^22. See man 5 proc.
|
||||||
@ -107,7 +100,7 @@ final class DefaultChannelId implements ChannelId {
|
|||||||
if (machineId == null) {
|
if (machineId == null) {
|
||||||
machineId = defaultMachineId();
|
machineId = defaultMachineId();
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("-Dio.netty.machineId: {} (auto-detected)", formatAddress(machineId));
|
logger.debug("-Dio.netty.machineId: {} (auto-detected)", MacAddressUtil.formatAddress(machineId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,182 +121,17 @@ final class DefaultChannelId implements ChannelId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] defaultMachineId() {
|
private static byte[] defaultMachineId() {
|
||||||
// Find the best MAC address available.
|
byte[] bestMacAddr = MacAddressUtil.bestAvailableMac();
|
||||||
final byte[] NOT_FOUND = { -1 };
|
if (bestMacAddr == null) {
|
||||||
byte[] bestMacAddr = NOT_FOUND;
|
bestMacAddr = new byte[MacAddressUtil.MAC_ADDRESS_LENGTH];
|
||||||
InetAddress bestInetAddr = null;
|
|
||||||
try {
|
|
||||||
bestInetAddr = InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 });
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
// Never happens.
|
|
||||||
PlatformDependent.throwException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the list of available network interfaces.
|
|
||||||
Map<NetworkInterface, InetAddress> ifaces = new LinkedHashMap<NetworkInterface, InetAddress>();
|
|
||||||
try {
|
|
||||||
for (Enumeration<NetworkInterface> i = NetworkInterface.getNetworkInterfaces(); i.hasMoreElements();) {
|
|
||||||
NetworkInterface iface = i.nextElement();
|
|
||||||
// Use the interface with proper INET addresses only.
|
|
||||||
Enumeration<InetAddress> addrs = iface.getInetAddresses();
|
|
||||||
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<NetworkInterface, InetAddress> entry: ifaces.entrySet()) {
|
|
||||||
NetworkInterface iface = entry.getKey();
|
|
||||||
InetAddress inetAddr = entry.getValue();
|
|
||||||
if (iface.isVirtual()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] macAddr;
|
|
||||||
try {
|
|
||||||
macAddr = iface.getHardwareAddress();
|
|
||||||
} 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 == NOT_FOUND) {
|
|
||||||
bestMacAddr = new byte[MACHINE_ID_LEN];
|
|
||||||
ThreadLocalRandom.current().nextBytes(bestMacAddr);
|
ThreadLocalRandom.current().nextBytes(bestMacAddr);
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"Failed to find a usable hardware address from the network interfaces; using random bytes: {}",
|
"Failed to find a usable hardware address from the network interfaces; using random bytes: {}",
|
||||||
formatAddress(bestMacAddr));
|
MacAddressUtil.formatAddress(bestMacAddr));
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (bestMacAddr.length) {
|
|
||||||
case 6: // EUI-48 - convert to EUI-64
|
|
||||||
byte[] newAddr = new byte[MACHINE_ID_LEN];
|
|
||||||
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, MACHINE_ID_LEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
return bestMacAddr;
|
return bestMacAddr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return positive - current is better, 0 - cannot tell from MAC addr, negative - candidate is better.
|
|
||||||
*/
|
|
||||||
private static int compareAddresses(byte[] current, byte[] candidate) {
|
|
||||||
if (candidate == null) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must be EUI-48 or longer.
|
|
||||||
if (candidate.length < 6) {
|
|
||||||
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 ((current[0] & 2) == 0) {
|
|
||||||
if ((candidate[0] & 2) == 0) {
|
|
||||||
// Both current and candidate are globally unique addresses.
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
// Only current is globally unique.
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ((candidate[0] & 2) == 0) {
|
|
||||||
// Only candidate 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 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int defaultProcessId() {
|
private static int defaultProcessId() {
|
||||||
final ClassLoader loader = PlatformDependent.getSystemClassLoader();
|
final ClassLoader loader = PlatformDependent.getSystemClassLoader();
|
||||||
String value;
|
String value;
|
||||||
|
Loading…
Reference in New Issue
Block a user