Skip to content

SOCKS proxy support #1466

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@
<artifactId>netty-transport-native-epoll</artifactId>
<classifier>linux-x86_64</classifier>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-socks</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler-proxy</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-resolver-dns</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.asynchttpclient.channel;

import org.asynchttpclient.proxy.ProxyServer;
import org.asynchttpclient.proxy.ProxyType;
import org.asynchttpclient.uri.Uri;
import org.asynchttpclient.util.HttpUtils;

Expand All @@ -23,12 +24,14 @@ class ProxyPartitionKey {
private final int proxyPort;
private final boolean secured;
private final String targetHostBaseUrl;
private final ProxyType proxyType;

public ProxyPartitionKey(String proxyHost, int proxyPort, boolean secured, String targetHostBaseUrl) {
public ProxyPartitionKey(String proxyHost, int proxyPort, boolean secured, String targetHostBaseUrl, ProxyType proxyType) {
this.proxyHost = proxyHost;
this.proxyPort = proxyPort;
this.secured = secured;
this.targetHostBaseUrl = targetHostBaseUrl;
this.proxyType = proxyType;
}

@Override
Expand All @@ -37,6 +40,7 @@ public int hashCode() {
int result = 1;
result = prime * result + ((proxyHost == null) ? 0 : proxyHost.hashCode());
result = prime * result + proxyPort;
result = prime * result + proxyType.hashCode();
result = prime * result + (secured ? 1231 : 1237);
result = prime * result + ((targetHostBaseUrl == null) ? 0 : targetHostBaseUrl.hashCode());
return result;
Expand All @@ -60,6 +64,8 @@ public boolean equals(Object obj) {
return false;
if (secured != other.secured)
return false;
if (proxyType != other.proxyType)
return false;
if (targetHostBaseUrl == null) {
if (other.targetHostBaseUrl != null)
return false;
Expand All @@ -70,12 +76,13 @@ public boolean equals(Object obj) {

@Override
public String toString() {
return new StringBuilder()//
.append("ProxyPartitionKey(proxyHost=").append(proxyHost)//
.append(", proxyPort=").append(proxyPort)//
.append(", secured=").append(secured)//
.append(", targetHostBaseUrl=").append(targetHostBaseUrl)//
.toString();
return //
"ProxyPartitionKey(proxyHost=" + proxyHost +//
", proxyPort=" + proxyPort +//
", secured=" + secured +//
", targetHostBaseUrl=" + targetHostBaseUrl +//
", proxyType=" + proxyType//
;
}
}

Expand All @@ -89,8 +96,8 @@ public Object getPartitionKey(Uri uri, String virtualHost, ProxyServer proxyServ
String targetHostBaseUrl = virtualHost != null ? virtualHost : HttpUtils.getBaseUrl(uri);
if (proxyServer != null) {
return uri.isSecured() ? //
new ProxyPartitionKey(proxyServer.getHost(), proxyServer.getSecuredPort(), true, targetHostBaseUrl)
: new ProxyPartitionKey(proxyServer.getHost(), proxyServer.getPort(), false, targetHostBaseUrl);
new ProxyPartitionKey(proxyServer.getHost(), proxyServer.getSecuredPort(), true, targetHostBaseUrl, proxyServer.getProxyType())
: new ProxyPartitionKey(proxyServer.getHost(), proxyServer.getPort(), false, targetHostBaseUrl, proxyServer.getProxyType());
} else {
return targetHostBaseUrl;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ public String toString() {
sb.append("\t\t").append(header.getKey()).append(": ").append(header.getValue()).append("\n");
}
sb.append("\tbody=\n").append(getResponseBody()).append("\n")//
.append("}").toString();
.append("}");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! Not related to this change though. Will commit separately.

return sb.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,7 @@

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.*;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
Expand All @@ -34,13 +29,16 @@
import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.proxy.Socks4ProxyHandler;
import io.netty.handler.proxy.Socks5ProxyHandler;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.Timer;
import io.netty.util.concurrent.DefaultThreadFactory;
import io.netty.util.concurrent.GlobalEventExecutor;
import io.netty.util.concurrent.*;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ThreadFactory;
Expand All @@ -51,11 +49,7 @@
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;

import org.asynchttpclient.AsyncHandler;
import org.asynchttpclient.AsyncHttpClientConfig;
import org.asynchttpclient.ClientStats;
import org.asynchttpclient.HostStats;
import org.asynchttpclient.SslEngineFactory;
import org.asynchttpclient.*;
import org.asynchttpclient.channel.ChannelPool;
import org.asynchttpclient.channel.ChannelPoolPartitioning;
import org.asynchttpclient.channel.NoopChannelPool;
Expand All @@ -68,6 +62,7 @@
import org.asynchttpclient.netty.request.NettyRequestSender;
import org.asynchttpclient.netty.ssl.DefaultSslEngineFactory;
import org.asynchttpclient.proxy.ProxyServer;
import org.asynchttpclient.proxy.ProxyType;
import org.asynchttpclient.uri.Uri;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -78,6 +73,7 @@ public class ChannelManager {
public static final String PINNED_ENTRY = "entry";
public static final String HTTP_CLIENT_CODEC = "http";
public static final String SSL_HANDLER = "ssl";
public static final String SOCKS_HANDLER = "socks";
public static final String DEFLATER_HANDLER = "deflater";
public static final String INFLATER_HANDLER = "inflater";
public static final String CHUNKED_WRITER_HANDLER = "chunked-writer";
Expand Down Expand Up @@ -391,8 +387,49 @@ public SslHandler addSslHandler(ChannelPipeline pipeline, Uri uri, String virtua
return sslHandler;
}

public Bootstrap getBootstrap(Uri uri, ProxyServer proxy) {
return uri.isWebSocket() && proxy == null ? wsBootstrap : httpBootstrap;
public Promise<Bootstrap> getBootstrap(Request request, ProxyServer proxy) {
final Promise<Bootstrap> promise = ImmediateEventExecutor.INSTANCE.newPromise();

if (request.getUri().isWebSocket() && proxy == null) {
promise.setSuccess(wsBootstrap);
} else {
if (proxy != null && proxy.getProxyType().isSocks()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please collapse into else if

request.getNameResolver().resolve(proxy.getHost()).addListener((Future<InetAddress> proxyDnsFuture) -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/proxyDnsFuture/whenProxyAddress

if (proxyDnsFuture.isSuccess()) {
Bootstrap bootstrap = httpBootstrap.clone();
ChannelHandler handler = bootstrap.config().handler();

bootstrap.handler(new ChannelInitializer<Channel>() {
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
handler.handlerAdded(ctx);
super.handlerAdded(ctx);
}

@Override
protected void initChannel(Channel channel) throws Exception {
InetSocketAddress proxyAddress = new InetSocketAddress(proxyDnsFuture.get(), proxy.getPort());
if (proxy.getProxyType() == ProxyType.SOCKS_V4) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switch instead of if/else

channel.pipeline().addFirst(ChannelManager.SOCKS_HANDLER, new Socks4ProxyHandler(proxyAddress));
} else if (proxy.getProxyType() == ProxyType.SOCKS_V5) {
channel.pipeline().addFirst(ChannelManager.SOCKS_HANDLER, new Socks5ProxyHandler(proxyAddress));
} else {
throw new IllegalArgumentException("Only SOCKS4 and SOCKS5 supported at the moment.");
}
}
});

promise.setSuccess(bootstrap);
} else {
promise.setFailure(proxyDnsFuture.cause());
}
});
} else {
promise.setSuccess(httpBootstrap);
}
}

return promise;
}

public void upgradePipelineForWebSockets(ChannelPipeline pipeline) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,36 @@
import static org.asynchttpclient.util.HttpUtils.getBaseUrl;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.proxy.Socks4ProxyHandler;
import io.netty.handler.proxy.Socks5ProxyHandler;
import io.netty.handler.ssl.SslHandler;

import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import org.asynchttpclient.Request;
import org.asynchttpclient.RequestBuilderBase;
import org.asynchttpclient.handler.AsyncHandlerExtensions;
import org.asynchttpclient.netty.NettyResponseFuture;
import org.asynchttpclient.netty.SimpleFutureListener;
import org.asynchttpclient.netty.future.StackTraceInspector;
import org.asynchttpclient.netty.request.NettyRequestSender;
import org.asynchttpclient.netty.timeout.TimeoutsHolder;
import org.asynchttpclient.proxy.ProxyServer;
import org.asynchttpclient.proxy.ProxyType;
import org.asynchttpclient.uri.Uri;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLException;

/**
* Non Blocking connect.
*/
Expand Down Expand Up @@ -94,12 +106,7 @@ public void onSuccess(Channel channel, InetSocketAddress remoteAddress) {
Object partitionKeyLock = future.takePartitionKeyLock();

if (partitionKeyLock != null) {
channel.closeFuture().addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
connectionSemaphore.releaseChannelLock(partitionKeyLock);
}
});
channel.closeFuture().addListener(future -> connectionSemaphore.releaseChannelLock(partitionKeyLock));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, but not related to this feature. Please revert (I'll push it right away).

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import org.asynchttpclient.netty.request.body.NettyMultipartBody;
import org.asynchttpclient.netty.request.body.NettyReactiveStreamsBody;
import org.asynchttpclient.proxy.ProxyServer;
import org.asynchttpclient.proxy.ProxyType;
import org.asynchttpclient.request.body.generator.FileBodyGenerator;
import org.asynchttpclient.request.body.generator.InputStreamBodyGenerator;
import org.asynchttpclient.request.body.generator.ReactiveStreamsBodyGenerator;
Expand Down Expand Up @@ -237,7 +238,7 @@ private String requestUri(Uri uri, ProxyServer proxyServer, boolean connect) {
// proxy tunnelling, connect need host and explicit port
return getAuthority(uri);

} else if (proxyServer != null && !uri.isSecured()) {
} else if (proxyServer != null && !uri.isSecured() && proxyServer.getProxyType() == ProxyType.HTTP) {
// proxy over HTTP, need full url
return uri.toUrl();

Expand Down
Loading