diff --git a/briar-android/build.gradle b/briar-android/build.gradle
index adea13909d84b5fb0cb8f69d979fdd6e4315149c..dc93b62fb5c70e59fb8a55513742493a91e9efc4 100644
--- a/briar-android/build.gradle
+++ b/briar-android/build.gradle
@@ -11,9 +11,6 @@ dependencies {
 	compile project(':briar-api')
 	compile project(':briar-core')
 	compile fileTree(dir: 'libs', include: '*.jar')
-	// This shouldn't be necessary; per section 23.4.4 of the Gradle docs:
-	// "file dependencies are included in transitive project dependencies within the same build".
-	compile files('../briar-core/libs/jsocks.jar')
 
 	compile "com.android.support:support-v4:$supportVersion"
 	compile("com.android.support:appcompat-v7:$supportVersion") {
diff --git a/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java b/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java
index ca899bc8aed4e8a67cc0c13e3619426cbedd4c2a..4d49a13ac71e204fd45678065439aaf636d4f84b 100644
--- a/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java
+++ b/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java
@@ -22,6 +22,8 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.concurrent.Executor;
 
+import javax.net.SocketFactory;
+
 import dagger.Module;
 import dagger.Provides;
 
@@ -30,15 +32,16 @@ public class AndroidPluginsModule {
 
 	@Provides
 	public PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
-			AndroidExecutor androidExecutor,
-			SecureRandom random, BackoffFactory backoffFactory, Application app,
-			LocationUtils locationUtils, DevReporter reporter,
+			AndroidExecutor androidExecutor, SecureRandom random,
+			SocketFactory torSocketFactory, BackoffFactory backoffFactory,
+			Application app, LocationUtils locationUtils, DevReporter reporter,
 			EventBus eventBus) {
 		Context appContext = app.getApplicationContext();
 		DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor,
 				androidExecutor, appContext, random, backoffFactory);
 		DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, appContext,
-				locationUtils, reporter, eventBus, backoffFactory);
+				locationUtils, reporter, eventBus, torSocketFactory,
+				backoffFactory);
 		DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
 				backoffFactory, appContext);
 		final Collection<DuplexPluginFactory> duplex =
diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
index 0fcf4a87e61bcafa900ee59931d29f93396e41ab..3347c2477ee47bdce74ad69ac7b005e39dce8044 100644
--- a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
+++ b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
@@ -14,8 +14,6 @@ import android.os.PowerManager;
 
 import net.freehaven.tor.control.EventHandler;
 import net.freehaven.tor.control.TorControlConnection;
-import net.sourceforge.jsocks.socks.Socks5Proxy;
-import net.sourceforge.jsocks.socks.SocksSocket;
 
 import org.briarproject.android.util.AndroidUtils;
 import org.briarproject.api.TransportId;
@@ -62,6 +60,8 @@ import java.util.logging.Logger;
 import java.util.regex.Pattern;
 import java.util.zip.ZipInputStream;
 
+import javax.net.SocketFactory;
+
 import static android.content.Context.CONNECTIVITY_SERVICE;
 import static android.content.Context.MODE_PRIVATE;
 import static android.content.Context.POWER_SERVICE;
@@ -74,7 +74,6 @@ import static java.util.logging.Level.WARNING;
 import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
 import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
 import static org.briarproject.api.plugins.TorConstants.CONTROL_PORT;
-import static org.briarproject.api.plugins.TorConstants.SOCKS_PORT;
 import static org.briarproject.util.PrivacyUtils.scrubOnion;
 
 class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@@ -93,6 +92,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 	private final Context appContext;
 	private final LocationUtils locationUtils;
 	private final DevReporter reporter;
+	private final SocketFactory torSocketFactory;
 	private final Backoff backoff;
 	private final DuplexPluginCallback callback;
 	private final String architecture;
