View Javadoc
1   /*
2    * Copyright 2013 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    *   http://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.channel;
18  
19  import io.netty.buffer.ByteBufUtil;
20  import io.netty.util.internal.EmptyArrays;
21  import io.netty.util.internal.PlatformDependent;
22  import io.netty.util.internal.SystemPropertyUtil;
23  import io.netty.util.internal.ThreadLocalRandom;
24  import io.netty.util.internal.logging.InternalLogger;
25  import io.netty.util.internal.logging.InternalLoggerFactory;
26  
27  import java.lang.reflect.Method;
28  import java.net.InetAddress;
29  import java.net.NetworkInterface;
30  import java.net.SocketException;
31  import java.net.UnknownHostException;
32  import java.util.Arrays;
33  import java.util.Enumeration;
34  import java.util.LinkedHashMap;
35  import java.util.Map;
36  import java.util.Map.Entry;
37  import java.util.concurrent.atomic.AtomicInteger;
38  import java.util.regex.Pattern;
39  
40  /**
41   * The default {@link ChannelId} implementation.
42   */
43  final class DefaultChannelId implements ChannelId {
44  
45      private static final long serialVersionUID = 3884076183504074063L;
46  
47      private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelId.class);
48  
49      private static final Pattern MACHINE_ID_PATTERN = Pattern.compile("^(?:[0-9a-fA-F][:-]?){6,8}$");
50      private static final int MACHINE_ID_LEN = 8;
51      private static final byte[] MACHINE_ID;
52      private static final int PROCESS_ID_LEN = 4;
53      // Maximal value for 64bit systems is 2^22.  See man 5 proc.
54      // See https://github.com/netty/netty/issues/2706
55      private static final int MAX_PROCESS_ID = 4194304;
56      private static final int PROCESS_ID;
57      private static final int SEQUENCE_LEN = 4;
58      private static final int TIMESTAMP_LEN = 8;
59      private static final int RANDOM_LEN = 4;
60  
61      private static final AtomicInteger nextSequence = new AtomicInteger();
62  
63      static ChannelId newInstance() {
64          DefaultChannelId id = new DefaultChannelId();
65          id.init();
66          return id;
67      }
68  
69      static {
70          int processId = -1;
71          String customProcessId = SystemPropertyUtil.get("io.netty.processId");
72          if (customProcessId != null) {
73              try {
74                  processId = Integer.parseInt(customProcessId);
75              } catch (NumberFormatException e) {
76                  // Malformed input.
77              }
78  
79              if (processId < 0 || processId > MAX_PROCESS_ID) {
80                  processId = -1;
81                  logger.warn("-Dio.netty.processId: {} (malformed)", customProcessId);
82              } else if (logger.isDebugEnabled()) {
83                  logger.debug("-Dio.netty.processId: {} (user-set)", processId);
84              }
85          }
86  
87          if (processId < 0) {
88              processId = defaultProcessId();
89              if (logger.isDebugEnabled()) {
90                  logger.debug("-Dio.netty.processId: {} (auto-detected)", processId);
91              }
92          }
93  
94          PROCESS_ID = processId;
95  
96          byte[] machineId = null;
97          String customMachineId = SystemPropertyUtil.get("io.netty.machineId");
98          if (customMachineId != null) {
99              if (MACHINE_ID_PATTERN.matcher(customMachineId).matches()) {
100                 machineId = parseMachineId(customMachineId);
101                 logger.debug("-Dio.netty.machineId: {} (user-set)", customMachineId);
102             } else {
103                 logger.warn("-Dio.netty.machineId: {} (malformed)", customMachineId);
104             }
105         }
106 
107         if (machineId == null) {
108             machineId = defaultMachineId();
109             if (logger.isDebugEnabled()) {
110                 logger.debug("-Dio.netty.machineId: {} (auto-detected)", formatAddress(machineId));
111             }
112         }
113 
114         MACHINE_ID = machineId;
115     }
116 
117     @SuppressWarnings("DynamicRegexReplaceableByCompiledPattern")
118     private static byte[] parseMachineId(String value) {
119         // Strip separators.
120         value = value.replaceAll("[:-]", "");
121 
122         byte[] machineId = new byte[MACHINE_ID_LEN];
123         for (int i = 0; i < value.length(); i += 2) {
124             machineId[i] = (byte) Integer.parseInt(value.substring(i, i + 2), 16);
125         }
126 
127         return machineId;
128     }
129 
130     private static byte[] defaultMachineId() {
131         // Find the best MAC address available.
132         final byte[] NOT_FOUND = { -1 };
133         byte[] bestMacAddr = NOT_FOUND;
134         InetAddress bestInetAddr = null;
135         try {
136             bestInetAddr = InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 });
137         } catch (UnknownHostException e) {
138             // Never happens.
139             PlatformDependent.throwException(e);
140         }
141 
142         // Retrieve the list of available network interfaces.
143         Map<NetworkInterface, InetAddress> ifaces = new LinkedHashMap<NetworkInterface, InetAddress>();
144         try {
145             for (Enumeration<NetworkInterface> i = NetworkInterface.getNetworkInterfaces(); i.hasMoreElements();) {
146                 NetworkInterface iface = i.nextElement();
147                 // Use the interface with proper INET addresses only.
148                 Enumeration<InetAddress> addrs = iface.getInetAddresses();
149                 if (addrs.hasMoreElements()) {
150                     InetAddress a = addrs.nextElement();
151                     if (!a.isLoopbackAddress()) {
152                         ifaces.put(iface, a);
153                     }
154                 }
155             }
156         } catch (SocketException e) {
157             logger.warn("Failed to retrieve the list of available network interfaces", e);
158         }
159 
160         for (Entry<NetworkInterface, InetAddress> entry: ifaces.entrySet()) {
161             NetworkInterface iface = entry.getKey();
162             InetAddress inetAddr = entry.getValue();
163             if (iface.isVirtual()) {
164                 continue;
165             }
166 
167             byte[] macAddr;
168             try {
169                 macAddr = iface.getHardwareAddress();
170             } catch (SocketException e) {
171                 logger.debug("Failed to get the hardware address of a network interface: {}", iface, e);
172                 continue;
173             }
174 
175             boolean replace = false;
176             int res = compareAddresses(bestMacAddr, macAddr);
177             if (res < 0) {
178                 // Found a better MAC address.
179                 replace = true;
180             } else if (res == 0) {
181                 // Two MAC addresses are of pretty much same quality.
182                 res = compareAddresses(bestInetAddr, inetAddr);
183                 if (res < 0) {
184                     // Found a MAC address with better INET address.
185                     replace = true;
186                 } else if (res == 0) {
187                     // Cannot tell the difference.  Choose the longer one.
188                     if (bestMacAddr.length < macAddr.length) {
189                         replace = true;
190                     }
191                 }
192             }
193 
194             if (replace) {
195                 bestMacAddr = macAddr;
196                 bestInetAddr = inetAddr;
197             }
198         }
199 
200         if (bestMacAddr == NOT_FOUND) {
201             bestMacAddr = new byte[MACHINE_ID_LEN];
202             ThreadLocalRandom.current().nextBytes(bestMacAddr);
203             logger.warn(
204                     "Failed to find a usable hardware address from the network interfaces; using random bytes: {}",
205                     formatAddress(bestMacAddr));
206         }
207 
208         switch (bestMacAddr.length) {
209             case 6: // EUI-48 - convert to EUI-64
210                 byte[] newAddr = new byte[MACHINE_ID_LEN];
211                 System.arraycopy(bestMacAddr, 0, newAddr, 0, 3);
212                 newAddr[3] = (byte) 0xFF;
213                 newAddr[4] = (byte) 0xFE;
214                 System.arraycopy(bestMacAddr, 3, newAddr, 5, 3);
215                 bestMacAddr = newAddr;
216                 break;
217             default: // Unknown
218                 bestMacAddr = Arrays.copyOf(bestMacAddr, MACHINE_ID_LEN);
219         }
220 
221         return bestMacAddr;
222     }
223 
224     /**
225      * @return positive - current is better, 0 - cannot tell from MAC addr, negative - candidate is better.
226      */
227     private static int compareAddresses(byte[] current, byte[] candidate) {
228         if (candidate == null) {
229             return 1;
230         }
231 
232         // Must be EUI-48 or longer.
233         if (candidate.length < 6) {
234             return 1;
235         }
236 
237         // Must not be filled with only 0 and 1.
238         boolean onlyZeroAndOne = true;
239         for (byte b: candidate) {
240             if (b != 0 && b != 1) {
241                 onlyZeroAndOne = false;
242                 break;
243             }
244         }
245 
246         if (onlyZeroAndOne) {
247             return 1;
248         }
249 
250         // Must not be a multicast address
251         if ((candidate[0] & 1) != 0) {
252             return 1;
253         }
254 
255         // Prefer globally unique address.
256         if ((current[0] & 2) == 0) {
257             if ((candidate[0] & 2) == 0) {
258                 // Both current and candidate are globally unique addresses.
259                 return 0;
260             } else {
261                 // Only current is globally unique.
262                 return 1;
263             }
264         } else {
265             if ((candidate[0] & 2) == 0) {
266                 // Only candidate is globally unique.
267                 return -1;
268             } else {
269                 // Both current and candidate are non-unique.
270                 return 0;
271             }
272         }
273     }
274 
275     /**
276      * @return positive - current is better, 0 - cannot tell, negative - candidate is better
277      */
278     private static int compareAddresses(InetAddress current, InetAddress candidate) {
279         return scoreAddress(current) - scoreAddress(candidate);
280     }
281 
282     private static int scoreAddress(InetAddress addr) {
283         if (addr.isAnyLocalAddress()) {
284             return 0;
285         }
286         if (addr.isMulticastAddress()) {
287             return 1;
288         }
289         if (addr.isLinkLocalAddress()) {
290             return 2;
291         }
292         if (addr.isSiteLocalAddress()) {
293             return 3;
294         }
295 
296         return 4;
297     }
298 
299     private static String formatAddress(byte[] addr) {
300         StringBuilder buf = new StringBuilder(24);
301         for (byte b: addr) {
302             buf.append(String.format("%02x:", b & 0xff));
303         }
304         return buf.substring(0, buf.length() - 1);
305     }
306 
307     private static int defaultProcessId() {
308         final ClassLoader loader = PlatformDependent.getSystemClassLoader();
309         String value;
310         try {
311             // Invoke java.lang.management.ManagementFactory.getRuntimeMXBean().getName()
312             Class<?> mgmtFactoryType = Class.forName("java.lang.management.ManagementFactory", true, loader);
313             Class<?> runtimeMxBeanType = Class.forName("java.lang.management.RuntimeMXBean", true, loader);
314 
315             Method getRuntimeMXBean = mgmtFactoryType.getMethod("getRuntimeMXBean", EmptyArrays.EMPTY_CLASSES);
316             Object bean = getRuntimeMXBean.invoke(null, EmptyArrays.EMPTY_OBJECTS);
317             Method getName = runtimeMxBeanType.getDeclaredMethod("getName", EmptyArrays.EMPTY_CLASSES);
318             value = (String) getName.invoke(bean, EmptyArrays.EMPTY_OBJECTS);
319         } catch (Exception e) {
320             logger.debug("Could not invoke ManagementFactory.getRuntimeMXBean().getName(); Android?", e);
321             try {
322                 // Invoke android.os.Process.myPid()
323                 Class<?> processType = Class.forName("android.os.Process", true, loader);
324                 Method myPid = processType.getMethod("myPid", EmptyArrays.EMPTY_CLASSES);
325                 value = myPid.invoke(null, EmptyArrays.EMPTY_OBJECTS).toString();
326             } catch (Exception e2) {
327                 logger.debug("Could not invoke Process.myPid(); not Android?", e2);
328                 value = "";
329             }
330         }
331 
332         int atIndex = value.indexOf('@');
333         if (atIndex >= 0) {
334             value = value.substring(0, atIndex);
335         }
336 
337         int pid;
338         try {
339             pid = Integer.parseInt(value);
340         } catch (NumberFormatException e) {
341             // value did not contain an integer.
342             pid = -1;
343         }
344 
345         if (pid < 0 || pid > MAX_PROCESS_ID) {
346             pid = ThreadLocalRandom.current().nextInt(MAX_PROCESS_ID + 1);
347             logger.warn("Failed to find the current process ID from '{}'; using a random value: {}",  value, pid);
348         }
349 
350         return pid;
351     }
352 
353     private final byte[] data = new byte[MACHINE_ID_LEN + PROCESS_ID_LEN + SEQUENCE_LEN + TIMESTAMP_LEN + RANDOM_LEN];
354     private int hashCode;
355 
356     private transient String shortValue;
357     private transient String longValue;
358 
359     private void init() {
360         int i = 0;
361 
362         // machineId
363         System.arraycopy(MACHINE_ID, 0, data, i, MACHINE_ID_LEN);
364         i += MACHINE_ID_LEN;
365 
366         // processId
367         i = writeInt(i, PROCESS_ID);
368 
369         // sequence
370         i = writeInt(i, nextSequence.getAndIncrement());
371 
372         // timestamp (kind of)
373         i = writeLong(i, Long.reverse(System.nanoTime()) ^ System.currentTimeMillis());
374 
375         // random
376         int random = ThreadLocalRandom.current().nextInt();
377         hashCode = random;
378         i = writeInt(i, random);
379 
380         assert i == data.length;
381     }
382 
383     private int writeInt(int i, int value) {
384         data[i ++] = (byte) (value >>> 24);
385         data[i ++] = (byte) (value >>> 16);
386         data[i ++] = (byte) (value >>> 8);
387         data[i ++] = (byte) value;
388         return i;
389     }
390 
391     private int writeLong(int i, long value) {
392         data[i ++] = (byte) (value >>> 56);
393         data[i ++] = (byte) (value >>> 48);
394         data[i ++] = (byte) (value >>> 40);
395         data[i ++] = (byte) (value >>> 32);
396         data[i ++] = (byte) (value >>> 24);
397         data[i ++] = (byte) (value >>> 16);
398         data[i ++] = (byte) (value >>> 8);
399         data[i ++] = (byte) value;
400         return i;
401     }
402 
403     @Override
404     public String asShortText() {
405         String shortValue = this.shortValue;
406         if (shortValue == null) {
407             this.shortValue = shortValue = ByteBufUtil.hexDump(
408                     data, MACHINE_ID_LEN + PROCESS_ID_LEN + SEQUENCE_LEN + TIMESTAMP_LEN, RANDOM_LEN);
409         }
410         return shortValue;
411     }
412 
413     @Override
414     public String asLongText() {
415         String longValue = this.longValue;
416         if (longValue == null) {
417             this.longValue = longValue = newLongValue();
418         }
419         return longValue;
420     }
421 
422     private String newLongValue() {
423         StringBuilder buf = new StringBuilder(2 * data.length + 5);
424         int i = 0;
425         i = appendHexDumpField(buf, i, MACHINE_ID_LEN);
426         i = appendHexDumpField(buf, i, PROCESS_ID_LEN);
427         i = appendHexDumpField(buf, i, SEQUENCE_LEN);
428         i = appendHexDumpField(buf, i, TIMESTAMP_LEN);
429         i = appendHexDumpField(buf, i, RANDOM_LEN);
430         assert i == data.length;
431         return buf.substring(0, buf.length() - 1);
432     }
433 
434     private int appendHexDumpField(StringBuilder buf, int i, int length) {
435         buf.append(ByteBufUtil.hexDump(data, i, length));
436         buf.append('-');
437         i += length;
438         return i;
439     }
440 
441     @Override
442     public int hashCode() {
443         return hashCode;
444     }
445 
446     @Override
447     public int compareTo(ChannelId o) {
448         return 0;
449     }
450 
451     @Override
452     public boolean equals(Object obj) {
453         if (obj == this) {
454             return true;
455         }
456 
457         if (!(obj instanceof DefaultChannelId)) {
458             return false;
459         }
460 
461         return Arrays.equals(data, ((DefaultChannelId) obj).data);
462     }
463 
464     @Override
465     public String toString() {
466         return asShortText();
467     }
468 }