Skip to content

Commit 56043d1

Browse files
authored
TimeBasedGenerator factory method that defaults to a sensible choice of interface (#52)
1 parent 6679581 commit 56043d1

File tree

4 files changed

+140
-4
lines changed

4 files changed

+140
-4
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ UUID uuid = gen.generate();
7373
UUID anotherUuid = gen.generate();
7474
```
7575

76+
If your machine has a standard IP networking setup, the `Generators.egressTimeBasedGenerator`
77+
factory method will try to determine which network interface corresponds to the default route for
78+
all outgoing network traffic, and use that for creating a time based generator. This is likely a
79+
good choice for common usage scenarios if you want a version 1 UUID generator:
80+
```java
81+
TimeBasedGenerator gen = Generators.egressTimeBasedGenerator();
82+
UUID uuid = gen.generate();
83+
UUID anotherUuid = gen.generate();
84+
```
85+
7686
Generators are fully thread-safe, so a single instance may be shared among multiple threads.
7787

7888
Javadocs for further information can be found from [Project Wiki](../../wiki).

src/main/java/com/fasterxml/uuid/EthernetAddress.java

+76-4
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616
package com.fasterxml.uuid;
1717

1818
import java.io.Serializable;
19+
import java.net.DatagramSocket;
20+
import java.net.InetAddress;
21+
import java.net.InetSocketAddress;
1922
import java.net.NetworkInterface;
23+
import java.net.SocketException;
2024
import java.security.SecureRandom;
2125
import java.util.Enumeration;
2226
import java.util.Random;
@@ -271,9 +275,9 @@ public static EthernetAddress fromInterface()
271275
while (en.hasMoreElements()) {
272276
NetworkInterface nint = en.nextElement();
273277
if (!nint.isLoopback()) {
274-
byte[] data = nint.getHardwareAddress();
275-
if (data != null && data.length == 6) {
276-
return new EthernetAddress(data);
278+
EthernetAddress addr = fromInterface(nint);
279+
if (addr != null) {
280+
return addr;
277281
}
278282
}
279283
}
@@ -282,7 +286,75 @@ public static EthernetAddress fromInterface()
282286
}
283287
return null;
284288
}
285-
289+
290+
/**
291+
* A factory method to return the ethernet address of a specified network interface.
292+
*/
293+
public static EthernetAddress fromInterface(NetworkInterface nint)
294+
{
295+
if (nint != null) {
296+
try {
297+
byte[] data = nint.getHardwareAddress();
298+
if (data != null && data.length == 6) {
299+
return new EthernetAddress(data);
300+
}
301+
} catch (SocketException e) {
302+
// could not get address
303+
}
304+
}
305+
return null;
306+
}
307+
308+
/**
309+
* A factory method that will try to determine the ethernet address of
310+
* the network interface that connects to the default network gateway.
311+
* To do this it will try to open a connection to one of the root DNS
312+
* servers, or barring that, to address 1.1.1.1, or finally if that also
313+
* fails then to IPv6 address "1::1". If a connection can be opened then
314+
* the interface through which that connection is routed is determined
315+
* to be the default egress interface, and the corresponding address of
316+
* that interface will be returned. If all attempts are unsuccessful,
317+
* null will be returned.
318+
*/
319+
public static EthernetAddress fromEgressInterface()
320+
{
321+
String roots = "abcdefghijklm";
322+
int index = new Random().nextInt(roots.length());
323+
String name = roots.charAt(index) + ".root-servers.net";
324+
InetSocketAddress externalAddress = new InetSocketAddress(name, 0);
325+
if (externalAddress.isUnresolved()) {
326+
externalAddress = new InetSocketAddress("1.1.1.1", 0);
327+
}
328+
EthernetAddress ifAddr = fromEgressInterface(externalAddress);
329+
if (ifAddr == null) {
330+
return fromEgressInterface(new InetSocketAddress("1::1", 0));
331+
} else {
332+
return ifAddr;
333+
}
334+
}
335+
336+
/**
337+
* A factory method to return the address of the interface used to route
338+
* traffic to the specified IP address.
339+
*/
340+
public static EthernetAddress fromEgressInterface(InetSocketAddress externalSocketAddress)
341+
{
342+
DatagramSocket socket = null;
343+
try {
344+
socket = new DatagramSocket();
345+
socket.connect(externalSocketAddress);
346+
InetAddress localAddress = socket.getLocalAddress();
347+
NetworkInterface egressIf = NetworkInterface.getByInetAddress(localAddress);
348+
return fromInterface(egressIf);
349+
} catch (SocketException e) {
350+
return null;
351+
} finally {
352+
if (socket != null) {
353+
socket.close();
354+
}
355+
}
356+
}
357+
286358
/**
287359
* Factory method that can be used to construct a random multicast
288360
* address; to be used in cases where there is no "real" ethernet

src/main/java/com/fasterxml/uuid/Generators.java

+28
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public class Generators
4040
* synchronization but no external file-based syncing.
4141
*/
4242
protected static UUIDTimer _sharedTimer;
43+
44+
/**
45+
* The hardware address of the egress network interface.
46+
*/
47+
protected static EthernetAddress _egressIfAddr = null;
4348

4449
// // Random-based generation
4550

@@ -116,6 +121,21 @@ public static NameBasedGenerator nameBasedGenerator(UUID namespace, MessageDiges
116121

117122
// // Time+location-based generation
118123

124+
/**
125+
* Factory method for constructing UUID generator that generates UUID using variant 1
126+
* (time+location based). This method will use the ethernet address of the interface
127+
* that routes to the default gateway. For most simple and common networking configurations
128+
* this will be the most appropriate address to use. The default interface is determined
129+
* by the calling {@link EthernetAddress#fromEgressInterface()}. Note that this will only
130+
* identify the egress interface once: if you have a complex network setup where your
131+
* outbound routes/interfaces may change dynamically, and you want your UUIDs to
132+
* accurately reflect which interface is being actively used, this method is not for you.
133+
*/
134+
public static TimeBasedGenerator egressTimeBasedGenerator()
135+
{
136+
return timeBasedGenerator(egressInterfaceAddress());
137+
}
138+
119139
/**
120140
* Factory method for constructing UUID generator that generates UUID using
121141
* variant 1 (time+location based).
@@ -238,4 +258,12 @@ private static synchronized UUIDTimer sharedTimer()
238258
}
239259
return _sharedTimer;
240260
}
261+
262+
private static synchronized EthernetAddress egressInterfaceAddress()
263+
{
264+
if (_egressIfAddr == null) {
265+
_egressIfAddr = EthernetAddress.fromEgressInterface();
266+
}
267+
return _egressIfAddr;
268+
}
241269
}

src/test/java/com/fasterxml/uuid/EthernetAddressTest.java

+26
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
package com.fasterxml.uuid;
1919

20+
import com.fasterxml.uuid.impl.TimeBasedGenerator;
21+
import java.net.InetSocketAddress;
2022
import junit.framework.Test;
2123
import junit.framework.TestCase;
2224
import junit.framework.TestSuite;
@@ -1307,6 +1309,30 @@ public void testFromInterface() throws Exception
13071309
assertNotNull(addr.toString());
13081310
}
13091311

1312+
public void testFromEgressInterfaceRoot() throws Exception
1313+
{
1314+
InetSocketAddress extAddr = new InetSocketAddress("a.root-servers.net", 0);
1315+
EthernetAddress ifAddr = EthernetAddress.fromEgressInterface(extAddr);
1316+
assertNotNull(ifAddr);
1317+
assertNotNull(ifAddr.toString());
1318+
}
1319+
1320+
public void testFromEgressInterface() throws Exception
1321+
{
1322+
EthernetAddress ifAddr = EthernetAddress.fromEgressInterface();
1323+
assertNotNull(ifAddr);
1324+
assertNotNull(ifAddr.toString());
1325+
}
1326+
1327+
public void testDefaultTimeBasedGenerator()
1328+
{
1329+
TimeBasedGenerator generator = Generators.egressTimeBasedGenerator();
1330+
assertNotNull(generator);
1331+
EthernetAddress ifAddr = generator.getEthernetAddress();
1332+
assertNotNull(ifAddr);
1333+
assertNotNull(ifAddr.toString());
1334+
}
1335+
13101336
public void testBogus() throws Exception
13111337
{
13121338
// First, two using pseudo-random; verify they are different

0 commit comments

Comments
 (0)