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