/*
* 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<
NetworkInterface,
InetAddress>
ifaces = new
LinkedHashMap<
NetworkInterface,
InetAddress>();
try {
Enumeration<
NetworkInterface>
interfaces =
NetworkInterface.
getNetworkInterfaces();
if (
interfaces != null) {
while (
interfaces.
hasMoreElements()) {
NetworkInterface iface =
interfaces.
nextElement();
// Use the interface with proper INET addresses only.
Enumeration<
InetAddress>
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<
NetworkInterface,
InetAddress>
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() { }
}