@@ -110,13 +110,15 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 	private volatile BroadcastReceiver networkStateReceiver = null;
 
 	TorPlugin(Executor ioExecutor, Context appContext,
-			LocationUtils locationUtils, DevReporter reporter, Backoff backoff,
+			LocationUtils locationUtils, DevReporter reporter,
+			SocketFactory torSocketFactory, Backoff backoff,
 			DuplexPluginCallback callback, String architecture, int maxLatency,
 			int maxIdleTime) {
 		this.ioExecutor = ioExecutor;
 		this.appContext = appContext;
 		this.locationUtils = locationUtils;
 		this.reporter = reporter;
+		this.torSocketFactory = torSocketFactory;
 		this.backoff = backoff;
 		this.callback = callback;
 		this.architecture = architecture;
@@ -295,6 +297,14 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 		}
 	}
 
+	private void tryToClose(Socket s) {
+		try {
+			if (s != null) s.close();
+		} catch (IOException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		}
+	}
+
 	private void listFiles(File f) {
 		if (f.isDirectory()) for (File child : f.listFiles()) listFiles(child);
 		else LOG.info(f.getAbsolutePath());
@@ -320,8 +330,9 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 		ioExecutor.execute(new Runnable() {
 			@Override
 			public void run() {
+				// TODO: Trigger this with a TransportEnabledEvent
 				File reportDir = AndroidUtils.getReportDir(appContext);
-				reporter.sendReports(reportDir, SOCKS_PORT);
+				reporter.sendReports(reportDir);
 			}
 		});
 	}
@@ -516,21 +527,22 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 			if (LOG.isLoggable(INFO)) LOG.info("Invalid hostname: " + onion);
 			return null;
 		}
+		Socket s = null;
 		try {
 			if (LOG.isLoggable(INFO))
 				LOG.info("Connecting to " + scrubOnion(onion));
 			controlConnection.forgetHiddenService(onion);
-			Socks5Proxy proxy = new Socks5Proxy("127.0.0.1", SOCKS_PORT);
-			proxy.resolveAddrLocally(false);
-			Socket s = new SocksSocket(proxy, onion + ".onion", 80);
+			s = torSocketFactory.createSocket(onion + ".onion", 80);
 			s.setSoTimeout(socketTimeout);
 			if (LOG.isLoggable(INFO))
 				LOG.info("Connected to " + scrubOnion(onion));
 			return new TorTransportConnection(this, s);
 		} catch (IOException e) {
-			if (LOG.isLoggable(INFO))
+			if (LOG.isLoggable(INFO)) {
 				LOG.info("Could not connect to " + scrubOnion(onion) + ": " +
 						e.toString());
+			}
+			tryToClose(s);
 			return null;
 		}
 	}
diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java b/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java
index ae1b9e856a6cbac77de5b85ecb4860d21db00ca6..c85405f676ad3afad530fabfcb2ef0cd1fcecd04 100644
--- a/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java
+++ b/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java
@@ -18,6 +18,8 @@ import org.briarproject.api.system.LocationUtils;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
+import javax.net.SocketFactory;
+
 public class TorPluginFactory implements DuplexPluginFactory {
 
 	private static final Logger LOG =
@@ -34,16 +36,19 @@ public class TorPluginFactory implements DuplexPluginFactory {
 	private final LocationUtils locationUtils;
 	private final DevReporter reporter;
 	private final EventBus eventBus;
+	private final SocketFactory torSocketFactory;
 	private final BackoffFactory backoffFactory;
 
 	public TorPluginFactory(Executor ioExecutor, Context appContext,
 			LocationUtils locationUtils, DevReporter reporter,
-			EventBus eventBus, BackoffFactory backoffFactory) {
+			EventBus eventBus, SocketFactory torSocketFactory,
+			BackoffFactory backoffFactory) {
 		this.ioExecutor = ioExecutor;
 		this.appContext = appContext;
 		this.locationUtils = locationUtils;
 		this.reporter = reporter;
 		this.eventBus = eventBus;
+		this.torSocketFactory = torSocketFactory;
 		this.backoffFactory = backoffFactory;
 	}
 
@@ -81,8 +86,8 @@ public class TorPluginFactory implements DuplexPluginFactory {
 		Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
 				MAX_POLLING_INTERVAL, BACKOFF_BASE);
 		TorPlugin plugin = new TorPlugin(ioExecutor, appContext, locationUtils,
-				reporter, backoff, callback, architecture, MAX_LATENCY,
-				MAX_IDLE_TIME);
+				reporter, torSocketFactory, backoff, callback, architecture,
+				MAX_LATENCY, MAX_IDLE_TIME);
 		eventBus.addListener(plugin);
 		return plugin;
 	}
