1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.netty.util.internal;
18
19 import io.netty.util.NetUtil;
20 import io.netty.util.internal.logging.InternalLogger;
21 import io.netty.util.internal.logging.InternalLoggerFactory;
22
23 import java.net.InetAddress;
24 import java.net.NetworkInterface;
25 import java.net.SocketException;
26 import java.util.Arrays;
27 import java.util.Enumeration;
28 import java.util.LinkedHashMap;
29 import java.util.Map;
30 import java.util.Map.Entry;
31 import java.util.concurrent.ThreadLocalRandom;
32
33 import static io.netty.util.internal.EmptyArrays.EMPTY_BYTES;
34
35 public final class MacAddressUtil {
36 private static final InternalLogger logger = InternalLoggerFactory.getInstance(MacAddressUtil.class);
37
38 private static final int EUI64_MAC_ADDRESS_LENGTH = 8;
39 private static final int EUI48_MAC_ADDRESS_LENGTH = 6;
40
41
42
43
44
45
46
47
48 public static byte[] bestAvailableMac() {
49
50 byte[] bestMacAddr = EMPTY_BYTES;
51 InetAddress bestInetAddr = NetUtil.LOCALHOST4;
52
53
54 Map<NetworkInterface, InetAddress> ifaces = new LinkedHashMap<NetworkInterface, InetAddress>();
55 for (NetworkInterface iface: NetUtil.NETWORK_INTERFACES) {
56
57 Enumeration<InetAddress> addrs = SocketUtils.addressesFromNetworkInterface(iface);
58 if (addrs.hasMoreElements()) {
59 InetAddress a = addrs.nextElement();
60 if (!a.isLoopbackAddress()) {
61 ifaces.put(iface, a);
62 }
63 }
64 }
65
66 for (Entry<NetworkInterface, InetAddress> entry: ifaces.entrySet()) {
67 NetworkInterface iface = entry.getKey();
68 InetAddress inetAddr = entry.getValue();
69 if (iface.isVirtual()) {
70 continue;
71 }
72
73 byte[] macAddr;
74 try {
75 macAddr = SocketUtils.hardwareAddressFromNetworkInterface(iface);
76 } catch (SocketException e) {
77 logger.debug("Failed to get the hardware address of a network interface: {}", iface, e);
78 continue;
79 }
80
81 boolean replace = false;
82 int res = compareAddresses(bestMacAddr, macAddr);
83 if (res < 0) {
84
85 replace = true;
86 } else if (res == 0) {
87
88 res = compareAddresses(bestInetAddr, inetAddr);
89 if (res < 0) {
90
91 replace = true;
92 } else if (res == 0) {
93
94 if (bestMacAddr.length < macAddr.length) {
95 replace = true;
96 }
97 }
98 }
99
100 if (replace) {
101 bestMacAddr = macAddr;
102 bestInetAddr = inetAddr;
103 }
104 }
105
106 if (bestMacAddr == EMPTY_BYTES) {
107 return null;
108 }
109
110 if (bestMacAddr.length == EUI48_MAC_ADDRESS_LENGTH) {
111 byte[] newAddr = new byte[EUI64_MAC_ADDRESS_LENGTH];
112 System.arraycopy(bestMacAddr, 0, newAddr, 0, 3);
113 newAddr[3] = (byte) 0xFF;
114 newAddr[4] = (byte) 0xFE;
115 System.arraycopy(bestMacAddr, 3, newAddr, 5, 3);
116 bestMacAddr = newAddr;
117 } else {
118
119 bestMacAddr = Arrays.copyOf(bestMacAddr, EUI64_MAC_ADDRESS_LENGTH);
120 }
121
122 return bestMacAddr;
123 }
124
125
126
127
128
129 public static byte[] defaultMachineId() {
130 byte[] bestMacAddr = bestAvailableMac();
131 if (bestMacAddr == null) {
132 bestMacAddr = new byte[EUI64_MAC_ADDRESS_LENGTH];
133 ThreadLocalRandom.current().nextBytes(bestMacAddr);
134 logger.warn(
135 "Failed to find a usable hardware address from the network interfaces; using random bytes: {}",
136 formatAddress(bestMacAddr));
137 }
138 return bestMacAddr;
139 }
140
141
142
143
144
145
146 public static byte[] parseMAC(String value) {
147 final byte[] machineId;
148 final char separator;
149 switch (value.length()) {
150 case 17:
151 separator = value.charAt(2);
152 validateMacSeparator(separator);
153 machineId = new byte[EUI48_MAC_ADDRESS_LENGTH];
154 break;
155 case 23:
156 separator = value.charAt(2);
157 validateMacSeparator(separator);
158 machineId = new byte[EUI64_MAC_ADDRESS_LENGTH];
159 break;
160 default:
161 throw new IllegalArgumentException("value is not supported [MAC-48, EUI-48, EUI-64]");
162 }
163
164 final int end = machineId.length - 1;
165 int j = 0;
166 for (int i = 0; i < end; ++i, j += 3) {
167 final int sIndex = j + 2;
168 machineId[i] = StringUtil.decodeHexByte(value, j);
169 if (value.charAt(sIndex) != separator) {
170 throw new IllegalArgumentException("expected separator '" + separator + " but got '" +
171 value.charAt(sIndex) + "' at index: " + sIndex);
172 }
173 }
174
175 machineId[end] = StringUtil.decodeHexByte(value, j);
176
177 return machineId;
178 }
179
180 private static void validateMacSeparator(char separator) {
181 if (separator != ':' && separator != '-') {
182 throw new IllegalArgumentException("unsupported separator: " + separator + " (expected: [:-])");
183 }
184 }
185
186
187
188
189
190 public static String formatAddress(byte[] addr) {
191 StringBuilder buf = new StringBuilder(24);
192 for (byte b: addr) {
193 buf.append(String.format("%02x:", b & 0xff));
194 }
195 return buf.substring(0, buf.length() - 1);
196 }
197
198
199
200
201
202 static int compareAddresses(byte[] current, byte[] candidate) {
203 if (candidate == null || candidate.length < EUI48_MAC_ADDRESS_LENGTH) {
204 return 1;
205 }
206
207
208 boolean onlyZeroAndOne = true;
209 for (byte b: candidate) {
210 if (b != 0 && b != 1) {
211 onlyZeroAndOne = false;
212 break;
213 }
214 }
215
216 if (onlyZeroAndOne) {
217 return 1;
218 }
219
220
221 if ((candidate[0] & 1) != 0) {
222 return 1;
223 }
224
225
226 if ((candidate[0] & 2) == 0) {
227 if (current.length != 0 && (current[0] & 2) == 0) {
228
229 return 0;
230 } else {
231
232 return -1;
233 }
234 } else {
235 if (current.length != 0 && (current[0] & 2) == 0) {
236
237 return 1;
238 } else {
239
240 return 0;
241 }
242 }
243 }
244
245
246
247
248 private static int compareAddresses(InetAddress current, InetAddress candidate) {
249 return scoreAddress(current) - scoreAddress(candidate);
250 }
251
252 private static int scoreAddress(InetAddress addr) {
253 if (addr.isAnyLocalAddress() || addr.isLoopbackAddress()) {
254 return 0;
255 }
256 if (addr.isMulticastAddress()) {
257 return 1;
258 }
259 if (addr.isLinkLocalAddress()) {
260 return 2;
261 }
262 if (addr.isSiteLocalAddress()) {
263 return 3;
264 }
265
266 return 4;
267 }
268
269 private MacAddressUtil() { }
270 }