diff --git a/src/main/java/com/fasterxml/uuid/EthernetAddress.java b/src/main/java/com/fasterxml/uuid/EthernetAddress.java index 5cbb4cd..6b322a4 100644 --- a/src/main/java/com/fasterxml/uuid/EthernetAddress.java +++ b/src/main/java/com/fasterxml/uuid/EthernetAddress.java @@ -16,7 +16,11 @@ package com.fasterxml.uuid; import java.io.Serializable; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.NetworkInterface; +import java.net.SocketException; import java.security.SecureRandom; import java.util.Enumeration; import java.util.Random; @@ -271,10 +275,7 @@ public static EthernetAddress fromInterface() while (en.hasMoreElements()) { NetworkInterface nint = en.nextElement(); if (!nint.isLoopback()) { - byte[] data = nint.getHardwareAddress(); - if (data != null && data.length == 6) { - return new EthernetAddress(data); - } + return fromInterface(nint); } } } catch (java.net.SocketException e) { @@ -282,7 +283,73 @@ public static EthernetAddress fromInterface() } return null; } - + + /** + * A factory method to return the ethernet address of a specified network interface. + */ + public static EthernetAddress fromInterface(NetworkInterface nint) + { + try { + byte[] data = nint.getHardwareAddress(); + if (data != null && data.length == 6) { + return new EthernetAddress(data); + } + } catch (SocketException e) { + // could not get address + } + return null; + } + + /** + * A factory method that will try to determine the ethernet address of + * the network interface that connects to the default network gateway. + * To do this it will try to open a connection to one of the root DNS + * servers, or barring that, to adresss 1.1.1.1, or finally if that also + * fails then to IPv6 address "1::1". If a connection can be opened then + * the interface through which that connection is routed is determined + * to be the default egress interface, and the corresponding address of + * that interface will be returned. If all attempts are unsuccessful, + * null will be returned. + */ + public static EthernetAddress fromEgressInterface() + { + String roots = "abcdefghijklm"; + int index = new Random().nextInt(roots.length()); + String name = roots.charAt(index) + ".root-servers.net"; + InetSocketAddress externalAddress = new InetSocketAddress(name, 0); + if (externalAddress.isUnresolved()) { + externalAddress = new InetSocketAddress("1.1.1.1", 0); + } + EthernetAddress ifAddr = fromEgressInterface(externalAddress); + if (ifAddr == null) { + return fromEgressInterface(new InetSocketAddress("1::1", 0)); + } else { + return ifAddr; + } + } + + /** + * A factory method to return the address of the interface used to route + * traffic to the specified address. + */ + public static EthernetAddress fromEgressInterface(InetSocketAddress externalSocketAddress) + { + DatagramSocket socket = null; + try { + socket = new DatagramSocket(); + socket.connect(externalSocketAddress); + InetAddress localAddress = socket.getLocalAddress(); + NetworkInterface egressIf = NetworkInterface.getByInetAddress(localAddress); + return fromInterface(egressIf); + } catch (SocketException e) { + return null; + } finally { + if (socket != null) { + socket.close(); + } + } + } + /** * Factory method that can be used to construct a random multicast * address; to be used in cases where there is no "real" ethernet diff --git a/src/main/java/com/fasterxml/uuid/Generators.java b/src/main/java/com/fasterxml/uuid/Generators.java index 35515e0..88b8542 100644 --- a/src/main/java/com/fasterxml/uuid/Generators.java +++ b/src/main/java/com/fasterxml/uuid/Generators.java @@ -40,6 +40,11 @@ public class Generators * synchronization but no external file-based syncing. */ protected static UUIDTimer _sharedTimer; + + /** + * The default egress network interface. + */ + protected static EthernetAddress _egressIfAddr = null; // // Random-based generation @@ -116,6 +121,18 @@ public static NameBasedGenerator nameBasedGenerator(UUID namespace, MessageDiges // // Time+location-based generation + /** + * Factory method for constructing UUID generator that generates UUID using variant 1 + * (time+location based). This method will use the ethernet address of the interface + * that routes to the default gateway. For most simple and common networking configurations + * this will be the most appropriate address to use. The default interface is determined + * by the calling {@link EthernetAddress#fromEgressInterface()}. + */ + public static TimeBasedGenerator egressTimeBasedGenerator() + { + return timeBasedGenerator(egressInterfaceAddress()); + } + /** * Factory method for constructing UUID generator that generates UUID using * variant 1 (time+location based). @@ -238,4 +255,12 @@ private static synchronized UUIDTimer sharedTimer() } return _sharedTimer; } + + private static synchronized EthernetAddress egressInterfaceAddress() + { + if (_egressIfAddr == null) { + _egressIfAddr = EthernetAddress.fromEgressInterface(); + } + return _egressIfAddr; + } } diff --git a/src/test/java/com/fasterxml/uuid/EthernetAddressTest.java b/src/test/java/com/fasterxml/uuid/EthernetAddressTest.java index 6e06308..59bcd01 100644 --- a/src/test/java/com/fasterxml/uuid/EthernetAddressTest.java +++ b/src/test/java/com/fasterxml/uuid/EthernetAddressTest.java @@ -17,6 +17,8 @@ package com.fasterxml.uuid; +import com.fasterxml.uuid.impl.TimeBasedGenerator; +import java.net.InetSocketAddress; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; @@ -1307,6 +1309,45 @@ public void testFromInterface() throws Exception assertNotNull(addr.toString()); } + public void testFromEgressInterfaceRoot() throws Exception + { + InetSocketAddress extAddr = new InetSocketAddress("a.root-servers.net", 0); + EthernetAddress ifAddr = EthernetAddress.fromEgressInterface(extAddr); + assertNotNull(ifAddr); + assertNotNull(ifAddr.toString()); + } + + public void testFromEgressInterfaceIp4() throws Exception + { + InetSocketAddress extAddr = new InetSocketAddress("1.1.1.1", 0); + EthernetAddress ifAddr = EthernetAddress.fromEgressInterface(extAddr); + assertNotNull(ifAddr); + assertNotNull(ifAddr.toString()); + } + + public void testFromEgressInterfaceIp6() throws Exception + { + InetSocketAddress extAddr = new InetSocketAddress("1::1", 0); + EthernetAddress ifAddr = EthernetAddress.fromEgressInterface(extAddr); + assertNotNull(ifAddr); + assertNotNull(ifAddr.toString()); + } + + public void testFromEgressInterface() throws Exception + { + EthernetAddress ifAddr = EthernetAddress.fromEgressInterface(); + assertNotNull(ifAddr); + assertNotNull(ifAddr.toString()); + } + + public void testDefaultTimeBasedGenerator() + { + TimeBasedGenerator generator = Generators.egressTimeBasedGenerator(); + EthernetAddress ifAddr = generator.getEthernetAddress(); + assertNotNull(ifAddr); + assertNotNull(ifAddr.toString()); + } + public void testBogus() throws Exception { // First, two using pseudo-random; verify they are different