diff --git a/briar-api/src/org/briarproject/api/plugins/TorConstants.java b/briar-api/src/org/briarproject/api/plugins/TorConstants.java
index 3eec84e08c7b71d53ea51ff831daaca4202757b1..f90e047bb00c1374ea0884775a60a280e1116c99 100644
--- a/briar-api/src/org/briarproject/api/plugins/TorConstants.java
+++ b/briar-api/src/org/briarproject/api/plugins/TorConstants.java
@@ -9,4 +9,5 @@ public interface TorConstants {
 	int SOCKS_PORT   = 59050;
 	int CONTROL_PORT = 59051;
 
+	int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
 }
diff --git a/briar-api/src/org/briarproject/api/reporting/DevReporter.java b/briar-api/src/org/briarproject/api/reporting/DevReporter.java
index 7c0f14aa010caa3d12135eb3199637b2171adbf8..dfcbb9c1a96ff7bfe08fb6f0b5a80655981e94ee 100644
--- a/briar-api/src/org/briarproject/api/reporting/DevReporter.java
+++ b/briar-api/src/org/briarproject/api/reporting/DevReporter.java
@@ -22,7 +22,6 @@ public interface DevReporter {
 	 * Send reports previously stored on-disk.
 	 *
 	 * @param reportDir the directory where reports are stored.
-	 * @param socksPort the SOCKS port of a Tor client.
 	 */
-	void sendReports(File reportDir, int socksPort);
+	void sendReports(File reportDir);
 }
diff --git a/briar-core/libs/jsocks.jar b/briar-core/libs/jsocks.jar
deleted file mode 100644
index 5b5ff17ef666ab56f4fd647d37c9798e9798c269..0000000000000000000000000000000000000000
Binary files a/briar-core/libs/jsocks.jar and /dev/null differ
diff --git a/briar-core/src/org/briarproject/CoreModule.java b/briar-core/src/org/briarproject/CoreModule.java
index d865d6e43a25eebcf6ffa6198794703ab584be23..dbfaeb256a9c4d7f3670afbccf8181e61066212e 100644
--- a/briar-core/src/org/briarproject/CoreModule.java
+++ b/briar-core/src/org/briarproject/CoreModule.java
@@ -22,6 +22,7 @@ import org.briarproject.reliability.ReliabilityModule;
 import org.briarproject.reporting.ReportingModule;
 import org.briarproject.settings.SettingsModule;
 import org.briarproject.sharing.SharingModule;
+import org.briarproject.socks.SocksModule;
 import org.briarproject.sync.SyncModule;
 import org.briarproject.system.SystemModule;
 import org.briarproject.transport.TransportModule;
@@ -50,6 +51,7 @@ import dagger.Module;
 		ReportingModule.class,
 		SettingsModule.class,
 		SharingModule.class,
+		SocksModule.class,
 		SyncModule.class,
 		SystemModule.class,
 		TransportModule.class,
diff --git a/briar-core/src/org/briarproject/feed/FeedManagerImpl.java b/briar-core/src/org/briarproject/feed/FeedManagerImpl.java
index 496c28403dae49e02578fe1fcb56c4657d7f85fe..d0baff13791bb1bfb7018f245aa95f7f43b077b2 100644
--- a/briar-core/src/org/briarproject/feed/FeedManagerImpl.java
+++ b/briar-core/src/org/briarproject/feed/FeedManagerImpl.java
@@ -40,9 +40,6 @@ import org.briarproject.util.StringUtils;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.net.InetSocketAddress;
-import java.net.Proxy;
-import java.net.SocketAddress;
 import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -55,11 +52,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Logger;
 
 import javax.inject.Inject;
