1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.netty.handler.proxy;
18
19 import io.netty.channel.ChannelHandlerContext;
20 import io.netty.channel.ChannelPipeline;
21 import io.netty.handler.codec.socksx.v5.DefaultSocks5InitialRequest;
22 import io.netty.handler.codec.socksx.v5.DefaultSocks5CommandRequest;
23 import io.netty.handler.codec.socksx.v5.DefaultSocks5PasswordAuthRequest;
24 import io.netty.handler.codec.socksx.v5.Socks5AddressType;
25 import io.netty.handler.codec.socksx.v5.Socks5AuthMethod;
26 import io.netty.handler.codec.socksx.v5.Socks5InitialRequest;
27 import io.netty.handler.codec.socksx.v5.Socks5InitialResponse;
28 import io.netty.handler.codec.socksx.v5.Socks5InitialResponseDecoder;
29 import io.netty.handler.codec.socksx.v5.Socks5ClientEncoder;
30 import io.netty.handler.codec.socksx.v5.Socks5CommandResponse;
31 import io.netty.handler.codec.socksx.v5.Socks5CommandResponseDecoder;
32 import io.netty.handler.codec.socksx.v5.Socks5CommandStatus;
33 import io.netty.handler.codec.socksx.v5.Socks5CommandType;
34 import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthResponse;
35 import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthResponseDecoder;
36 import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthStatus;
37 import io.netty.util.NetUtil;
38 import io.netty.util.internal.StringUtil;
39
40 import java.net.InetSocketAddress;
41 import java.net.SocketAddress;
42 import java.util.Arrays;
43 import java.util.Collections;
44
45 public final class Socks5ProxyHandler extends ProxyHandler {
46
47 private static final String PROTOCOL = "socks5";
48 private static final String AUTH_PASSWORD = "password";
49
50 private static final Socks5InitialRequest INIT_REQUEST_NO_AUTH =
51 new DefaultSocks5InitialRequest(Collections.singletonList(Socks5AuthMethod.NO_AUTH));
52
53 private static final Socks5InitialRequest INIT_REQUEST_PASSWORD =
54 new DefaultSocks5InitialRequest(Arrays.asList(Socks5AuthMethod.NO_AUTH, Socks5AuthMethod.PASSWORD));
55
56 private final String username;
57 private final String password;
58
59 private String decoderName;
60 private String encoderName;
61
62 public Socks5ProxyHandler(SocketAddress proxyAddress) {
63 this(proxyAddress, null, null);
64 }
65
66 public Socks5ProxyHandler(SocketAddress proxyAddress, String username, String password) {
67 super(proxyAddress);
68 if (username != null && username.isEmpty()) {
69 username = null;
70 }
71 if (password != null && password.isEmpty()) {
72 password = null;
73 }
74 this.username = username;
75 this.password = password;
76 }
77
78 @Override
79 public String protocol() {
80 return PROTOCOL;
81 }
82
83 @Override
84 public String authScheme() {
85 return socksAuthMethod() == Socks5AuthMethod.PASSWORD? AUTH_PASSWORD : AUTH_NONE;
86 }
87
88 public String username() {
89 return username;
90 }
91
92 public String password() {
93 return password;
94 }
95
96 @Override
97 protected void addCodec(ChannelHandlerContext ctx) throws Exception {
98 ChannelPipeline p = ctx.pipeline();
99 String name = ctx.name();
100
101 Socks5InitialResponseDecoder decoder = new Socks5InitialResponseDecoder();
102 p.addBefore(name, null, decoder);
103
104 decoderName = p.context(decoder).name();
105 encoderName = decoderName + ".encoder";
106
107 p.addBefore(name, encoderName, Socks5ClientEncoder.DEFAULT);
108 }
109
110 @Override
111 protected void removeEncoder(ChannelHandlerContext ctx) throws Exception {
112 ctx.pipeline().remove(encoderName);
113 }
114
115 @Override
116 protected void removeDecoder(ChannelHandlerContext ctx) throws Exception {
117 ChannelPipeline p = ctx.pipeline();
118 if (p.context(decoderName) != null) {
119 p.remove(decoderName);
120 }
121 }
122
123 @Override
124 protected Object newInitialMessage(ChannelHandlerContext ctx) throws Exception {
125 return socksAuthMethod() == Socks5AuthMethod.PASSWORD? INIT_REQUEST_PASSWORD : INIT_REQUEST_NO_AUTH;
126 }
127
128 @Override
129 protected boolean handleResponse(ChannelHandlerContext ctx, Object response) throws Exception {
130 if (response instanceof Socks5InitialResponse) {
131 Socks5InitialResponse res = (Socks5InitialResponse) response;
132 Socks5AuthMethod authMethod = socksAuthMethod();
133
134 if (res.authMethod() != Socks5AuthMethod.NO_AUTH && res.authMethod() != authMethod) {
135
136 throw new ProxyConnectException(exceptionMessage("unexpected authMethod: " + res.authMethod()));
137 }
138
139 if (authMethod == Socks5AuthMethod.NO_AUTH) {
140 sendConnectCommand(ctx);
141 } else if (authMethod == Socks5AuthMethod.PASSWORD) {
142
143 ctx.pipeline().replace(decoderName, decoderName, new Socks5PasswordAuthResponseDecoder());
144 sendToProxyServer(new DefaultSocks5PasswordAuthRequest(
145 username != null? username : "", password != null? password : ""));
146 } else {
147
148 throw new Error();
149 }
150
151 return false;
152 }
153
154 if (response instanceof Socks5PasswordAuthResponse) {
155
156 Socks5PasswordAuthResponse res = (Socks5PasswordAuthResponse) response;
157 if (res.status() != Socks5PasswordAuthStatus.SUCCESS) {
158 throw new ProxyConnectException(exceptionMessage("authStatus: " + res.status()));
159 }
160
161 sendConnectCommand(ctx);
162 return false;
163 }
164
165
166 Socks5CommandResponse res = (Socks5CommandResponse) response;
167 if (res.status() != Socks5CommandStatus.SUCCESS) {
168 throw new ProxyConnectException(exceptionMessage("status: " + res.status()));
169 }
170
171 return true;
172 }
173
174 private Socks5AuthMethod socksAuthMethod() {
175 Socks5AuthMethod authMethod;
176 if (username == null && password == null) {
177 authMethod = Socks5AuthMethod.NO_AUTH;
178 } else {
179 authMethod = Socks5AuthMethod.PASSWORD;
180 }
181 return authMethod;
182 }
183
184 private void sendConnectCommand(ChannelHandlerContext ctx) throws Exception {
185 InetSocketAddress raddr = destinationAddress();
186 Socks5AddressType addrType;
187 String rhost;
188 if (raddr.isUnresolved()) {
189 addrType = Socks5AddressType.DOMAIN;
190 rhost = raddr.getHostString();
191 } else {
192 rhost = raddr.getAddress().getHostAddress();
193 if (NetUtil.isValidIpV4Address(rhost)) {
194 addrType = Socks5AddressType.IPv4;
195 } else if (NetUtil.isValidIpV6Address(rhost)) {
196 addrType = Socks5AddressType.IPv6;
197 } else {
198 throw new ProxyConnectException(
199 exceptionMessage("unknown address type: " + StringUtil.simpleClassName(rhost)));
200 }
201 }
202
203 ctx.pipeline().replace(decoderName, decoderName, new Socks5CommandResponseDecoder());
204 sendToProxyServer(new DefaultSocks5CommandRequest(Socks5CommandType.CONNECT, addrType, rhost, raddr.getPort()));
205 }
206 }