1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.hc.core5.testing;
28
29 import java.io.DataInputStream;
30 import java.io.DataOutputStream;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.OutputStream;
34 import java.net.InetAddress;
35 import java.net.ServerSocket;
36 import java.net.Socket;
37 import java.net.SocketAddress;
38 import java.util.ArrayList;
39 import java.util.List;
40
41 import org.apache.hc.core5.net.InetAddressUtils;
42 import org.apache.hc.core5.util.TimeValue;
43
44
45
46
47 public class SocksProxy {
48
49 private static class SocksProxyHandler {
50
51 public static final int VERSION_5 = 5;
52 public static final int COMMAND_CONNECT = 1;
53 public static final int ATYP_DOMAINNAME = 3;
54
55 private final SocksProxy parent;
56 private final Socket socket;
57 private volatile Socket remote;
58
59 public SocksProxyHandler(final SocksProxy parent, final Socket socket) {
60 this.parent = parent;
61 this.socket = socket;
62 }
63
64 public void start() {
65 new Thread(new Runnable() {
66 @Override
67 public void run() {
68 try {
69 final DataInputStream input = new DataInputStream(socket.getInputStream());
70 final DataOutputStream output = new DataOutputStream(socket.getOutputStream());
71 final Socket target = establishConnection(input, output);
72 remote = target;
73
74 final Thread t1 = pumpStream(input, target.getOutputStream());
75 final Thread t2 = pumpStream(target.getInputStream(), output);
76 try {
77 t1.join();
78 } catch (final InterruptedException e) {
79 }
80 try {
81 t2.join();
82 } catch (final InterruptedException e) {
83 }
84 } catch (final IOException e) {
85 } finally {
86 parent.cleanupSocksProxyHandler(SocksProxyHandler.this);
87 }
88 }
89
90 private Socket establishConnection(final DataInputStream input, final DataOutputStream output) throws IOException {
91 final int clientVersion = input.readUnsignedByte();
92 if (clientVersion != VERSION_5) {
93 throw new IOException("SOCKS implementation only supports version 5");
94 }
95 final int nMethods = input.readUnsignedByte();
96 for (int i = 0; i < nMethods; i++) {
97 input.readUnsignedByte();
98 }
99
100 output.writeByte(VERSION_5);
101 output.writeByte(0);
102 output.flush();
103
104 input.readUnsignedByte();
105 final int command = input.readUnsignedByte();
106 if (command != COMMAND_CONNECT) {
107 throw new IOException("SOCKS implementation only supports CONNECT command");
108 }
109 input.readUnsignedByte();
110
111 final String targetHost;
112 final byte[] targetAddress;
113 final int addressType = input.readUnsignedByte();
114 switch (addressType) {
115 case InetAddressUtils.IPV4:
116 targetHost = null;
117 targetAddress = new byte[4];
118 for (int i = 0; i < targetAddress.length; i++) {
119 targetAddress[i] = input.readByte();
120 }
121 break;
122 case InetAddressUtils.IPV6:
123 targetHost = null;
124 targetAddress = new byte[16];
125 for (int i = 0; i < targetAddress.length; i++) {
126 targetAddress[i] = input.readByte();
127 }
128 break;
129 case ATYP_DOMAINNAME:
130 final int length = input.readUnsignedByte();
131 final StringBuilder domainname = new StringBuilder();
132 for (int i = 0; i < length; i++) {
133 domainname.append((char) input.readUnsignedByte());
134 }
135 targetHost = domainname.toString();
136 targetAddress = null;
137 break;
138 default:
139 throw new IOException("Unsupported address type: " + addressType);
140 }
141
142 final int targetPort = input.readUnsignedShort();
143 final Socket target;
144 if (targetHost != null) {
145 target = new Socket(targetHost, targetPort);
146 } else {
147 target = new Socket(InetAddress.getByAddress(targetAddress), targetPort);
148 }
149
150 output.writeByte(VERSION_5);
151 output.writeByte(0);
152 output.writeByte(0);
153 final byte[] localAddress = target.getLocalAddress().getAddress();
154 if (localAddress.length == 4) {
155 output.writeByte(InetAddressUtils.IPV4);
156 } else if (localAddress.length == 16) {
157 output.writeByte(InetAddressUtils.IPV6);
158 } else {
159 throw new IOException("Unsupported localAddress byte length: " + localAddress.length);
160 }
161 output.write(localAddress);
162 output.writeShort(target.getLocalPort());
163 output.flush();
164
165 return target;
166 }
167
168 private Thread pumpStream(final InputStream input, final OutputStream output) {
169 final Thread t = new Thread(() -> {
170 final byte[] buffer = new byte[1024 * 8];
171 try {
172 while (true) {
173 final int read = input.read(buffer);
174 if (read < 0) {
175 break;
176 }
177 output.write(buffer, 0, read);
178 output.flush();
179 }
180 } catch (final IOException e) {
181 } finally {
182 shutdown();
183 }
184 });
185 t.start();
186 return t;
187 }
188
189 }).start();
190 }
191
192 public void shutdown() {
193 try {
194 this.socket.close();
195 } catch (final IOException e) {
196 }
197 if (this.remote != null) {
198 try {
199 this.remote.close();
200 } catch (final IOException e) {
201 }
202 }
203 }
204
205 }
206
207 private final int port;
208
209 private final List<SocksProxyHandler> handlers = new ArrayList<>();
210 private ServerSocket server;
211 private Thread serverThread;
212
213 public SocksProxy() {
214 this(0);
215 }
216
217 public SocksProxy(final int port) {
218 this.port = port;
219 }
220
221 public synchronized void start() throws IOException {
222 if (this.server == null) {
223 this.server = new ServerSocket(this.port);
224 this.serverThread = new Thread(() -> {
225 try {
226 while (true) {
227 final Socket socket = server.accept();
228 startSocksProxyHandler(socket);
229 }
230 } catch (final IOException e) {
231 } finally {
232 if (server != null) {
233 try {
234 server.close();
235 } catch (final IOException e) {
236 }
237 server = null;
238 }
239 }
240 });
241 this.serverThread.start();
242 }
243 }
244
245 public void shutdown(final TimeValue timeout) throws InterruptedException {
246 final long waitUntil = System.currentTimeMillis() + timeout.toMilliseconds();
247 Thread t = null;
248 synchronized (this) {
249 if (this.server != null) {
250 try {
251 this.server.close();
252 } catch (final IOException e) {
253 } finally {
254 this.server = null;
255 }
256 t = this.serverThread;
257 this.serverThread = null;
258 }
259 for (final SocksProxyHandler handler : this.handlers) {
260 handler.shutdown();
261 }
262 while (!this.handlers.isEmpty()) {
263 final long waitTime = waitUntil - System.currentTimeMillis();
264 if (waitTime > 0) {
265 wait(waitTime);
266 }
267 }
268 }
269 if (t != null) {
270 final long waitTime = waitUntil - System.currentTimeMillis();
271 if (waitTime > 0) {
272 t.join(waitTime);
273 }
274 }
275 }
276
277 protected void startSocksProxyHandler(final Socket socket) {
278 final SocksProxyHandler handler = new SocksProxyHandler(this, socket);
279 synchronized (this) {
280 this.handlers.add(handler);
281 }
282 handler.start();
283 }
284
285 protected synchronized void cleanupSocksProxyHandler(final SocksProxyHandler handler) {
286 this.handlers.remove(handler);
287 }
288
289 public SocketAddress getProxyAddress() {
290 return this.server.getLocalSocketAddress();
291 }
292
293 }