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.DefaultSocks5PrivateAuthRequest;
25 import io.netty.handler.codec.socksx.v5.Socks5AddressType;
26 import io.netty.handler.codec.socksx.v5.Socks5AuthMethod;
27 import io.netty.handler.codec.socksx.v5.Socks5InitialRequest;
28 import io.netty.handler.codec.socksx.v5.Socks5InitialResponse;
29 import io.netty.handler.codec.socksx.v5.Socks5InitialResponseDecoder;
30 import io.netty.handler.codec.socksx.v5.Socks5ClientEncoder;
31 import io.netty.handler.codec.socksx.v5.Socks5CommandResponse;
32 import io.netty.handler.codec.socksx.v5.Socks5CommandResponseDecoder;
33 import io.netty.handler.codec.socksx.v5.Socks5CommandStatus;
34 import io.netty.handler.codec.socksx.v5.Socks5CommandType;
35 import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthResponse;
36 import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthResponseDecoder;
37 import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthStatus;
38 import io.netty.handler.codec.socksx.v5.Socks5PrivateAuthResponse;
39 import io.netty.handler.codec.socksx.v5.Socks5PrivateAuthResponseDecoder;
40 import io.netty.handler.codec.socksx.v5.Socks5PrivateAuthStatus;
41 import io.netty.util.NetUtil;
42 import io.netty.util.internal.StringUtil;
43
44 import java.net.InetSocketAddress;
45 import java.net.SocketAddress;
46 import java.util.Arrays;
47 import java.util.Collections;
48
49
50
51
52
53 public final class Socks5ProxyHandler extends ProxyHandler {
54
55 private static final String PROTOCOL = "socks5";
56 private static final String AUTH_PASSWORD = "password";
57 private static final String AUTH_PRIVATE = "private";
58
59 private static final byte NO_PRIVATE_AUTH_METHOD =
60 Socks5AuthMethod.NO_AUTH.byteValue();
61
62 private static final Socks5InitialRequest INIT_REQUEST_NO_AUTH =
63 new DefaultSocks5InitialRequest(Collections.singletonList(Socks5AuthMethod.NO_AUTH));
64
65 private static final Socks5InitialRequest INIT_REQUEST_PASSWORD =
66 new DefaultSocks5InitialRequest(Arrays.asList(Socks5AuthMethod.NO_AUTH, Socks5AuthMethod.PASSWORD));
67
68 private final String username;
69 private final String password;
70 private final byte privateAuthMethod;
71 private final byte[] privateToken;
72 private final Socks5ClientEncoder clientEncoder;
73
74 private String decoderName;
75 private String encoderName;
76
77 public Socks5ProxyHandler(SocketAddress proxyAddress) {
78 this(proxyAddress, null, null);
79 }
80
81 public Socks5ProxyHandler(SocketAddress proxyAddress, String username, String password) {
82 super(proxyAddress);
83 if (username != null && username.isEmpty()) {
84 username = null;
85 }
86 if (password != null && password.isEmpty()) {
87 password = null;
88 }
89 this.username = username;
90 this.password = password;
91 this.privateToken = null;
92 this.privateAuthMethod = NO_PRIVATE_AUTH_METHOD;
93 this.clientEncoder = Socks5ClientEncoder.DEFAULT;
94 }
95
96
97
98
99
100
101
102
103
104
105
106 public Socks5ProxyHandler(SocketAddress proxyAddress, byte privateAuthMethod, byte[] privateToken,
107 Socks5ClientEncoder customEncoder) {
108 super(proxyAddress);
109 if (!Socks5AuthMethod.isPrivateMethod(privateAuthMethod)) {
110 throw new IllegalArgumentException(
111 "privateAuthMethod: " + (privateAuthMethod & 0xFF) + " (expected: 0x80-0xFE)");
112 }
113 this.username = this.password = null;
114 this.privateToken = privateToken;
115 this.privateAuthMethod = privateAuthMethod;
116 this.clientEncoder = customEncoder != null ? customEncoder : Socks5ClientEncoder.DEFAULT;
117 }
118
119 @Override
120 public String protocol() {
121 return PROTOCOL;
122 }
123
124 @Override
125 public String authScheme() {
126 Socks5AuthMethod authMethod = socksAuthMethod();
127 if (Socks5AuthMethod.isPrivateMethod(authMethod.byteValue())) {
128 return AUTH_PRIVATE;
129 }
130 if (authMethod == Socks5AuthMethod.PASSWORD) {
131 return AUTH_PASSWORD;
132 }
133 return AUTH_NONE;
134 }
135
136 public String username() {
137 return username;
138 }
139
140 public String password() {
141 return password;
142 }
143
144 @Override
145 protected void addCodec(ChannelHandlerContext ctx) throws Exception {
146 ChannelPipeline p = ctx.pipeline();
147 String name = ctx.name();
148
149 Socks5InitialResponseDecoder decoder = new Socks5InitialResponseDecoder();
150 p.addBefore(name, null, decoder);
151
152 decoderName = p.context(decoder).name();
153 encoderName = decoderName + ".encoder";
154
155 p.addBefore(name, encoderName, clientEncoder);
156 }
157
158 @Override
159 protected void removeEncoder(ChannelHandlerContext ctx) throws Exception {
160 ctx.pipeline().remove(encoderName);
161 }
162
163 @Override
164 protected void removeDecoder(ChannelHandlerContext ctx) throws Exception {
165 ChannelPipeline p = ctx.pipeline();
166 if (p.context(decoderName) != null) {
167 p.remove(decoderName);
168 }
169 }
170
171 @Override
172 protected Object newInitialMessage(ChannelHandlerContext ctx) throws Exception {
173 Socks5AuthMethod authMethod = socksAuthMethod();
174 if (authMethod == Socks5AuthMethod.PASSWORD) {
175 return INIT_REQUEST_PASSWORD;
176 }
177 if (Socks5AuthMethod.isPrivateMethod(authMethod.byteValue())) {
178 return new DefaultSocks5InitialRequest(Arrays.asList(Socks5AuthMethod.NO_AUTH,
179 authMethod));
180 }
181 return INIT_REQUEST_NO_AUTH;
182 }
183
184 @Override
185 protected boolean handleResponse(ChannelHandlerContext ctx, Object response) throws Exception {
186 if (response instanceof Socks5InitialResponse) {
187 Socks5InitialResponse res = (Socks5InitialResponse) response;
188 Socks5AuthMethod authMethod = socksAuthMethod();
189 Socks5AuthMethod resAuthMethod = res.authMethod();
190 if (resAuthMethod != Socks5AuthMethod.NO_AUTH && resAuthMethod != authMethod
191 && !Socks5AuthMethod.isPrivateMethod(resAuthMethod.byteValue())) {
192
193 throw new ProxyConnectException(exceptionMessage("unexpected authMethod: " + res.authMethod()));
194 }
195
196 if (resAuthMethod == Socks5AuthMethod.NO_AUTH) {
197 sendConnectCommand(ctx);
198 } else if (resAuthMethod == Socks5AuthMethod.PASSWORD) {
199
200 ctx.pipeline().replace(decoderName, decoderName, new Socks5PasswordAuthResponseDecoder());
201 sendToProxyServer(new DefaultSocks5PasswordAuthRequest(
202 username != null? username : "", password != null? password : ""));
203 } else if (Socks5AuthMethod.isPrivateMethod(resAuthMethod.byteValue())) {
204 ctx.pipeline().replace(decoderName, decoderName, new Socks5PrivateAuthResponseDecoder());
205 sendToProxyServer(new DefaultSocks5PrivateAuthRequest(privateToken));
206 } else {
207
208 throw new Error();
209 }
210
211 return false;
212 }
213
214 if (response instanceof Socks5PasswordAuthResponse) {
215
216 Socks5PasswordAuthResponse res = (Socks5PasswordAuthResponse) response;
217 if (res.status() != Socks5PasswordAuthStatus.SUCCESS) {
218 throw new ProxyConnectException(exceptionMessage("authStatus: " + res.status()));
219 }
220
221 sendConnectCommand(ctx);
222 return false;
223 }
224
225 if (response instanceof Socks5PrivateAuthResponse) {
226 Socks5PrivateAuthResponse res = (Socks5PrivateAuthResponse) response;
227 if (res.status() != Socks5PrivateAuthStatus.SUCCESS) {
228 throw new ProxyConnectException(exceptionMessage("privateAuthStatus: " + res.status()));
229 }
230
231 sendConnectCommand(ctx);
232 return false;
233 }
234
235
236 Socks5CommandResponse res = (Socks5CommandResponse) response;
237 if (res.status() != Socks5CommandStatus.SUCCESS) {
238 throw new ProxyConnectException(exceptionMessage("status: " + res.status()));
239 }
240
241 return true;
242 }
243
244 private Socks5AuthMethod socksAuthMethod() {
245 Socks5AuthMethod authMethod;
246 if (privateToken != null && privateToken.length > 0) {
247 authMethod = new Socks5AuthMethod(privateAuthMethod & 0xFF, "PRIVATE_" + (privateAuthMethod & 0xFF));
248 } else if (username == null && password == null) {
249 authMethod = Socks5AuthMethod.NO_AUTH;
250 } else {
251 authMethod = Socks5AuthMethod.PASSWORD;
252 }
253 return authMethod;
254 }
255
256 private void sendConnectCommand(ChannelHandlerContext ctx) throws Exception {
257 InetSocketAddress raddr = destinationAddress();
258 Socks5AddressType addrType;
259 String rhost;
260 if (raddr.isUnresolved()) {
261 addrType = Socks5AddressType.DOMAIN;
262 rhost = raddr.getHostString();
263 } else {
264 rhost = raddr.getAddress().getHostAddress();
265 if (NetUtil.isValidIpV4Address(rhost)) {
266 addrType = Socks5AddressType.IPv4;
267 } else if (NetUtil.isValidIpV6Address(rhost)) {
268 addrType = Socks5AddressType.IPv6;
269 } else {
270 throw new ProxyConnectException(
271 exceptionMessage("unknown address type: " + StringUtil.simpleClassName(rhost)));
272 }
273 }
274
275 ctx.pipeline().replace(decoderName, decoderName, new Socks5CommandResponseDecoder());
276 sendToProxyServer(new DefaultSocks5CommandRequest(Socks5CommandType.CONNECT, addrType, rhost, raddr.getPort()));
277 }
278 }