diff --git a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java index 8a75a971e6495b1f470a0cee40aa3fced823f327..3429d06b82922618a7c037c66e4b7f15cf69a5ae 100644 --- a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java +++ b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java @@ -1,14 +1,19 @@ package org.briarproject.plugins.tcp; import org.briarproject.api.TransportId; +import org.briarproject.api.contact.ContactId; import org.briarproject.api.plugins.Backoff; import org.briarproject.api.plugins.duplex.DuplexPluginCallback; import org.briarproject.api.properties.TransportProperties; +import org.briarproject.api.settings.Settings; +import org.briarproject.util.StringUtils; import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Executor; @@ -17,6 +22,10 @@ class LanTcpPlugin extends TcpPlugin { static final TransportId ID = new TransportId("lan"); + private static final int MAX_ADDRESSES = 5; + private static final String PROP_IP_PORTS = "ipPorts"; + private static final String SEPARATOR = ","; + LanTcpPlugin(Executor ioExecutor, Backoff backoff, DuplexPluginCallback callback, int maxLatency, int maxIdleTime) { super(ioExecutor, backoff, callback, maxLatency, maxIdleTime); @@ -30,18 +39,74 @@ class LanTcpPlugin extends TcpPlugin { protected List<SocketAddress> getLocalSocketAddresses() { // Use the same address and port as last time if available TransportProperties p = callback.getLocalProperties(); - String oldAddress = p.get("address"), oldPort = p.get("port"); - InetSocketAddress old = parseSocketAddress(oldAddress, oldPort); - List<SocketAddress> addrs = new LinkedList<SocketAddress>(); - for (InetAddress a : getLocalIpAddresses()) { - if (isAcceptableAddress(a)) { + String oldIpPorts = p.get(PROP_IP_PORTS); + List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts); + List<SocketAddress> locals = new LinkedList<SocketAddress>(); + for (InetAddress local : getLocalIpAddresses()) { + if (isAcceptableAddress(local)) { // If this is the old address, try to use the same port - if (old != null && old.getAddress().equals(a)) - addrs.add(0, new InetSocketAddress(a, old.getPort())); - addrs.add(new InetSocketAddress(a, 0)); + for (InetSocketAddress old : olds) { + if (old.getAddress().equals(local)) { + int port = old.getPort(); + locals.add(0, new InetSocketAddress(local, port)); + } + } + locals.add(new InetSocketAddress(local, 0)); } } - return addrs; + return locals; + } + + private List<InetSocketAddress> parseSocketAddresses(String ipPorts) { + if (StringUtils.isNullOrEmpty(ipPorts)) return Collections.emptyList(); + String[] split = ipPorts.split(SEPARATOR); + List<InetSocketAddress> remotes = new ArrayList<InetSocketAddress>(); + for (String ipPort : split) { + InetSocketAddress a = parseSocketAddress(ipPort); + if (a != null) remotes.add(a); + } + return remotes; + } + + @Override + protected void setLocalSocketAddress(InetSocketAddress a) { + String ipPort = getIpPortString(a); + // Get the list of recently used addresses + String setting = callback.getSettings().get(PROP_IP_PORTS); + List<String> recent = new ArrayList<String>(); + if (!StringUtils.isNullOrEmpty(setting)) + Collections.addAll(recent, setting.split(SEPARATOR)); + // Is the address already in the list? + if (recent.remove(ipPort)) { + // Move the address to the start of the list + recent.add(0, ipPort); + setting = StringUtils.join(recent, SEPARATOR); + } else { + // Add the address to the start of the list + recent.add(0, ipPort); + // Drop the least recently used address if the list is full + if (recent.size() > MAX_ADDRESSES) + recent = recent.subList(0, MAX_ADDRESSES); + setting = StringUtils.join(recent, SEPARATOR); + // Update the list of addresses shared with contacts + List<String> shared = new ArrayList<String>(recent); + Collections.sort(shared); + String property = StringUtils.join(shared, SEPARATOR); + TransportProperties properties = new TransportProperties(); + properties.put(PROP_IP_PORTS, property); + callback.mergeLocalProperties(properties); + } + // Save the setting + Settings settings = new Settings(); + settings.put(PROP_IP_PORTS, setting); + callback.mergeSettings(settings); + } + + @Override + protected List<InetSocketAddress> getRemoteSocketAddresses(ContactId c) { + TransportProperties p = callback.getRemoteProperties().get(c); + if (p == null) return Collections.emptyList(); + return parseSocketAddresses(p.get(PROP_IP_PORTS)); } private boolean isAcceptableAddress(InetAddress a) { diff --git a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java index d6effe2a722930d94a5a6dac0022e02005ecb30c..ead2c80b89680017659d240bd2224cab5683c214 100644 --- a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java +++ b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java @@ -8,7 +8,6 @@ import org.briarproject.api.plugins.Backoff; import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPluginCallback; import org.briarproject.api.plugins.duplex.DuplexTransportConnection; -import org.briarproject.api.properties.TransportProperties; import org.briarproject.util.StringUtils; import java.io.IOException; @@ -52,7 +51,22 @@ abstract class TcpPlugin implements DuplexPlugin { */ protected abstract List<SocketAddress> getLocalSocketAddresses(); - /** Returns true if connections to the given address can be attempted. */ + /** + * Adds the address on which the plugin is listening to the transport + * properties. + */ + protected abstract void setLocalSocketAddress(InetSocketAddress a); + + /** + * Returns zero or more socket addresses for connecting to the given + * contact. + */ + protected abstract List<InetSocketAddress> getRemoteSocketAddresses( + ContactId c); + + /** + * Returns true if connections to the given address can be attempted. + */ protected abstract boolean isConnectable(InetSocketAddress remote); protected TcpPlugin(Executor ioExecutor, Backoff backoff, @@ -126,17 +140,11 @@ abstract class TcpPlugin implements DuplexPlugin { } } - protected String getHostAddress(InetAddress a) { - String addr = a.getHostAddress(); + protected String getIpPortString(InetSocketAddress a) { + String addr = a.getAddress().getHostAddress(); int percent = addr.indexOf('%'); - return percent == -1 ? addr : addr.substring(0, percent); - } - - protected void setLocalSocketAddress(InetSocketAddress a) { - TransportProperties p = new TransportProperties(); - p.put("address", getHostAddress(a.getAddress())); - p.put("port", String.valueOf(a.getPort())); - callback.mergeLocalProperties(p); + if (percent != -1) addr = addr.substring(0, percent); + return addr + ":" + a.getPort(); } private void acceptContactConnections() { @@ -197,37 +205,34 @@ abstract class TcpPlugin implements DuplexPlugin { public DuplexTransportConnection createConnection(ContactId c) { if (!isRunning()) return null; - InetSocketAddress remote = getRemoteSocketAddress(c); - if (remote == null) return null; - if (!isConnectable(remote)) { - if (LOG.isLoggable(INFO)) { - SocketAddress local = socket.getLocalSocketAddress(); - LOG.info(remote + " is not connectable from " + local); + for (InetSocketAddress remote : getRemoteSocketAddresses(c)) { + if (!isConnectable(remote)) { + if (LOG.isLoggable(INFO)) { + SocketAddress local = socket.getLocalSocketAddress(); + LOG.info(remote + " is not connectable from " + local); + } + continue; + } + Socket s = new Socket(); + try { + if (LOG.isLoggable(INFO)) LOG.info("Connecting to " + remote); + s.connect(remote); + s.setSoTimeout(socketTimeout); + if (LOG.isLoggable(INFO)) LOG.info("Connected to " + remote); + return new TcpTransportConnection(this, s); + } catch (IOException e) { + if (LOG.isLoggable(INFO)) + LOG.info("Could not connect to " + remote); } - return null; - } - Socket s = new Socket(); - try { - if (LOG.isLoggable(INFO)) LOG.info("Connecting to " + remote); - s.connect(remote); - s.setSoTimeout(socketTimeout); - if (LOG.isLoggable(INFO)) LOG.info("Connected to " + remote); - return new TcpTransportConnection(this, s); - } catch (IOException e) { - if (LOG.isLoggable(INFO)) LOG.info("Could not connect to " + remote); - return null; } + return null; } - private InetSocketAddress getRemoteSocketAddress(ContactId c) { - TransportProperties p = callback.getRemoteProperties().get(c); - if (p == null) return null; - return parseSocketAddress(p.get("address"), p.get("port")); - } - - protected InetSocketAddress parseSocketAddress(String addr, String port) { - if (StringUtils.isNullOrEmpty(addr)) return null; - if (StringUtils.isNullOrEmpty(port)) return null; + protected InetSocketAddress parseSocketAddress(String ipPort) { + if (StringUtils.isNullOrEmpty(ipPort)) return null; + String[] split = ipPort.split(":"); + if (split.length != 2) return null; + String addr = split[0], port = split[1]; // Ensure getByName() won't perform a DNS lookup if (!DOTTED_QUAD.matcher(addr).matches()) return null; try { @@ -235,10 +240,12 @@ abstract class TcpPlugin implements DuplexPlugin { int p = Integer.parseInt(port); return new InetSocketAddress(a, p); } catch (UnknownHostException e) { - if (LOG.isLoggable(WARNING)) LOG.warning("Invalid address: " + addr); + if (LOG.isLoggable(WARNING)) + LOG.warning("Invalid address: " + addr); return null; } catch (NumberFormatException e) { - if (LOG.isLoggable(WARNING)) LOG.warning("Invalid port: " + port); + if (LOG.isLoggable(WARNING)) + LOG.warning("Invalid port: " + port); return null; } } diff --git a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java index beabc8bec54f71f6814a4077cc30e6bfd20dc67e..4274a8cbe4aa7e486ee99eea00aca5b69d7d1059 100644 --- a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java +++ b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java @@ -1,6 +1,7 @@ package org.briarproject.plugins.tcp; import org.briarproject.api.TransportId; +import org.briarproject.api.contact.ContactId; import org.briarproject.api.plugins.Backoff; import org.briarproject.api.plugins.duplex.DuplexPluginCallback; import org.briarproject.api.properties.TransportProperties; @@ -9,6 +10,7 @@ import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Executor; @@ -17,6 +19,8 @@ class WanTcpPlugin extends TcpPlugin { static final TransportId ID = new TransportId("wan"); + private static final String PROP_IP_PORT = "ipPort"; + private final PortMapper portMapper; private volatile MappingResult mappingResult; @@ -35,8 +39,7 @@ class WanTcpPlugin extends TcpPlugin { protected List<SocketAddress> getLocalSocketAddresses() { // Use the same address and port as last time if available TransportProperties p = callback.getLocalProperties(); - String oldAddress = p.get("address"), oldPort = p.get("port"); - InetSocketAddress old = parseSocketAddress(oldAddress, oldPort); + InetSocketAddress old = parseSocketAddress(p.get(PROP_IP_PORT)); List<SocketAddress> addrs = new LinkedList<SocketAddress>(); for (InetAddress a : getLocalIpAddresses()) { if (isAcceptableAddress(a)) { @@ -69,6 +72,15 @@ class WanTcpPlugin extends TcpPlugin { return 32768 + (int) (Math.random() * 32768); } + @Override + protected List<InetSocketAddress> getRemoteSocketAddresses(ContactId c) { + TransportProperties p = callback.getRemoteProperties().get(c); + if (p == null) return Collections.emptyList(); + InetSocketAddress parsed = parseSocketAddress(p.get(PROP_IP_PORT)); + if (parsed == null) return Collections.emptyList(); + return Collections.singletonList(parsed); + } + @Override protected boolean isConnectable(InetSocketAddress remote) { if (remote.getPort() == 0) return false; @@ -83,8 +95,7 @@ class WanTcpPlugin extends TcpPlugin { a = mappingResult.getExternal(); } TransportProperties p = new TransportProperties(); - p.put("address", getHostAddress(a.getAddress())); - p.put("port", String.valueOf(a.getPort())); + p.put(PROP_IP_PORT, getIpPortString(a)); callback.mergeLocalProperties(p); } } diff --git a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java index 1554a0832e6a66fc75130acc11035c3753ecefc5..de5f3ce81b395467ceea89662151086e87467574 100644 --- a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java +++ b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java @@ -26,6 +26,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -91,14 +92,17 @@ public class LanTcpPluginTest extends BriarTestCase { plugin.start(); // The plugin should have bound a socket and stored the port number assertTrue(callback.propertiesLatch.await(5, SECONDS)); - String addrString = callback.local.get("address"); - assertNotNull(addrString); + String ipPorts = callback.local.get("ipPorts"); + assertNotNull(ipPorts); + String[] split = ipPorts.split(","); + assertEquals(1, split.length); + split = split[0].split(":"); + assertEquals(2, split.length); + String addrString = split[0], portString = split[1]; InetAddress addr = InetAddress.getByName(addrString); assertTrue(addr instanceof Inet4Address); assertFalse(addr.isLoopbackAddress()); assertTrue(addr.isLinkLocalAddress() || addr.isSiteLocalAddress()); - String portString = callback.local.get("port"); - assertNotNull(portString); int port = Integer.parseInt(portString); assertTrue(port > 0 && port < 65536); // The plugin should be listening on the port @@ -124,11 +128,17 @@ public class LanTcpPluginTest extends BriarTestCase { plugin.start(); // The plugin should have bound a socket and stored the port number assertTrue(callback.propertiesLatch.await(5, SECONDS)); - String addr = callback.local.get("address"); - assertNotNull(addr); + assertTrue(callback.propertiesLatch.await(5, SECONDS)); + String ipPorts = callback.local.get("ipPorts"); + assertNotNull(ipPorts); + String[] split = ipPorts.split(","); + assertEquals(1, split.length); + split = split[0].split(":"); + assertEquals(2, split.length); + String addrString = split[0]; // Listen on the same interface as the plugin final ServerSocket ss = new ServerSocket(); - ss.bind(new InetSocketAddress(addr, 0), 10); + ss.bind(new InetSocketAddress(addrString, 0), 10); int port = ss.getLocalPort(); final CountDownLatch latch = new CountDownLatch(1); final AtomicBoolean error = new AtomicBoolean(false); @@ -145,8 +155,7 @@ public class LanTcpPluginTest extends BriarTestCase { }.start(); // Tell the plugin about the port TransportProperties p = new TransportProperties(); - p.put("address", addr); - p.put("port", String.valueOf(port)); + p.put("ipPorts", addrString + ":" + port); callback.remote.put(contactId, p); // Connect to the port DuplexTransportConnection d = plugin.createConnection(contactId); @@ -175,7 +184,7 @@ public class LanTcpPluginTest extends BriarTestCase { private static class Callback implements DuplexPluginCallback { private final Map<ContactId, TransportProperties> remote = - new Hashtable<ContactId, TransportProperties>(); + new Hashtable<>(); private final CountDownLatch propertiesLatch = new CountDownLatch(1); private final CountDownLatch connectionsLatch = new CountDownLatch(1); private final TransportProperties local = new TransportProperties(); @@ -192,7 +201,8 @@ public class LanTcpPluginTest extends BriarTestCase { return remote; } - public void mergeSettings(Settings s) {} + public void mergeSettings(Settings s) { + } public void mergeLocalProperties(TransportProperties p) { local.putAll(p); @@ -207,18 +217,22 @@ public class LanTcpPluginTest extends BriarTestCase { return false; } - public void showMessage(String... message) {} + public void showMessage(String... message) { + } public void incomingConnectionCreated(DuplexTransportConnection d) { connectionsLatch.countDown(); } public void outgoingConnectionCreated(ContactId c, - DuplexTransportConnection d) {} + DuplexTransportConnection d) { + } - public void transportEnabled() {} + public void transportEnabled() { + } - public void transportDisabled() {} + public void transportDisabled() { + } } private static class TestBackoff implements Backoff { @@ -227,8 +241,10 @@ public class LanTcpPluginTest extends BriarTestCase { return 60 * 1000; } - public void increment() {} + public void increment() { + } - public void reset() {} + public void reset() { + } } }