Skip to content

Add Generators.egressTimeBasedGenerator() method that constructs TimedBasedGenerator with a sensible choice of interface #52

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
80 changes: 76 additions & 4 deletions src/main/java/com/fasterxml/uuid/EthernetAddress.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -271,9 +275,9 @@ 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);
EthernetAddress addr = fromInterface(nint);
if (addr != null) {
return addr;
}
}
}
Expand All @@ -282,7 +286,75 @@ 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)
{
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
}
}
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 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
* 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 IP 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
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/com/fasterxml/uuid/Generators.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public class Generators
* synchronization but no external file-based syncing.
*/
protected static UUIDTimer _sharedTimer;

/**
* The hardware address of the egress network interface.
*/
protected static EthernetAddress _egressIfAddr = null;

// // Random-based generation

Expand Down Expand Up @@ -115,6 +120,21 @@ 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()}. 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()
{
return timeBasedGenerator(egressInterfaceAddress());
}

/**
* Factory method for constructing UUID generator that generates UUID using
* variant 1 (time+location based).
Expand Down Expand Up @@ -195,4 +215,12 @@ private static synchronized UUIDTimer sharedTimer()
}
return _sharedTimer;
}

private static synchronized EthernetAddress egressInterfaceAddress()
{
if (_egressIfAddr == null) {
_egressIfAddr = EthernetAddress.fromEgressInterface();
}
return _egressIfAddr;
}
}
28 changes: 26 additions & 2 deletions src/test/java/com/fasterxml/uuid/EthernetAddressTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
*
Expand Down Expand Up @@ -1309,6 +1309,30 @@ 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 testFromEgressInterface() throws Exception
{
EthernetAddress ifAddr = EthernetAddress.fromEgressInterface();
assertNotNull(ifAddr);
assertNotNull(ifAddr.toString());
}

public void testDefaultTimeBasedGenerator()
{
TimeBasedGenerator generator = Generators.egressTimeBasedGenerator();
assertNotNull(generator);
EthernetAddress ifAddr = generator.getEthernetAddress();
assertNotNull(ifAddr);
assertNotNull(ifAddr.toString());
}

public void testBogus() throws Exception
{
// First, two using pseudo-random; verify they are different
Expand Down