+import javax.net.SocketFactory;
 
 import okhttp3.OkHttpClient;
 import okhttp3.Request;
 import okhttp3.Response;
 
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
@@ -67,7 +66,6 @@ import static org.briarproject.api.feed.FeedConstants.FETCH_DELAY_INITIAL;
 import static org.briarproject.api.feed.FeedConstants.FETCH_INTERVAL;
 import static org.briarproject.api.feed.FeedConstants.FETCH_UNIT;
 import static org.briarproject.api.feed.FeedConstants.KEY_FEEDS;
-import static org.briarproject.api.plugins.TorConstants.SOCKS_PORT;
 
 class FeedManagerImpl implements FeedManager, Client, EventListener {
 
@@ -79,6 +77,8 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
 					"466565644d616e6167657202fb797097"
 							+ "255af837abbf8c16e250b3c2ccc286eb"));
 
+	private static final int CONNECT_TIMEOUT = 60 * 1000; // Milliseconds
+
 	private final ScheduledExecutorService feedExecutor;
 	private final Executor ioExecutor;
 	private final DatabaseComponent db;
@@ -86,6 +86,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
 	private final ClientHelper clientHelper;
 	private final IdentityManager identityManager;
 	private final BlogManager blogManager;
+	private final SocketFactory torSocketFactory;
 	private final AtomicBoolean fetcherStarted = new AtomicBoolean(false);
 
 	@Inject
@@ -99,7 +100,8 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
 	FeedManagerImpl(ScheduledExecutorService feedExecutor,
 			@IoExecutor Executor ioExecutor, DatabaseComponent db,
 			PrivateGroupFactory privateGroupFactory, ClientHelper clientHelper,
-			IdentityManager identityManager, BlogManager blogManager) {
+			IdentityManager identityManager, BlogManager blogManager,
+			SocketFactory torSocketFactory) {
 
 		this.feedExecutor = feedExecutor;
 		this.ioExecutor = ioExecutor;
@@ -108,6 +110,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
 		this.clientHelper = clientHelper;
 		this.identityManager = identityManager;
 		this.blogManager = blogManager;
+		this.torSocketFactory = torSocketFactory;
 	}
 
 	@Override
