diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/LanTcpPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/LanTcpPlugin.java index 7d26b8e5e06999e1b7b48561e1f039dd9e4a3991..37c5935f949f4199b5e977728ca4cb8492171a2e 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/LanTcpPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tcp/LanTcpPlugin.java @@ -23,7 +23,7 @@ import java.net.SocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; -import java.util.LinkedList; +import java.util.Comparator; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.Executor; @@ -44,6 +44,9 @@ class LanTcpPlugin extends TcpPlugin { private static final Logger LOG = Logger.getLogger(LanTcpPlugin.class.getName()); + private static final LanAddressComparator ADDRESS_COMPARATOR = + new LanAddressComparator(); + private static final int MAX_ADDRESSES = 4; private static final String SEPARATOR = ","; @@ -63,19 +66,18 @@ class LanTcpPlugin extends TcpPlugin { TransportProperties p = callback.getLocalProperties(); String oldIpPorts = p.get(PROP_IP_PORTS); List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts); - List<InetSocketAddress> locals = new LinkedList<>(); + List<InetSocketAddress> locals = new ArrayList<>(); for (InetAddress local : getLocalIpAddresses()) { if (isAcceptableAddress(local)) { // If this is the old address, try to use the same port for (InetSocketAddress old : olds) { - if (old.getAddress().equals(local)) { - int port = old.getPort(); - locals.add(0, new InetSocketAddress(local, port)); - } + if (old.getAddress().equals(local)) + locals.add(new InetSocketAddress(local, old.getPort())); } locals.add(new InetSocketAddress(local, 0)); } } + Collections.sort(locals, ADDRESS_COMPARATOR); return locals; } @@ -153,17 +155,39 @@ class LanTcpPlugin extends TcpPlugin { // Package access for testing boolean addressesAreOnSameLan(byte[] localIp, byte[] remoteIp) { // 10.0.0.0/8 - if (localIp[0] == 10) return remoteIp[0] == 10; + if (isPrefix10(localIp)) return isPrefix10(remoteIp); // 172.16.0.0/12 - if (localIp[0] == (byte) 172 && (localIp[1] & 0xF0) == 16) - return remoteIp[0] == (byte) 172 && (remoteIp[1] & 0xF0) == 16; + if (isPrefix172(localIp)) return isPrefix172(remoteIp); // 192.168.0.0/16 - if (localIp[0] == (byte) 192 && localIp[1] == (byte) 168) - return remoteIp[0] == (byte) 192 && remoteIp[1] == (byte) 168; + if (isPrefix192(localIp)) return isPrefix192(remoteIp); // Unrecognised prefix - may be compatible return true; } + private static boolean isPrefix10(byte[] ipv4) { + return ipv4[0] == 10; + } + + private static boolean isPrefix172(byte[] ipv4) { + return ipv4[0] == (byte) 172 && (ipv4[1] & 0xF0) == 16; + } + + private static boolean isPrefix192(byte[] ipv4) { + return ipv4[0] == (byte) 192 && ipv4[1] == (byte) 168; + } + + // Returns the prefix length for an RFC 1918 address, or 0 for any other + // address + private static int getRfc1918PrefixLength(InetAddress addr) { + if (!(addr instanceof Inet4Address)) return 0; + if (!addr.isSiteLocalAddress()) return 0; + byte[] ipv4 = addr.getAddress(); + if (isPrefix10(ipv4)) return 8; + if (isPrefix172(ipv4)) return 12; + if (isPrefix192(ipv4)) return 16; + return 0; + } + @Override public boolean supportsKeyAgreement() { return true; @@ -278,4 +302,19 @@ class LanTcpPlugin extends TcpPlugin { } } } + + static class LanAddressComparator implements Comparator<InetSocketAddress> { + + @Override + public int compare(InetSocketAddress a, InetSocketAddress b) { + // Prefer addresses with non-zero ports + int aPort = a.getPort(), bPort = b.getPort(); + if (aPort > 0 && bPort == 0) return -1; + if (aPort == 0 && bPort > 0) return 1; + // Prefer addresses with longer RFC 1918 prefixes + int aPrefix = getRfc1918PrefixLength(a.getAddress()); + int bPrefix = getRfc1918PrefixLength(b.getAddress()); + return bPrefix - aPrefix; + } + } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginTest.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginTest.java index d6ea0b6390289463e1049bc3fff8acc3f3f7c934..ede3df810f2c646728e9f23715e5d53723d5e7b5 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/tcp/LanTcpPluginTest.java @@ -11,6 +11,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.settings.Settings; +import org.briarproject.bramble.plugin.tcp.LanTcpPlugin.LanAddressComparator; import org.briarproject.bramble.test.BrambleTestCase; import org.junit.Test; @@ -22,6 +23,7 @@ import java.net.NetworkInterface; import java.net.ServerSocket; import java.net.Socket; import java.util.Collections; +import java.util.Comparator; import java.util.Hashtable; import java.util.Map; import java.util.concurrent.Callable; @@ -56,6 +58,9 @@ public class LanTcpPluginTest extends BrambleTestCase { // Local and remote in 192.168.0.0/16 should return true assertTrue(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0), makeAddress(192, 168, 255, 255))); + // Local and remote in 169.254.0.0/16 (link-local) should return true + assertTrue(plugin.addressesAreOnSameLan(makeAddress(169, 254, 0, 0), + makeAddress(169, 254, 255, 255))); // Local and remote in different recognised prefixes should return false assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0), makeAddress(172, 31, 255, 255))); @@ -269,6 +274,57 @@ public class LanTcpPluginTest extends BrambleTestCase { plugin.stop(); } + @Test + public void testComparatorPrefersNonZeroPorts() throws Exception { + Comparator<InetSocketAddress> comparator = new LanAddressComparator(); + InetSocketAddress nonZero = new InetSocketAddress("1.2.3.4", 1234); + InetSocketAddress zero = new InetSocketAddress("1.2.3.4", 0); + + assertEquals(0, comparator.compare(nonZero, nonZero)); + assertTrue(comparator.compare(nonZero, zero) < 0); + + assertTrue(comparator.compare(zero, nonZero) > 0); + assertEquals(0, comparator.compare(zero, zero)); + } + + @Test + public void testComparatorPrefersLongerPrefixes() throws Exception { + Comparator<InetSocketAddress> comparator = new LanAddressComparator(); + InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0); + InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0); + InetSocketAddress prefix10 = new InetSocketAddress("10.0.0.1", 0); + + assertEquals(0, comparator.compare(prefix192, prefix192)); + assertTrue(comparator.compare(prefix192, prefix172) < 0); + assertTrue(comparator.compare(prefix192, prefix10) < 0); + + assertTrue(comparator.compare(prefix172, prefix192) > 0); + assertEquals(0, comparator.compare(prefix172, prefix172)); + assertTrue(comparator.compare(prefix172, prefix10) < 0); + + assertTrue(comparator.compare(prefix10, prefix192) > 0); + assertTrue(comparator.compare(prefix10, prefix172) > 0); + assertEquals(0, comparator.compare(prefix10, prefix10)); + } + + @Test + public void testComparatorPrefersSiteLocalToLinkLocal() throws Exception { + Comparator<InetSocketAddress> comparator = new LanAddressComparator(); + InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0); + InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0); + InetSocketAddress prefix10 = new InetSocketAddress("10.0.0.1", 0); + InetSocketAddress linkLocal = new InetSocketAddress("169.254.0.1", 0); + + assertTrue(comparator.compare(prefix192, linkLocal) < 0); + assertTrue(comparator.compare(prefix172, linkLocal) < 0); + assertTrue(comparator.compare(prefix10, linkLocal) < 0); + + assertTrue(comparator.compare(linkLocal, prefix192) > 0); + assertTrue(comparator.compare(linkLocal, prefix172) > 0); + assertTrue(comparator.compare(linkLocal, prefix10) > 0); + assertEquals(0, comparator.compare(linkLocal, linkLocal)); + } + private boolean systemHasLocalIpv4Address() throws Exception { for (NetworkInterface i : Collections.list( NetworkInterface.getNetworkInterfaces())) {