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