View Javadoc
1   /*
2    * Copyright 2016 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
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       * Obtains the best MAC address found on local network interfaces.
43       * Generally speaking, an active network interface used on public
44       * networks is better than a local network interface.
45       *
46       * @return byte array containing a MAC. null if no MAC can be found.
47       */
48      public static byte[] bestAvailableMac() {
49          // Find the best MAC address available.
50          byte[] bestMacAddr = EMPTY_BYTES;
51          InetAddress bestInetAddr = NetUtil.LOCALHOST4;
52  
53          // Retrieve the list of available network interfaces.
54          Map<NetworkInterface, InetAddress> ifaces = new LinkedHashMap<NetworkInterface, InetAddress>();
55          for (NetworkInterface iface: NetUtil.NETWORK_INTERFACES) {
56              // Use the interface with proper INET addresses only.
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                  // Found a better MAC address.
85                  replace = true;
86              } else if (res == 0) {
87                  // Two MAC addresses are of pretty much same quality.
88                  res = compareAddresses(bestInetAddr, inetAddr);
89                  if (res < 0) {
90                      // Found a MAC address with better INET address.
91                      replace = true;
92                  } else if (res == 0) {
93                      // Cannot tell the difference.  Choose the longer one.
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) { // EUI-48 - convert to EUI-64
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             // Unknown
119             bestMacAddr = Arrays.copyOf(bestMacAddr, EUI64_MAC_ADDRESS_LENGTH);
120         }
121 
122         return bestMacAddr;
123     }
124 
125     /**
126      * Returns the result of {@link #bestAvailableMac()} if non-{@code null} otherwise returns a random EUI-64 MAC
127      * address.
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      * Parse a EUI-48, MAC-48, or EUI-64 MAC address from a {@link String} and return it as a {@code byte[]}.
143      * @param value The string representation of the MAC address.
144      * @return The byte representation of the MAC address.
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      * @param addr byte array of a MAC address.
188      * @return hex formatted MAC address.
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      * @return positive - current is better, 0 - cannot tell from MAC addr, negative - candidate is better.
200      */
201     // visible for testing
202     static int compareAddresses(byte[] current, byte[] candidate) {
203         if (candidate == null || candidate.length < EUI48_MAC_ADDRESS_LENGTH) {
204             return 1;
205         }
206 
207         // Must not be filled with only 0 and 1.
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         // Must not be a multicast address
221         if ((candidate[0] & 1) != 0) {
222             return 1;
223         }
224 
225         // Prefer globally unique address.
226         if ((candidate[0] & 2) == 0) {
227             if (current.length != 0 && (current[0] & 2) == 0) {
228                 // Both current and candidate are globally unique addresses.
229                 return 0;
230             } else {
231                 // Only candidate is globally unique.
232                 return -1;
233             }
234         } else {
235             if (current.length != 0 && (current[0] & 2) == 0) {
236                 // Only current is globally unique.
237                 return 1;
238             } else {
239                 // Both current and candidate are non-unique.
240                 return 0;
241             }
242         }
243     }
244 
245     /**
246      * @return positive - current is better, 0 - cannot tell, negative - candidate is better
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 }