diff --git a/src/net/sf/briar/api/plugins/PluginManager.java b/src/net/sf/briar/api/plugins/PluginManager.java
index 4d1a6897fa5ea710f484169639a1961de484b89d..f3f49ab716a5f9525111da1218bfe2e511e725b8 100644
--- a/src/net/sf/briar/api/plugins/PluginManager.java
+++ b/src/net/sf/briar/api/plugins/PluginManager.java
@@ -10,9 +10,9 @@ public interface PluginManager {
 	/**
 	 * Starts the plugins and returns the number of plugins successfully
 	 * started. This method must not be called until the database has been
-	 * opened.
+	 * opened. The appContext argument is null on non-Android platforms.
 	 */
-	int start(Context context);
+	int start(Context appContext);
 
 	/**
 	 * Stops the plugins and returns the number of plugins successfully stopped.
diff --git a/src/net/sf/briar/plugins/PluginManagerImpl.java b/src/net/sf/briar/plugins/PluginManagerImpl.java
index 80a795c5cb53eb96609228350bd4486ff52490e4..5c3d0dd562035ef18c6adb3888648f09eeac2d8e 100644
--- a/src/net/sf/briar/plugins/PluginManagerImpl.java
+++ b/src/net/sf/briar/plugins/PluginManagerImpl.java
@@ -50,7 +50,8 @@ class PluginManagerImpl implements PluginManager {
 
 	private static final String[] ANDROID_DUPLEX_FACTORIES = new String[] {
 		"net.sf.briar.plugins.droidtooth.DroidtoothPluginFactory",
-		"net.sf.briar.plugins.socket.SimpleSocketPluginFactory"
+		"net.sf.briar.plugins.tcp.LanTcpPluginFactory",
+		"net.sf.briar.plugins.tcp.WanTcpPluginFactory"
 	};
 
 	private static final String[] J2SE_SIMPLEX_FACTORIES = new String[] {
@@ -59,7 +60,8 @@ class PluginManagerImpl implements PluginManager {
 
 	private static final String[] J2SE_DUPLEX_FACTORIES = new String[] {
 		"net.sf.briar.plugins.bluetooth.BluetoothPluginFactory",
-		"net.sf.briar.plugins.socket.SimpleSocketPluginFactory",
+		"net.sf.briar.plugins.tcp.LanSocketPluginFactory",
+		"net.sf.briar.plugins.tcp.WanSocketPluginFactory",
 		"net.sf.briar.plugins.tor.TorPluginFactory"
 	};
 
diff --git a/src/net/sf/briar/plugins/socket/SimpleSocketPlugin.java b/src/net/sf/briar/plugins/socket/SimpleSocketPlugin.java
deleted file mode 100644
index 9f1072a2301ca7d30b7b96d9b4a5656b235a209a..0000000000000000000000000000000000000000
--- a/src/net/sf/briar/plugins/socket/SimpleSocketPlugin.java
+++ /dev/null
@@ -1,154 +0,0 @@
-package net.sf.briar.plugins.socket;
-
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.NetworkInterface;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.logging.Logger;
-
-import net.sf.briar.api.ContactId;
-import net.sf.briar.api.TransportProperties;
-import net.sf.briar.api.crypto.PseudoRandom;
-import net.sf.briar.api.plugins.PluginExecutor;
-import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
-import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
-import net.sf.briar.api.protocol.TransportId;
-import net.sf.briar.util.StringUtils;
-
-class SimpleSocketPlugin extends SocketPlugin {
-
-	public static final byte[] TRANSPORT_ID =
-			StringUtils.fromHexString("58c66d999e492b85065924acfd739d80"
-					+ "c65a62f87e5a4fc6c284f95908b9007d"
-					+ "512a93ebf89bf68f50a29e96eebf97b6");
-
-	private static final TransportId ID = new TransportId(TRANSPORT_ID);
-	private static final Logger LOG =
-			Logger.getLogger(SimpleSocketPlugin.class.getName());
-
-	SimpleSocketPlugin(@PluginExecutor Executor pluginExecutor,
-			DuplexPluginCallback callback, long pollingInterval) {
-		super(pluginExecutor, callback, pollingInterval);
-	}
-
-	public TransportId getId() {
-		return ID;
-	}
-
-	@Override
-	protected Socket createClientSocket() throws IOException {
-		assert running;
-		return new Socket();
-	}
-
-	@Override
-	protected ServerSocket createServerSocket() throws IOException {
-		assert running;
-		return new ServerSocket();
-	}
-
-	@Override
-	protected SocketAddress getLocalSocketAddress() {
-		SocketAddress addr = createSocketAddress(callback.getLocalProperties());
-		if(addr == null) {
-			try {
-				return new InetSocketAddress(chooseInterface(false), 0);
-			} catch(IOException e) {
-				if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
-			}
-		}
-		return addr;
-	}
-
-	protected InetAddress chooseInterface(boolean lan) throws IOException {
-		List<NetworkInterface> ifaces =
-				Collections.list(NetworkInterface.getNetworkInterfaces());
-		// Try to find an interface of the preferred type (LAN or WAN)
-		for(NetworkInterface iface : ifaces) {
-			for(InetAddress addr : Collections.list(iface.getInetAddresses())) {
-				if(!addr.isLoopbackAddress()) {
-					boolean link = addr.isLinkLocalAddress();
-					boolean site = addr.isSiteLocalAddress();
-					if(lan == (link || site)) {
-						if(LOG.isLoggable(INFO)) {
-							LOG.info("Choosing interface "
-									+ addr.getHostAddress());
-						}
-						return addr;
-					}
-				}
-			}
-		}
-		// Settle for an interface that's not of the preferred type
-		for(NetworkInterface iface : ifaces) {
-			for(InetAddress addr : Collections.list(iface.getInetAddresses())) {
-				if(!addr.isLoopbackAddress()) {
-					if(LOG.isLoggable(INFO)) {
-						LOG.info("Accepting interface "
-								+ addr.getHostAddress());
-					}
-					return addr;
-				}
-			}
-		}
-		throw new IOException("No suitable interfaces");
-	}
-
-	@Override
-	protected SocketAddress getRemoteSocketAddress(ContactId c) {
-		TransportProperties p = callback.getRemoteProperties().get(c);
-		return p == null ? null : createSocketAddress(p);
-	}
-
-	private SocketAddress createSocketAddress(TransportProperties p) {
-		assert p != null;
-		String host = p.get("external");
-		if(host == null) host = p.get("internal");
-		String portString = p.get("port");
-		if(host == null || portString == null) return null;
-		int port;
-		try {
-			port = Integer.valueOf(portString);
-		} catch(NumberFormatException e) {
-			return null;
-		}
-		return new InetSocketAddress(host, port);
-	}
-
-	@Override
-	protected void setLocalSocketAddress(SocketAddress s) {
-		if(!(s instanceof InetSocketAddress))
-			throw new IllegalArgumentException();
-		InetSocketAddress i = (InetSocketAddress) s;
-		InetAddress addr = i.getAddress();
-		TransportProperties p = new TransportProperties();
-		if(addr.isLinkLocalAddress() || addr.isSiteLocalAddress())
-			p.put("internal", addr.getHostAddress());
-		else p.put("external", addr.getHostAddress());
-		p.put("port", String.valueOf(i.getPort()));
-		callback.mergeLocalProperties(p);
-	}
-
-	public boolean supportsInvitations() {
-		return false;
-	}
-
-	public DuplexTransportConnection sendInvitation(PseudoRandom r,
-			long timeout) {
-		throw new UnsupportedOperationException();
-	}
-
-	public DuplexTransportConnection acceptInvitation(PseudoRandom r,
-			long timeout) {
-		throw new UnsupportedOperationException();
-	}
-}
diff --git a/src/net/sf/briar/plugins/socket/LanSocketPlugin.java b/src/net/sf/briar/plugins/tcp/LanTcpPlugin.java
similarity index 60%
rename from src/net/sf/briar/plugins/socket/LanSocketPlugin.java
rename to src/net/sf/briar/plugins/tcp/LanTcpPlugin.java
index a97403df0333adf79e8931574fef68e5fb45b045..8f57a679f036f845b335e4b4c02c0a789ee493bd 100644
--- a/src/net/sf/briar/plugins/socket/LanSocketPlugin.java
+++ b/src/net/sf/briar/plugins/tcp/LanTcpPlugin.java
@@ -1,4 +1,4 @@
-package net.sf.briar.plugins.socket;
+package net.sf.briar.plugins.tcp;
 
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
@@ -8,36 +8,103 @@ import java.net.DatagramPacket;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.MulticastSocket;
+import java.net.NetworkInterface;
 import java.net.ServerSocket;
 import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
 import java.net.SocketTimeoutException;
 import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
+import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.crypto.PseudoRandom;
 import net.sf.briar.api.plugins.PluginExecutor;
 import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
 import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
+import net.sf.briar.api.protocol.TransportId;
 import net.sf.briar.util.ByteUtils;
+import net.sf.briar.util.StringUtils;
 
 /** A socket plugin that supports exchanging invitations over a LAN. */
-class LanSocketPlugin extends SimpleSocketPlugin {
+class LanTcpPlugin extends TcpPlugin {
 
+	public static final byte[] TRANSPORT_ID =
+			StringUtils.fromHexString("0d79357fd7f74d66c2f6f6ad0f7fff81"
+					+ "d21c53a43b90b0507ed0683872d8e2fc"
+					+ "5a88e8f953638228dc26669639757bbf");
+
+	private static final TransportId ID = new TransportId(TRANSPORT_ID);
 	private static final Logger LOG =
-			Logger.getLogger(LanSocketPlugin.class.getName());
+			Logger.getLogger(LanTcpPlugin.class.getName());
 
-	LanSocketPlugin(@PluginExecutor Executor pluginExecutor,
+	LanTcpPlugin(@PluginExecutor Executor pluginExecutor,
 			DuplexPluginCallback callback, long pollingInterval) {
 		super(pluginExecutor, callback, pollingInterval);
 	}
 
+	public TransportId getId() {
+		return ID;
+	}
+
 	@Override
+	protected List<SocketAddress> getLocalSocketAddresses() {
+		List<SocketAddress> addrs = new ArrayList<SocketAddress>();
+		// Prefer a previously used address and port if available
+		TransportProperties p = callback.getLocalProperties();
+		String addrString = p.get("address");
+		String portString = p.get("port");
+		InetAddress addr = null;
+		if(addrString != null && portString != null) {
+			try {
+				addr = InetAddress.getByName(addrString);
+				int port = Integer.valueOf(portString);
+				addrs.add(new InetSocketAddress(addr, port));
+				addrs.add(new InetSocketAddress(addr, 0));
+			} catch(NumberFormatException e) {
+				if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
+			} catch(UnknownHostException e) {
+				if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
+			}
+		}
+		List<NetworkInterface> ifaces;
+		try {
+			ifaces = Collections.list(NetworkInterface.getNetworkInterfaces());
+		} catch(SocketException e) {
+			if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
+			return addrs;
+		}
+		// Prefer interfaces with link-local or site-local addresses
+		for(NetworkInterface iface : ifaces) {
+			for(InetAddress a : Collections.list(iface.getInetAddresses())) {
+				if(addr != null && a.equals(addr)) continue;
+				if(a.isLoopbackAddress()) continue;
+				boolean link = a.isLinkLocalAddress();
+				boolean site = a.isSiteLocalAddress();
+				if(link || site) addrs.add(new InetSocketAddress(a, 0));
+			}
+		}
+		// Accept interfaces without link-local or site-local addresses
+		for(NetworkInterface iface : ifaces) {
+			for(InetAddress a : Collections.list(iface.getInetAddresses())) {
+				if(addr != null && a.equals(addr)) continue;
+				if(a.isLoopbackAddress()) continue;
+				boolean link = a.isLinkLocalAddress();
+				boolean site = a.isSiteLocalAddress();
+				if(!link && !site) addrs.add(new InetSocketAddress(a, 0));
+			}
+		}
+		return addrs;
+	}
+
 	public boolean supportsInvitations() {
 		return true;
 	}
 
-	@Override
 	public DuplexTransportConnection sendInvitation(PseudoRandom r,
 			long timeout) {
 		synchronized(this) {
@@ -48,7 +115,7 @@ class LanSocketPlugin extends SimpleSocketPlugin {
 		// Bind a multicast socket for receiving packets
 		MulticastSocket ms = null;
 		try {
-			InetAddress iface = chooseInterface(true);
+			InetAddress iface = chooseInterface();
 			ms = new MulticastSocket(mcast.getPort());
 			ms.setInterface(iface);
 			ms.joinGroup(mcast.getAddress());
@@ -75,7 +142,7 @@ class LanSocketPlugin extends SimpleSocketPlugin {
 						try {
 							// Connect back on the advertised TCP port
 							Socket s = new Socket(packet.getAddress(), port);
-							return new SocketTransportConnection(s);
+							return new TcpTransportConnection(s);
 						} catch(IOException e) {
 							if(LOG.isLoggable(WARNING))
 								LOG.warning(e.toString());
@@ -99,6 +166,35 @@ class LanSocketPlugin extends SimpleSocketPlugin {
 		return null;
 	}
 
+	private InetAddress chooseInterface() throws IOException {
+		List<NetworkInterface> ifaces =
+				Collections.list(NetworkInterface.getNetworkInterfaces());
+		// Prefer an interface with a link-local or site-local address
+		for(NetworkInterface iface : ifaces) {
+			for(InetAddress addr : Collections.list(iface.getInetAddresses())) {
+				if(addr.isLoopbackAddress()) continue;
+				boolean link = addr.isLinkLocalAddress();
+				boolean site = addr.isSiteLocalAddress();
+				if(link || site) {
+					if(LOG.isLoggable(INFO))
+						LOG.info("Preferring " + addr.getHostAddress());
+					return addr;
+				}
+			}
+		}
+		// Accept an interface without a link-local or site-local address
+		for(NetworkInterface iface : ifaces) {
+			for(InetAddress addr : Collections.list(iface.getInetAddresses())) {
+				if(addr.isLoopbackAddress()) continue;
+				if(LOG.isLoggable(INFO))
+					LOG.info("Accepting " + addr.getHostAddress());
+				return addr;
+			}
+		}
+		// No suitable interfaces
+		return null;
+	}
+
 	private void tryToClose(MulticastSocket ms, InetAddress addr) {
 		try {
 			ms.leaveGroup(addr);
@@ -139,7 +235,6 @@ class LanSocketPlugin extends SimpleSocketPlugin {
 		return ByteUtils.readUint16(b, off);
 	}
 
-	@Override
 	public DuplexTransportConnection acceptInvitation(PseudoRandom r,
 			long timeout) {
 		synchronized(this) {
@@ -150,7 +245,7 @@ class LanSocketPlugin extends SimpleSocketPlugin {
 		// Bind a TCP socket for receiving connections
 		ServerSocket ss = null;
 		try {
-			InetAddress iface = chooseInterface(true);
+			InetAddress iface = chooseInterface();
 			ss = new ServerSocket();
 			ss.bind(new InetSocketAddress(iface, 0));
 		} catch(IOException e) {
@@ -161,7 +256,7 @@ class LanSocketPlugin extends SimpleSocketPlugin {
 		// Bind a multicast socket for sending packets
 		MulticastSocket ms = null;
 		try {
-			InetAddress iface = chooseInterface(true);
+			InetAddress iface = chooseInterface();
 			ms = new MulticastSocket();
 			ms.setInterface(iface);
 		} catch(IOException e) {
@@ -185,7 +280,7 @@ class LanSocketPlugin extends SimpleSocketPlugin {
 				try {
 					int wait = (int) (Math.min(end, nextPacket) - now);
 					ss.setSoTimeout(wait < 1 ? 1 : wait);
-					return new SocketTransportConnection(ss.accept());
+					return new TcpTransportConnection(ss.accept());
 				} catch(SocketTimeoutException e) {
 					now = System.currentTimeMillis();
 					if(now < end) {
diff --git a/src/net/sf/briar/plugins/socket/SocketPlugin.java b/src/net/sf/briar/plugins/tcp/TcpPlugin.java
similarity index 61%
rename from src/net/sf/briar/plugins/socket/SocketPlugin.java
rename to src/net/sf/briar/plugins/tcp/TcpPlugin.java
index cfccb8933dd297d214fe8b5f8603596147f1925e..d082b0425b8d8cda293d1816b1d3500269e4c978 100644
--- a/src/net/sf/briar/plugins/socket/SocketPlugin.java
+++ b/src/net/sf/briar/plugins/tcp/TcpPlugin.java
@@ -1,13 +1,17 @@
-package net.sf.briar.plugins.socket;
+package net.sf.briar.plugins.tcp;
 
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 
 import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.SocketAddress;
+import java.net.UnknownHostException;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
@@ -19,10 +23,10 @@ import net.sf.briar.api.plugins.duplex.DuplexPlugin;
 import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
 import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
 
-abstract class SocketPlugin implements DuplexPlugin {
+abstract class TcpPlugin implements DuplexPlugin {
 
 	private static final Logger LOG =
-		Logger.getLogger(SocketPlugin.class.getName());
+			Logger.getLogger(TcpPlugin.class.getName());
 
 	protected final Executor pluginExecutor;
 	protected final DuplexPluginCallback callback;
@@ -30,16 +34,15 @@ abstract class SocketPlugin implements DuplexPlugin {
 	private final long pollingInterval;
 
 	protected boolean running = false; // Locking: this
-	protected ServerSocket socket = null; // Locking: this
+	private ServerSocket socket = null; // Locking: this
 
-	protected abstract void setLocalSocketAddress(SocketAddress s);
+	/**
+	 * Returns zero or more socket addresses on which the plugin should listen,
+	 * in order of preference. At most one of the addresses will be bound.
+	 */
+	protected abstract List<SocketAddress> getLocalSocketAddresses();
 
-	protected abstract Socket createClientSocket() throws IOException;
-	protected abstract ServerSocket createServerSocket() throws IOException;
-	protected abstract SocketAddress getLocalSocketAddress();
-	protected abstract SocketAddress getRemoteSocketAddress(ContactId c);
-
-	protected SocketPlugin(@PluginExecutor Executor pluginExecutor,
+	protected TcpPlugin(@PluginExecutor Executor pluginExecutor,
 			DuplexPluginCallback callback, long pollingInterval) {
 		this.pluginExecutor = pluginExecutor;
 		this.callback = callback;
@@ -58,21 +61,27 @@ abstract class SocketPlugin implements DuplexPlugin {
 	}
 
 	private void bind() {
-		SocketAddress addr;
-		ServerSocket ss = null;
+		ServerSocket ss;
 		try {
-			addr = getLocalSocketAddress();
-			ss = createServerSocket();
+			ss = new ServerSocket();
 		} catch(IOException e) {
 			if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
 			return;
 		}
-		if(addr == null || ss == null) return;
-		try {
-			ss.bind(addr);
-		} catch(IOException e) {
-			if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
-			tryToClose(ss);
+		boolean found = false;
+		for(SocketAddress addr : getLocalSocketAddresses()) {
+			try {
+				ss.bind(addr);
+				found = true;
+				break;
+			} catch(IOException e) {
+				if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
+				tryToClose(ss);
+				continue;
+			}
+		}
+		if(!found) {
+			if(LOG.isLoggable(INFO)) LOG.info("Could not bind server socket");
 			return;
 		}
 		synchronized(this) {
@@ -98,6 +107,15 @@ abstract class SocketPlugin implements DuplexPlugin {
 		}
 	}
 
+	private void setLocalSocketAddress(SocketAddress s) {
+		InetSocketAddress i = (InetSocketAddress) s;
+		InetAddress addr = i.getAddress();
+		TransportProperties p = new TransportProperties();
+		p.put("address", addr.getHostAddress());
+		p.put("port", String.valueOf(i.getPort()));
+		callback.mergeLocalProperties(p);
+	}
+
 	private void acceptContactConnections(ServerSocket ss) {
 		while(true) {
 			Socket s;
@@ -109,7 +127,7 @@ abstract class SocketPlugin implements DuplexPlugin {
 				tryToClose(ss);
 				return;
 			}
-			SocketTransportConnection conn = new SocketTransportConnection(s);
+			TcpTransportConnection conn = new TcpTransportConnection(s);
 			callback.incomingConnectionCreated(conn);
 			synchronized(this) {
 				if(!running) return;
@@ -138,7 +156,7 @@ abstract class SocketPlugin implements DuplexPlugin {
 			if(!running) return;
 		}
 		Map<ContactId, TransportProperties> remote =
-			callback.getRemoteProperties();
+				callback.getRemoteProperties();
 		for(final ContactId c : remote.keySet()) {
 			if(connected.contains(c)) continue;
 			pluginExecutor.execute(new Runnable() {
@@ -160,13 +178,32 @@ abstract class SocketPlugin implements DuplexPlugin {
 		}
 		SocketAddress addr = getRemoteSocketAddress(c);
 		try {
-			Socket s = createClientSocket();
+			Socket s = new Socket();
 			if(addr == null || s == null) return null;
 			s.connect(addr);
-			return new SocketTransportConnection(s);
+			return new TcpTransportConnection(s);
 		} catch(IOException e) {
 			if(LOG.isLoggable(INFO)) LOG.info(e.toString());
 			return null;
 		}
 	}
+
+	private SocketAddress getRemoteSocketAddress(ContactId c) {
+		TransportProperties p = callback.getRemoteProperties().get(c);
+		if(p == null) return null;
+		String addrString = p.get("address");
+		String portString = p.get("port");
+		if(addrString != null && portString != null) {
+			try {
+				InetAddress addr = InetAddress.getByName(addrString);
+				int port = Integer.valueOf(portString);
+				return new InetSocketAddress(addr, port);
+			} catch(NumberFormatException e) {
+				if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
+			} catch(UnknownHostException e) {
+				if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
+			}
+		}
+		return null;
+	}
 }
diff --git a/src/net/sf/briar/plugins/socket/SocketTransportConnection.java b/src/net/sf/briar/plugins/tcp/TcpTransportConnection.java
similarity index 79%
rename from src/net/sf/briar/plugins/socket/SocketTransportConnection.java
rename to src/net/sf/briar/plugins/tcp/TcpTransportConnection.java
index 953f74f6d7d32602f31ea33afd5306b8cbbb6b7c..379b706da63964cf809c213ccb22fb6cb01b0227 100644
--- a/src/net/sf/briar/plugins/socket/SocketTransportConnection.java
+++ b/src/net/sf/briar/plugins/tcp/TcpTransportConnection.java
@@ -1,4 +1,4 @@
-package net.sf.briar.plugins.socket;
+package net.sf.briar.plugins.tcp;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -7,11 +7,11 @@ import java.net.Socket;
 
 import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
 
-class SocketTransportConnection implements DuplexTransportConnection {
+class TcpTransportConnection implements DuplexTransportConnection {
 
 	private final Socket socket;
 
-	SocketTransportConnection(Socket socket) {
+	TcpTransportConnection(Socket socket) {
 		this.socket = socket;
 	}
 
diff --git a/src/net/sf/briar/plugins/tcp/WanTcpPlugin.java b/src/net/sf/briar/plugins/tcp/WanTcpPlugin.java
new file mode 100644
index 0000000000000000000000000000000000000000..69bd3074e0deb8584a71e812f11d8e2b2e926e89
--- /dev/null
+++ b/src/net/sf/briar/plugins/tcp/WanTcpPlugin.java
@@ -0,0 +1,98 @@
+package net.sf.briar.plugins.tcp;
+
+import static java.util.logging.Level.WARNING;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+
+import net.sf.briar.api.TransportProperties;
+import net.sf.briar.api.crypto.PseudoRandom;
+import net.sf.briar.api.plugins.PluginExecutor;
+import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
+import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
+import net.sf.briar.api.protocol.TransportId;
+import net.sf.briar.util.StringUtils;
+
+class WanTcpPlugin extends TcpPlugin {
+
+	public static final byte[] TRANSPORT_ID =
+			StringUtils.fromHexString("58c66d999e492b85065924acfd739d80"
+					+ "c65a62f87e5a4fc6c284f95908b9007d"
+					+ "512a93ebf89bf68f50a29e96eebf97b6");
+
+	private static final TransportId ID = new TransportId(TRANSPORT_ID);
+	private static final Logger LOG =
+			Logger.getLogger(WanTcpPlugin.class.getName());
+
+	WanTcpPlugin(@PluginExecutor Executor pluginExecutor,
+			DuplexPluginCallback callback, long pollingInterval) {
+		super(pluginExecutor, callback, pollingInterval);
+	}
+
+	public TransportId getId() {
+		return ID;
+	}
+
+	@Override
+	protected List<SocketAddress> getLocalSocketAddresses() {
+		List<SocketAddress> addrs = new ArrayList<SocketAddress>();
+		// Prefer a previously used address and port if available
+		TransportProperties p = callback.getLocalProperties();
+		String addrString = p.get("address");
+		String portString = p.get("port");
+		InetAddress addr = null;
+		if(addrString != null && portString != null) {
+			try {
+				addr = InetAddress.getByName(addrString);
+				int port = Integer.valueOf(portString);
+				addrs.add(new InetSocketAddress(addr, port));
+				addrs.add(new InetSocketAddress(addr, 0));
+			} catch(NumberFormatException e) {
+				if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
+			} catch(UnknownHostException e) {
+				if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
+			}
+		}
+		List<NetworkInterface> ifaces;
+		try {
+			ifaces = Collections.list(NetworkInterface.getNetworkInterfaces());
+		} catch(SocketException e) {
+			if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
+			return addrs;
+		}
+		// Accept interfaces without link-local or site-local addresses
+		for(NetworkInterface iface : ifaces) {
+			for(InetAddress a : Collections.list(iface.getInetAddresses())) {
+				if(addr != null && a.equals(addr)) continue;
+				if(a.isLoopbackAddress()) continue;
+				boolean link = a.isLinkLocalAddress();
+				boolean site = a.isSiteLocalAddress();
+				if(!link && !site) addrs.add(new InetSocketAddress(a, 0));
+			}
+		}
+		return addrs;
+	}
+
+	public boolean supportsInvitations() {
+		return false;
+	}
+
+	public DuplexTransportConnection sendInvitation(PseudoRandom r,
+			long timeout) {
+		throw new UnsupportedOperationException();
+	}
+
+	public DuplexTransportConnection acceptInvitation(PseudoRandom r,
+			long timeout) {
+		throw new UnsupportedOperationException();
+	}
+}
diff --git a/src/net/sf/briar/plugins/socket/SimpleSocketPluginFactory.java b/src/net/sf/briar/plugins/tcp/WanTcpPluginFactory.java
similarity index 74%
rename from src/net/sf/briar/plugins/socket/SimpleSocketPluginFactory.java
rename to src/net/sf/briar/plugins/tcp/WanTcpPluginFactory.java
index d674571465ed545aa087ef4da71f9fa03a2f367b..e4ed87284fb30e8bed42e7305e44852fae56ff68 100644
--- a/src/net/sf/briar/plugins/socket/SimpleSocketPluginFactory.java
+++ b/src/net/sf/briar/plugins/tcp/WanTcpPluginFactory.java
@@ -1,4 +1,4 @@
-package net.sf.briar.plugins.socket;
+package net.sf.briar.plugins.tcp;
 
 import java.util.concurrent.Executor;
 
@@ -9,14 +9,13 @@ import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
 import net.sf.briar.api.plugins.duplex.DuplexPluginFactory;
 import android.content.Context;
 
-public class SimpleSocketPluginFactory implements DuplexPluginFactory {
+public class WanTcpPluginFactory implements DuplexPluginFactory {
 
-	private static final long POLLING_INTERVAL = 5L * 60L * 1000L; // 5 mins
+	private static final long POLLING_INTERVAL = 5L * 60L * 1000L; // 5 minutes
 
 	public DuplexPlugin createPlugin(@PluginExecutor Executor pluginExecutor,
 			AndroidExecutor androidExecutor, Context appContext,
 			DuplexPluginCallback callback) {
-		return new SimpleSocketPlugin(pluginExecutor, callback,
-				POLLING_INTERVAL);
+		return new WanTcpPlugin(pluginExecutor, callback, POLLING_INTERVAL);
 	}
 }