@@ -354,14 +357,10 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
 	}
 
 	private InputStream getFeedInputStream(String url) throws IOException {
-		// Set proxy
-		SocketAddress socketAddress =
-				new InetSocketAddress("127.0.0.1", SOCKS_PORT);
-		Proxy proxy = new Proxy(Proxy.Type.SOCKS, socketAddress);
-
 		// Build HTTP Client
 		OkHttpClient client = new OkHttpClient.Builder()
-//				.proxy(proxy)
+				.socketFactory(torSocketFactory)
+				.connectTimeout(CONNECT_TIMEOUT, MILLISECONDS)
 				.build();
 
 		// Build Request
diff --git a/briar-core/src/org/briarproject/reporting/DevReporterImpl.java b/briar-core/src/org/briarproject/reporting/DevReporterImpl.java
index bd7154068ca901e2f1491e2e44fd67fba65dc352..7d7415fb018dcd74e87a0930469bc0dbde296721 100644
--- a/briar-core/src/org/briarproject/reporting/DevReporterImpl.java
+++ b/briar-core/src/org/briarproject/reporting/DevReporterImpl.java
@@ -1,9 +1,5 @@
 package org.briarproject.reporting;
 
-import net.sourceforge.jsocks.socks.Socks5Proxy;
-import net.sourceforge.jsocks.socks.SocksException;
-import net.sourceforge.jsocks.socks.SocksSocket;
-
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.reporting.DevConfig;
 import org.briarproject.api.reporting.DevReporter;
@@ -21,10 +17,10 @@ import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.net.Socket;
-import java.net.SocketException;
-import java.net.UnknownHostException;
 import java.util.logging.Logger;
 
+import javax.net.SocketFactory;
+
 import static java.util.logging.Level.WARNING;
 
 class DevReporterImpl implements DevReporter {
@@ -35,21 +31,28 @@ class DevReporterImpl implements DevReporter {
 	private static final int SOCKET_TIMEOUT = 30 * 1000; // 30 seconds
 	private static final int LINE_LENGTH = 70;
 
-	private CryptoComponent crypto;
-	private DevConfig devConfig;
+	private final CryptoComponent crypto;
+	private final DevConfig devConfig;
+	private final SocketFactory torSocketFactory;
 
-	public DevReporterImpl(CryptoComponent crypto, DevConfig devConfig) {
+	public DevReporterImpl(CryptoComponent crypto, DevConfig devConfig,
+			SocketFactory torSocketFactory) {
 		this.crypto = crypto;
 		this.devConfig = devConfig;
+		this.torSocketFactory = torSocketFactory;
 	}
 
-	private Socket connectToDevelopers(int socksPort)
-			throws UnknownHostException, SocksException, SocketException {
-		Socks5Proxy proxy = new Socks5Proxy("127.0.0.1", socksPort);
-		proxy.resolveAddrLocally(false);
-		Socket s = new SocksSocket(proxy, devConfig.getDevOnionAddress(), 80);
-		s.setSoTimeout(SOCKET_TIMEOUT);
-		return s;
+	private Socket connectToDevelopers() throws IOException {
+		String onion = devConfig.getDevOnionAddress();
+		Socket s = null;
+		try {
+			s = torSocketFactory.createSocket(onion, 80);
+			s.setSoTimeout(SOCKET_TIMEOUT);
+			return s;
+		} catch (IOException e) {
+			tryToClose(s);
+			throw e;
+		}
 	}
 
 	@Override
@@ -74,7 +77,7 @@ class DevReporterImpl implements DevReporter {
 	}
 
 	@Override
-	public void sendReports(File reportDir, int socksPort) {
+	public void sendReports(File reportDir) {
 		File[] reports = reportDir.listFiles();
 		if (reports == null || reports.length == 0)
 			return; // No reports to send
@@ -84,7 +87,7 @@ class DevReporterImpl implements DevReporter {
 			OutputStream out = null;
 			InputStream in = null;
 			try {
-				Socket s = connectToDevelopers(socksPort);
+				Socket s = connectToDevelopers();
 				out = s.getOutputStream();
 				in = new FileInputStream(f);
 				IoUtils.copy(in, out);
@@ -106,4 +109,12 @@ class DevReporterImpl implements DevReporter {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 		}
 	}
+
+	private void tryToClose(Socket s) {
+		try {
+			if (s != null) s.close();
+		} catch (IOException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		}
+	}
 }
diff --git a/briar-core/src/org/briarproject/reporting/ReportingModule.java b/briar-core/src/org/briarproject/reporting/ReportingModule.java
index 7c88261de3faae5b3d535d2683e0de242b1c7446..333b3afca06339862198382907349b3488f21472 100644
--- a/briar-core/src/org/briarproject/reporting/ReportingModule.java
+++ b/briar-core/src/org/briarproject/reporting/ReportingModule.java
@@ -4,6 +4,8 @@ import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.reporting.DevConfig;
 import org.briarproject.api.reporting.DevReporter;
 
+import javax.net.SocketFactory;
+
 import dagger.Module;
 import dagger.Provides;
 
@@ -12,7 +14,7 @@ public class ReportingModule {
 
 	@Provides
 	DevReporter provideDevReportTask(CryptoComponent crypto,
-			DevConfig devConfig) {
-		return new DevReporterImpl(crypto, devConfig);
+			DevConfig devConfig, SocketFactory torSocketFactory) {
+		return new DevReporterImpl(crypto, devConfig, torSocketFactory);
 	}
 }
diff --git a/briar-core/src/org/briarproject/socks/SocksModule.java b/briar-core/src/org/briarproject/socks/SocksModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..eaaa3c4d6231cb52b47949e07d31ca52151bec15
--- /dev/null
+++ b/briar-core/src/org/briarproject/socks/SocksModule.java
@@ -0,0 +1,22 @@
+package org.briarproject.socks;
+
+import java.net.InetSocketAddress;
+
+import javax.net.SocketFactory;
+
+import dagger.Module;
+import dagger.Provides;
+
+import static org.briarproject.api.plugins.TorConstants.CONNECT_TO_PROXY_TIMEOUT;
+import static org.briarproject.api.plugins.TorConstants.SOCKS_PORT;
+
+@Module
+public class SocksModule {
+
+	@Provides
+	SocketFactory provideTorSocketFactory() {
+		InetSocketAddress proxy = new InetSocketAddress("127.0.0.1",
+				SOCKS_PORT);
+		return new SocksSocketFactory(proxy, CONNECT_TO_PROXY_TIMEOUT);
+	}
+}
diff --git a/briar-core/src/org/briarproject/socks/SocksSocket.java b/briar-core/src/org/briarproject/socks/SocksSocket.java
new file mode 100644
index 0000000000000000000000000000000000000000..7c627daef947e0bd3207c3046a3e9b888ca146ad
--- /dev/null
+++ b/briar-core/src/org/briarproject/socks/SocksSocket.java
@@ -0,0 +1,108 @@
+package org.briarproject.socks;
+
+import org.briarproject.util.ByteUtils;
+import org.briarproject.util.IoUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+
+class SocksSocket extends Socket {
+
+	private final SocketAddress proxy;
+	private final int connectToProxyTimeout;
+
+	SocksSocket(SocketAddress proxy, int connectToProxyTimeout) {
+		this.proxy = proxy;
+		this.connectToProxyTimeout = connectToProxyTimeout;
+	}
+
+	@Override
+	public void connect(SocketAddress endpoint, int timeout)
+			throws IOException {
+
+		// Validate the endpoint
+		if (!(endpoint instanceof InetSocketAddress))
+			throw new IllegalArgumentException();
+		InetSocketAddress inet = (InetSocketAddress) endpoint;
+		String host = inet.getHostName();
+		if (host.length() > 255) throw new IllegalArgumentException();
+		int port = inet.getPort();
+
+		// Connect to the proxy
+		super.connect(proxy, connectToProxyTimeout);
+		OutputStream out = getOutputStream();
+		InputStream in = getInputStream();
+
+		// Request SOCKS 5 with no authentication
+		sendMethodRequest(out);
+		receiveMethodResponse(in);
+
+		// Use the supplied timeout temporarily
+		int oldTimeout = getSoTimeout();
+		setSoTimeout(timeout);
+
+		// Connect to the endpoint via the proxy
+		sendConnectRequest(out, host, port);
+		receiveConnectResponse(in);
+
+		// Restore the old timeout
+		setSoTimeout(oldTimeout);
+	}
+
+	private void sendMethodRequest(OutputStream out) throws IOException {
+		byte[] methodRequest = new byte[] {
+				5, // SOCKS version is 5
+				1, // Number of methods is 1
+				0  // Method is 0, no authentication
+		};
+		out.write(methodRequest);
+		out.flush();
+	}
+
+	private void receiveMethodResponse(InputStream in) throws IOException {
+		byte[] methodResponse = new byte[2];
+		IoUtils.read(in, methodResponse);
+		byte version = methodResponse[0];
+		byte method = methodResponse[1];
+		if (version != 5)
+			throw new IOException("Unsupported SOCKS version: " + version);
+		if (method == (byte) 255)
+			throw new IOException("Proxy requires authentication");
+		if (method != 0)
+			throw new IOException("Unsupported auth method: " + method);
+	}
+
+	private void sendConnectRequest(OutputStream out, String host, int port)
+			throws IOException {
+		byte[] connectRequest = new byte[7 + host.length()];
+		connectRequest[0] = 5; // SOCKS version is 5
+		connectRequest[1] = 1; // Command is 1, connect
+		connectRequest[3] = 3; // Address type is 3, domain name
+		connectRequest[4] = (byte) host.length(); // Length of domain name
+		for (int i = 0; i < host.length(); i++)
+			connectRequest[5 + i] = (byte) host.charAt(i);
+		ByteUtils.writeUint16(port, connectRequest, connectRequest.length - 2);
+		out.write(connectRequest);
+		out.flush();
+	}
+
+	private void receiveConnectResponse(InputStream in) throws IOException {
+		byte[] connectResponse = new byte[4];
+		IoUtils.read(in, connectResponse);
+		byte version = connectResponse[0];
+		byte reply = connectResponse[1];
+		byte addressType = connectResponse[3];
+		if (version != 5)
+			throw new IOException("Unsupported SOCKS version: " + version);
+		if (reply != 0)
+			throw new IOException("Connection failed: " + reply);
+		if (addressType == 1) IoUtils.read(in, new byte[4]); // IPv4
+		else if (addressType == 4) IoUtils.read(in, new byte[16]); // IPv6
+		else throw new IOException("Unsupported address type: " + addressType);
+		IoUtils.read(in, new byte[2]); // Port number
+	}
+}
diff --git a/briar-core/src/org/briarproject/socks/SocksSocketFactory.java b/briar-core/src/org/briarproject/socks/SocksSocketFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..dfcd973597429e955de2b21cfa50bbbbe22ad42c
--- /dev/null
+++ b/briar-core/src/org/briarproject/socks/SocksSocketFactory.java
@@ -0,0 +1,49 @@
+package org.briarproject.socks;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+
+import javax.net.SocketFactory;
+
+class SocksSocketFactory extends SocketFactory {
+
+	private final SocketAddress proxy;
+	private final int connectToProxyTimeout;
+
+	SocksSocketFactory(SocketAddress proxy, int connectToProxyTimeout) {
+		this.proxy = proxy;
+		this.connectToProxyTimeout = connectToProxyTimeout;
+	}
+
+	@Override
+	public Socket createSocket() {
+		return new SocksSocket(proxy, connectToProxyTimeout);
+	}
+
+	@Override
+	public Socket createSocket(String host, int port) throws IOException {
+		Socket socket = createSocket();
+		socket.connect(InetSocketAddress.createUnresolved(host, port));
+		return socket;
+	}
+
+	@Override
+	public Socket createSocket(InetAddress host, int port) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public Socket createSocket(String host, int port, InetAddress localHost,
+			int localPort) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public Socket createSocket(InetAddress address, int port,
+			InetAddress localAddress, int localPort) throws IOException {
+		throw new UnsupportedOperationException();
+	}
+}
diff --git a/briar-core/src/org/briarproject/util/IoUtils.java b/briar-core/src/org/briarproject/util/IoUtils.java
index c149b024e16017e51d91f449e0613f1f57b34815..494c3a854be4c68386b6ed080e84eec4df051428 100644
--- a/briar-core/src/org/briarproject/util/IoUtils.java
+++ b/briar-core/src/org/briarproject/util/IoUtils.java
@@ -1,5 +1,7 @@
 package org.briarproject.util;
 
+import java.io.Closeable;
+import java.io.EOFException;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -22,19 +24,34 @@ public class IoUtils {
 			throws IOException {
 		byte[] buf = new byte[4096];
 		try {
-			try {
-				while (true) {
-					int read = in.read(buf);
-					if (read == -1) break;
-					out.write(buf, 0, read);
-				}
-				out.flush();
-			} finally {
-				in.close();
+			while (true) {
+				int read = in.read(buf);
+				if (read == -1) break;
+				out.write(buf, 0, read);
 			}
-		} finally {
+			in.close();
+			out.flush();
 			out.close();
+		} catch (IOException e) {
+			tryToClose(in);
+			tryToClose(out);
 		}
 	}
 
+	private static void tryToClose(Closeable c) {
+		try {
+			if (c != null) c.close();
+		} catch (IOException e) {
+			// We did our best
+		}
+	}
+
+	public static void read(InputStream in, byte[] b) throws IOException {
+		int offset = 0;
+		while (offset < b.length) {
+			int read = in.read(b, offset, b.length - offset);
+			if (read == -1) throw new EOFException();
+			offset += read;
+		}
+	}
 }