Skip to content

Commit

Permalink
Add proxy support for client socket connections
Browse files Browse the repository at this point in the history
Related issue: netty#1133

Motivation:

There is no support for client socket connections via a proxy server in
Netty.

Modifications:

- Add a new module 'handler-proxy'
- Add ProxyHandler and its subclasses to support SOCKS 4a/5 and HTTP(S)
  proxy connections
- Add a full parameterized test for most scenarios
- Clean up pom.xml

Result:

A user can make an outgoing connection via proxy servers with only
trivial effort.
  • Loading branch information
trustin committed Oct 14, 2014
1 parent e4ab108 commit de9c81b
Show file tree
Hide file tree
Showing 26 changed files with 2,648 additions and 27 deletions.
7 changes: 7 additions & 0 deletions all/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,13 @@
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-handler-proxy</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-transport</artifactId>
Expand Down
5 changes: 0 additions & 5 deletions codec-dns/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@
<artifactId>netty-codec</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-handler</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.apache.directory.server</groupId>
Expand Down
1 change: 1 addition & 0 deletions codec-http/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<groupId>${project.groupId}</groupId>
<artifactId>netty-handler</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
Expand Down
5 changes: 0 additions & 5 deletions codec-memcache/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@
<artifactId>netty-codec</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-handler</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

6 changes: 1 addition & 5 deletions codec-mqtt/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,7 @@
<artifactId>netty-codec</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-handler</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
Expand Down
5 changes: 0 additions & 5 deletions codec-socks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@
<artifactId>netty-codec</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-handler</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> o
msg = new Socks4CmdResponse(cmdStatus, host, port);
}
}
ctx.pipeline().remove(this);
out.add(msg);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> o
}
}
}
ctx.pipeline().remove(this);
out.add(msg);
}

Expand Down
5 changes: 0 additions & 5 deletions codec-stomp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@
<artifactId>netty-codec</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-handler</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

5 changes: 5 additions & 0 deletions example/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
<artifactId>netty-handler</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-handler-proxy</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-codec-http</artifactId>
Expand Down
55 changes: 55 additions & 0 deletions handler-proxy/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2014 The Netty Project
~
~ The Netty Project licenses this file to you under the Apache License,
~ version 2.0 (the "License"); you may not use this file except in compliance
~ with the License. You may obtain a copy of the License at:
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
~ License for the specific language governing permissions and limitations
~ under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.netty</groupId>
<artifactId>netty-parent</artifactId>
<version>5.0.0.Alpha2-SNAPSHOT</version>
</parent>

<artifactId>netty-handler-proxy</artifactId>
<packaging>jar</packaging>

<name>Netty/Handler/Proxy</name>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-transport</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-codec-socks</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-codec-http</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-handler</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package io.netty.handler.proxy;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.base64.Base64;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.util.CharsetUtil;

import java.net.InetSocketAddress;
import java.net.SocketAddress;

public final class HttpProxyHandler extends ProxyHandler {

private static final String PROTOCOL = "http";
private static final String AUTH_BASIC = "basic";

private final HttpClientCodec codec = new HttpClientCodec();
private final String username;
private final String password;
private final CharSequence authorization;
private HttpResponseStatus status;

public HttpProxyHandler(SocketAddress proxyAddress) {
super(proxyAddress);
username = null;
password = null;
authorization = null;
}

public HttpProxyHandler(SocketAddress proxyAddress, String username, String password) {
super(proxyAddress);
if (username == null) {
throw new NullPointerException("username");
}
if (password == null) {
throw new NullPointerException("password");
}
this.username = username;
this.password = password;

ByteBuf authz = Unpooled.copiedBuffer(username + ':' + password, CharsetUtil.UTF_8);
ByteBuf authzBase64 = Base64.encode(authz, false);

authorization = new AsciiString(authzBase64.toString(CharsetUtil.US_ASCII));

authz.release();
authzBase64.release();
}

@Override
public String protocol() {
return PROTOCOL;
}

@Override
public String authScheme() {
return authorization != null? AUTH_BASIC : AUTH_NONE;
}

public String username() {
return username;
}

public String password() {
return password;
}

@Override
protected void addCodec(ChannelHandlerContext ctx) throws Exception {
ChannelPipeline p = ctx.pipeline();
String name = ctx.name();
p.addBefore(name, null, codec);
}

@Override
protected void removeEncoder(ChannelHandlerContext ctx) throws Exception {
ctx.pipeline().remove(codec.encoder());
}

@Override
protected void removeDecoder(ChannelHandlerContext ctx) throws Exception {
ctx.pipeline().remove(codec.decoder());
}

@Override
protected Object newInitialMessage(ChannelHandlerContext ctx) throws Exception {
InetSocketAddress raddr = destinationAddress();
String rhost;
if (raddr.isUnresolved()) {
rhost = raddr.getHostString();
} else {
rhost = raddr.getAddress().getHostAddress();
}

FullHttpRequest req = new DefaultFullHttpRequest(
HttpVersion.HTTP_1_0, HttpMethod.CONNECT,
rhost + ':' + raddr.getPort(),
Unpooled.EMPTY_BUFFER, false);

SocketAddress proxyAddress = proxyAddress();
if (proxyAddress instanceof InetSocketAddress) {
InetSocketAddress hostAddr = (InetSocketAddress) proxyAddress;
req.headers().set(Names.HOST, hostAddr.getHostString() + ':' + hostAddr.getPort());
}

if (authorization != null) {
req.headers().set(Names.AUTHORIZATION, authorization);
}

return req;
}

@Override
protected boolean handleResponse(ChannelHandlerContext ctx, Object response) throws Exception {
if (response instanceof HttpResponse) {
if (status != null) {
throw new ProxyConnectException(exceptionMessage("too many responses"));
}
status = ((HttpResponse) response).status();
}

boolean finished = response instanceof LastHttpContent;
if (finished) {
if (status == null) {
throw new ProxyConnectException(exceptionMessage("missing response"));
}
if (status.code() != 200) {
throw new ProxyConnectException(exceptionMessage("status: " + status));
}
}

return finished;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package io.netty.handler.proxy;

import java.net.ConnectException;

public class ProxyConnectException extends ConnectException {
private static final long serialVersionUID = 5211364632246265538L;

public ProxyConnectException() { }

public ProxyConnectException(String msg) {
super(msg);
}

public ProxyConnectException(Throwable cause) {
initCause(cause);
}

public ProxyConnectException(String msg, Throwable cause) {
super(msg);
initCause(cause);
}
}
Loading

0 comments on commit de9c81b

Please sign in to comment.