From 37f5d68c35205916f68b641eced3aa9d6012b011 Mon Sep 17 00:00:00 2001 From: Paul Galbraith Date: Sat, 14 Aug 2021 14:02:28 -0400 Subject: [PATCH 1/2] by default use the default egress interface --- .../com/fasterxml/uuid/EthernetAddress.java | 77 +++++++++++++++++-- .../java/com/fasterxml/uuid/Generators.java | 25 ++++++ .../fasterxml/uuid/EthernetAddressTest.java | 43 ++++++++++- 3 files changed, 138 insertions(+), 7 deletions(-) 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 afffd47..03fd107 100644 --- a/src/main/java/com/fasterxml/uuid/Generators.java +++ b/src/main/java/com/fasterxml/uuid/Generators.java @@ -39,6 +39,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 @@ -115,6 +120,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). @@ -195,4 +212,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 b235c7d..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; @@ -25,8 +27,6 @@ import java.util.Arrays; import java.util.Random; -import com.fasterxml.uuid.EthernetAddress; - /** * JUnit Test class for the com.fasterxml.uuid.EthernetAddress class. * @@ -1309,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 From bfcfd196e312a4715d9f506808a372c95f4fdc74 Mon Sep 17 00:00:00 2001 From: Paul Galbraith Date: Sat, 18 Jun 2022 13:24:04 -0400 Subject: [PATCH 2/2] Fixing some bugs: * was not working when default IF was not the first interface returned * `EthernetAddress.fromInterface(nint)` will now return null if `nint` is null * removed unit tests that required both of IP4 and IP6 connectivity * mention the new factory method in the readme * minor documentation touch up --- README.md | 10 ++++++++ .../com/fasterxml/uuid/EthernetAddress.java | 23 +++++++++++-------- .../java/com/fasterxml/uuid/Generators.java | 7 ++++-- .../fasterxml/uuid/EthernetAddressTest.java | 17 +------------- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 0c9a526..e3042cc 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,16 @@ UUID uuid = gen.generate(); UUID anotherUuid = gen.generate(); ``` +If your machine has a standard IP networking setup, the `Generators.egressTimeBasedGenerator` +factory method will try to determine which network interface corresponds to the default route for +all outgoing network traffic, and use that for creating a time based generator. This is likely a +good choice for common usage scenarios if you want a version 1 UUID generator: +```java +TimeBasedGenerator gen = Generators.egressTimeBasedGenerator(); +UUID uuid = gen.generate(); +UUID anotherUuid = gen.generate(); +``` + Generators are fully thread-safe, so a single instance may be shared among multiple threads. JavaDocs for project can be found from [Project Wiki](../../wiki). diff --git a/src/main/java/com/fasterxml/uuid/EthernetAddress.java b/src/main/java/com/fasterxml/uuid/EthernetAddress.java index 6b322a4..97cbe79 100644 --- a/src/main/java/com/fasterxml/uuid/EthernetAddress.java +++ b/src/main/java/com/fasterxml/uuid/EthernetAddress.java @@ -275,7 +275,10 @@ public static EthernetAddress fromInterface() while (en.hasMoreElements()) { NetworkInterface nint = en.nextElement(); if (!nint.isLoopback()) { - return fromInterface(nint); + EthernetAddress addr = fromInterface(nint); + if (addr != null) { + return addr; + } } } } catch (java.net.SocketException e) { @@ -289,13 +292,15 @@ public static EthernetAddress fromInterface() */ public static EthernetAddress fromInterface(NetworkInterface nint) { - try { - byte[] data = nint.getHardwareAddress(); - if (data != null && data.length == 6) { - return new EthernetAddress(data); + if (nint != null) { + try { + byte[] data = nint.getHardwareAddress(); + if (data != null && data.length == 6) { + return new EthernetAddress(data); + } + } catch (SocketException e) { + // could not get address } - } catch (SocketException e) { - // could not get address } return null; } @@ -304,7 +309,7 @@ public static EthernetAddress fromInterface(NetworkInterface nint) * 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 + * servers, or barring that, to address 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 @@ -330,7 +335,7 @@ public static EthernetAddress fromEgressInterface() /** * A factory method to return the address of the interface used to route - * traffic to the specified address. + * traffic to the specified IP address. */ public static EthernetAddress fromEgressInterface(InetSocketAddress externalSocketAddress) { diff --git a/src/main/java/com/fasterxml/uuid/Generators.java b/src/main/java/com/fasterxml/uuid/Generators.java index 03fd107..2a46f6b 100644 --- a/src/main/java/com/fasterxml/uuid/Generators.java +++ b/src/main/java/com/fasterxml/uuid/Generators.java @@ -41,7 +41,7 @@ public class Generators protected static UUIDTimer _sharedTimer; /** - * The default egress network interface. + * The hardware address of the egress network interface. */ protected static EthernetAddress _egressIfAddr = null; @@ -125,7 +125,10 @@ public static NameBasedGenerator nameBasedGenerator(UUID namespace, MessageDiges * (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()}. + * by the calling {@link EthernetAddress#fromEgressInterface()}. Note that this will only + * identify the egress interface once: if you have a complex network setup where your + * outbound routes/interfaces may change dynamically, and you want your UUIDs to + * accurately reflect which interface is being actively used, this method is not for you. */ public static TimeBasedGenerator egressTimeBasedGenerator() { diff --git a/src/test/java/com/fasterxml/uuid/EthernetAddressTest.java b/src/test/java/com/fasterxml/uuid/EthernetAddressTest.java index 59bcd01..46c797e 100644 --- a/src/test/java/com/fasterxml/uuid/EthernetAddressTest.java +++ b/src/test/java/com/fasterxml/uuid/EthernetAddressTest.java @@ -1317,22 +1317,6 @@ public void testFromEgressInterfaceRoot() throws Exception 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(); @@ -1343,6 +1327,7 @@ public void testFromEgressInterface() throws Exception public void testDefaultTimeBasedGenerator() { TimeBasedGenerator generator = Generators.egressTimeBasedGenerator(); + assertNotNull(generator); EthernetAddress ifAddr = generator.getEthernetAddress(); assertNotNull(ifAddr); assertNotNull(ifAddr.toString());