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())) {