From 909bf4cab1dfd62a8365dc7c48aa4fb866321cec Mon Sep 17 00:00:00 2001 From: danda Date: Mon, 25 Jul 2016 17:04:18 -0700 Subject: [PATCH] route HttpClient over Socks5 proxy. requires apache http client. work in progress. --- network/pom.xml | 7 ++ .../io/bitsquare/http/FakeDnsResolver.java | 15 ++++ .../java/io/bitsquare/http/HttpClient.java | 83 ++++++++++++++++++- .../http/SocksConnectionSocketFactory.java | 38 +++++++++ .../http/SocksSSLConnectionSocketFactory.java | 48 +++++++++++ 5 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 network/src/main/java/io/bitsquare/http/FakeDnsResolver.java create mode 100644 network/src/main/java/io/bitsquare/http/SocksConnectionSocketFactory.java create mode 100644 network/src/main/java/io/bitsquare/http/SocksSSLConnectionSocketFactory.java diff --git a/network/pom.xml b/network/pom.xml index 34362cce97..371c1de791 100644 --- a/network/pom.xml +++ b/network/pom.xml @@ -23,5 +23,12 @@ jtorproxy ${project.parent.version} + + + org.apache.httpcomponents + httpclient + 4.5 + + \ No newline at end of file diff --git a/network/src/main/java/io/bitsquare/http/FakeDnsResolver.java b/network/src/main/java/io/bitsquare/http/FakeDnsResolver.java new file mode 100644 index 0000000000..aa04bdb7c7 --- /dev/null +++ b/network/src/main/java/io/bitsquare/http/FakeDnsResolver.java @@ -0,0 +1,15 @@ +package io.bitsquare.http; + +import org.apache.http.conn.DnsResolver; +import java.net.InetAddress; +import java.net.UnknownHostException; + +// This class is adapted from +// http://stackoverflow.com/a/25203021/5616248 +class FakeDnsResolver implements DnsResolver { + @Override + public InetAddress[] resolve(String host) throws UnknownHostException { + // Return some fake DNS record for every request, we won't be using it + return new InetAddress[] { InetAddress.getByAddress(new byte[] { 1, 1, 1, 1 }) }; + } +} \ No newline at end of file diff --git a/network/src/main/java/io/bitsquare/http/HttpClient.java b/network/src/main/java/io/bitsquare/http/HttpClient.java index 0c01425274..bda8bfde6e 100644 --- a/network/src/main/java/io/bitsquare/http/HttpClient.java +++ b/network/src/main/java/io/bitsquare/http/HttpClient.java @@ -1,24 +1,58 @@ package io.bitsquare.http; +import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy; import java.io.BufferedReader; -import java.io.IOException; +import java.io.InputStream; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.IOException; import java.net.HttpURLConnection; +import java.net.InetSocketAddress; import java.net.URL; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLContexts; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + -// TODO route over tor public class HttpClient { + private static final Logger log = LoggerFactory.getLogger(HttpClient.class); + private final String baseUrl; + private final Socks5Proxy proxy; public HttpClient(String baseUrl) { this.baseUrl = baseUrl; + this.proxy = null; + } + + public HttpClient(Socks5Proxy proxy, String baseUrl) { + this.baseUrl = baseUrl; + this.proxy = proxy; } public String requestWithGET(String param) throws IOException, HttpException { + return requestWithGETProxy(param); +// return proxy != null ? requestWithGETProxy(param) : requestWithGETNoProxy(param); + } + + /** + * Make an HTTP Get request directly (not routed over socks5 proxy). + */ + private String requestWithGETNoProxy(String param) throws IOException, HttpException { HttpURLConnection connection = null; try { + log.info( "Executing HTTP request " + baseUrl + param + " proxy: none."); URL url = new URL(baseUrl + param); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); @@ -37,6 +71,51 @@ public class HttpClient { connection.getInputStream().close(); } } + + /** + * Make an HTTP Get request routed over socks5 proxy. + */ + private String requestWithGETProxy(String param) throws IOException, HttpException { + + // This code is adapted from: + // http://stackoverflow.com/a/25203021/5616248 + + // Register our own SocketFactories to override createSocket() and connectSocket(). + // connectSocket does NOT resolve hostname before passing it to proxy. + Registry reg = RegistryBuilder. create() + .register("http", new SocksConnectionSocketFactory()) + .register("https", new SocksSSLConnectionSocketFactory(SSLContexts.createSystemDefault())).build(); + + // Use FakeDNSResolver if not resolving DNS locally. + // This prevents a local DNS lookup (which would be ignored anyway) + PoolingHttpClientConnectionManager cm = proxy.resolveAddrLocally() ? + new PoolingHttpClientConnectionManager(reg) : + new PoolingHttpClientConnectionManager(reg, new FakeDnsResolver()); + CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(cm).build(); + try { + // InetSocketAddress socksaddr = new InetSocketAddress(proxy.getInetAddress(), proxy.getPort()); + + // remove me: Use this to test with system-wide Tor proxy. + InetSocketAddress socksaddr = new InetSocketAddress("127.0.0.1", 9050); + + HttpClientContext context = HttpClientContext.create(); + context.setAttribute("socks.address", socksaddr); + + HttpGet request = new HttpGet(baseUrl + param); + + log.error( "Executing request " + request + " proxy: " + socksaddr); + CloseableHttpResponse response = httpclient.execute(request, context); + try { + InputStream stream = response.getEntity().getContent(); + String buf = convertInputStreamToString(stream); + return buf; + } finally { + response.close(); + } + } finally { + httpclient.close(); + } + } private String convertInputStreamToString(InputStream inputStream) throws IOException { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); diff --git a/network/src/main/java/io/bitsquare/http/SocksConnectionSocketFactory.java b/network/src/main/java/io/bitsquare/http/SocksConnectionSocketFactory.java new file mode 100644 index 0000000000..f7e6761f8a --- /dev/null +++ b/network/src/main/java/io/bitsquare/http/SocksConnectionSocketFactory.java @@ -0,0 +1,38 @@ +package io.bitsquare.http; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.HttpHost; +import org.apache.http.protocol.HttpContext; + +// This class is adapted from +// http://stackoverflow.com/a/25203021/5616248 +// +// This class routes connections over Socks, and avoids resolving hostnames locally. +class SocksConnectionSocketFactory extends PlainConnectionSocketFactory { + + /** + * creates an unconnected Socks Proxy socket + */ + @Override + public Socket createSocket(final HttpContext context) throws IOException { + InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socks.address"); + Proxy proxy = new Proxy(Proxy.Type.SOCKS, socksaddr); + return new Socket(proxy); + } + + /** + * connects a Socks Proxy socket and passes hostname to proxy without resolving it locally. + */ + @Override + public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress, + InetSocketAddress localAddress, HttpContext context) throws IOException { + // Convert address to unresolved + InetSocketAddress unresolvedRemote = InetSocketAddress + .createUnresolved(host.getHostName(), remoteAddress.getPort()); + return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress, context); + } +} diff --git a/network/src/main/java/io/bitsquare/http/SocksSSLConnectionSocketFactory.java b/network/src/main/java/io/bitsquare/http/SocksSSLConnectionSocketFactory.java new file mode 100644 index 0000000000..6281bf3b54 --- /dev/null +++ b/network/src/main/java/io/bitsquare/http/SocksSSLConnectionSocketFactory.java @@ -0,0 +1,48 @@ +package io.bitsquare.http; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; +import javax.net.ssl.SSLContext; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.HttpHost; +import org.apache.http.protocol.HttpContext; + +// This class is adapted from +// http://stackoverflow.com/a/25203021/5616248 +// +// This class routes connections over Socks, and avoids resolving hostnames locally. +class SocksSSLConnectionSocketFactory extends SSLConnectionSocketFactory { + + public SocksSSLConnectionSocketFactory(final SSLContext sslContext) { + + // Only allow connection's to site's with valid certs. + super(sslContext, STRICT_HOSTNAME_VERIFIER); + + // Or to allow insecure (eg self-signed certs) + // super(sslContext, ALLOW_ALL_HOSTNAME_VERIFIER); + } + + /** + * creates an unconnected Socks Proxy socket + */ + @Override + public Socket createSocket(final HttpContext context) throws IOException { + InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socks.address"); + Proxy proxy = new Proxy(Proxy.Type.SOCKS, socksaddr); + return new Socket(proxy); + } + + /** + * connects a Socks Proxy socket and passes hostname to proxy without resolving it locally. + */ + @Override + public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress, + InetSocketAddress localAddress, HttpContext context) throws IOException { + // Convert address to unresolved + InetSocketAddress unresolvedRemote = InetSocketAddress + .createUnresolved(host.getHostName(), remoteAddress.getPort()); + return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress, context); + } +} \ No newline at end of file