diff --git a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java index bb2a4de7faee44f5d4c5bde20949d8290a32273a..499eb9d9b71a15e337f07c09d300c35bdf7a4ed2 100644 --- a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java +++ b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java @@ -2,28 +2,44 @@ package org.briarproject.plugins.tcp; import org.briarproject.api.TransportId; import org.briarproject.api.contact.ContactId; +import org.briarproject.api.keyagreement.KeyAgreementConnection; +import org.briarproject.api.keyagreement.KeyAgreementListener; +import org.briarproject.api.keyagreement.TransportDescriptor; import org.briarproject.api.plugins.Backoff; import org.briarproject.api.plugins.duplex.DuplexPluginCallback; +import org.briarproject.api.plugins.duplex.DuplexTransportConnection; import org.briarproject.api.properties.TransportProperties; import org.briarproject.api.settings.Settings; import org.briarproject.util.StringUtils; +import java.io.IOException; import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; import java.net.SocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.Callable; import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; class LanTcpPlugin extends TcpPlugin { static final TransportId ID = new TransportId("lan"); + private static final Logger LOG = + Logger.getLogger(LanTcpPlugin.class.getName()); + private static final int MAX_ADDRESSES = 5; private static final String PROP_IP_PORTS = "ipPorts"; + private static final String PROP_IP_PORT = "ipPort"; private static final String SEPARATOR = ","; LanTcpPlugin(Executor ioExecutor, Backoff backoff, @@ -143,4 +159,100 @@ class LanTcpPlugin extends TcpPlugin { // Unrecognised prefix - may be compatible return true; } + + @Override + public boolean supportsKeyAgreement() { + return true; + } + + @Override + public KeyAgreementListener createKeyAgreementListener(byte[] commitment) { + ServerSocket ss = null; + for (SocketAddress addr : getLocalSocketAddresses()) { + try { + ss = new ServerSocket(); + ss.bind(addr); + break; + } catch (IOException e) { + if (LOG.isLoggable(INFO)) + LOG.info("Failed to bind " + addr); + tryToClose(ss); + } + } + if (ss == null || !ss.isBound()) { + LOG.info("Could not bind server socket for key agreement"); + return null; + } + TransportProperties p = new TransportProperties(); + SocketAddress local = ss.getLocalSocketAddress(); + p.put(PROP_IP_PORT, getIpPortString((InetSocketAddress) local)); + TransportDescriptor d = new TransportDescriptor(ID, p); + return new LanKeyAgreementListener(d, ss); + } + + @Override + public DuplexTransportConnection createKeyAgreementConnection( + byte[] commitment, TransportDescriptor d, long timeout) { + if (!isRunning()) return null; + if (!ID.equals(d.getIdentifier())) return null; + TransportProperties p = d.getProperties(); + if (p == null) return null; + String ipPort = p.get(PROP_IP_PORT); + InetSocketAddress remote = parseSocketAddress(ipPort); + if (remote == null) return null; + if (!isConnectable(remote)) { + if (LOG.isLoggable(INFO)) { + SocketAddress local = socket.getLocalSocketAddress(); + LOG.info(remote + " is not connectable from " + local); + } + 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; + } + } + + private class LanKeyAgreementListener extends KeyAgreementListener { + + private final ServerSocket ss; + + public LanKeyAgreementListener(TransportDescriptor descriptor, + ServerSocket ss) { + super(descriptor); + this.ss = ss; + } + + @Override + public Callable<KeyAgreementConnection> listen() { + return new Callable<KeyAgreementConnection>() { + @Override + public KeyAgreementConnection call() throws IOException { + Socket s = ss.accept(); + if (LOG.isLoggable(INFO)) + LOG.info(ID.getString() + ": Incoming connection"); + return new KeyAgreementConnection( + new TcpTransportConnection(LanTcpPlugin.this, s), + ID); + } + }; + } + + @Override + public void close() { + try { + ss.close(); + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + } } diff --git a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java index de5f3ce81b395467ceea89662151086e87467574..622bb59aa0d7e6dec97374ad96eae4912219414f 100644 --- a/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java +++ b/briar-tests/src/org/briarproject/plugins/tcp/LanTcpPluginTest.java @@ -2,6 +2,9 @@ package org.briarproject.plugins.tcp; import org.briarproject.BriarTestCase; import org.briarproject.api.contact.ContactId; +import org.briarproject.api.keyagreement.KeyAgreementConnection; +import org.briarproject.api.keyagreement.KeyAgreementListener; +import org.briarproject.api.keyagreement.TransportDescriptor; import org.briarproject.api.plugins.Backoff; import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPluginCallback; @@ -20,9 +23,12 @@ import java.net.Socket; import java.util.Collections; import java.util.Hashtable; import java.util.Map; +import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.AtomicBoolean; import static java.util.concurrent.TimeUnit.SECONDS; @@ -170,6 +176,102 @@ public class LanTcpPluginTest extends BriarTestCase { plugin.stop(); } + @Test + public void testIncomingKeyAgreementConnection() throws Exception { + if (!systemHasLocalIpv4Address()) { + System.err.println("WARNING: Skipping test, no local IPv4 address"); + return; + } + Callback callback = new Callback(); + Executor executor = Executors.newCachedThreadPool(); + DuplexPlugin plugin = new LanTcpPlugin(executor, backoff, callback, + 0, 0); + plugin.start(); + assertTrue(callback.propertiesLatch.await(5, SECONDS)); + KeyAgreementListener kal = plugin.createKeyAgreementListener(null); + Callable<KeyAgreementConnection> c = kal.listen(); + FutureTask<KeyAgreementConnection> f = new FutureTask<>(c); + new Thread(f).start(); + // The plugin should have bound a socket and stored the port number + TransportDescriptor d = kal.getDescriptor(); + TransportProperties p = d.getProperties(); + String ipPort = p.get("ipPort"); + assertNotNull(ipPort); + String[] split = ipPort.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()); + int port = Integer.parseInt(portString); + assertTrue(port > 0 && port < 65536); + // The plugin should be listening on the port + InetSocketAddress socketAddr = new InetSocketAddress(addr, port); + Socket s = new Socket(); + s.connect(socketAddr, 100); + assertNotNull(f.get(5, SECONDS)); + s.close(); + kal.close(); + // Stop the plugin + plugin.stop(); + } + + @Test + public void testOutgoingKeyAgreementConnection() throws Exception { + if (!systemHasLocalIpv4Address()) { + System.err.println("WARNING: Skipping test, no local IPv4 address"); + return; + } + Callback callback = new Callback(); + Executor executor = Executors.newCachedThreadPool(); + DuplexPlugin plugin = new LanTcpPlugin(executor, backoff, callback, + 0, 0); + plugin.start(); + // The plugin should have bound a socket and stored the port number + 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(addrString, 0), 10); + int port = ss.getLocalPort(); + final CountDownLatch latch = new CountDownLatch(1); + final AtomicBoolean error = new AtomicBoolean(false); + new Thread() { + @Override + public void run() { + try { + ss.accept(); + latch.countDown(); + } catch (IOException e) { + error.set(true); + } + } + }.start(); + // Tell the plugin about the port + TransportProperties p = new TransportProperties(); + p.put("ipPort", addrString + ":" + port); + TransportDescriptor desc = new TransportDescriptor(plugin.getId(), p); + // Connect to the port + DuplexTransportConnection d = + plugin.createKeyAgreementConnection(null, desc, 5000); + assertNotNull(d); + // Check that the connection was accepted + assertTrue(latch.await(5, SECONDS)); + assertFalse(error.get()); + // Clean up + d.getReader().dispose(false, true); + d.getWriter().dispose(false); + ss.close(); + plugin.stop(); + } + private boolean systemHasLocalIpv4Address() throws Exception { for (NetworkInterface i : Collections.list( NetworkInterface.getNetworkInterfaces())) {