0
|
1 package net.sourceforge.jsocks;
|
|
2 import java.io.EOFException;
|
|
3 import java.io.IOException;
|
|
4 import java.io.InputStream;
|
|
5 import java.io.InterruptedIOException;
|
|
6 import java.io.OutputStream;
|
|
7 import java.io.PrintStream;
|
|
8 import java.io.PushbackInputStream;
|
|
9 import java.net.ConnectException;
|
|
10 import java.net.InetAddress;
|
|
11 import java.net.NoRouteToHostException;
|
|
12 import java.net.ServerSocket;
|
|
13 import java.net.Socket;
|
|
14
|
|
15 import net.sourceforge.jsocks.server.ServerAuthenticator;
|
|
16
|
|
17 /**
|
|
18 SOCKS4 and SOCKS5 proxy, handles both protocols simultaniously.
|
|
19 Implements all SOCKS commands, including UDP relaying.
|
|
20 <p>
|
|
21 In order to use it you will need to implement ServerAuthenticator
|
|
22 interface. There is an implementation of this interface which does
|
|
23 no authentication ServerAuthenticatorNone, but it is very dangerous
|
|
24 to use, as it will give access to your local network to anybody
|
|
25 in the world. One should never use this authentication scheme unless
|
|
26 one have pretty good reason to do so.
|
|
27 There is a couple of other authentication schemes in socks.server package.
|
|
28 @see socks.server.ServerAuthenticator
|
|
29 */
|
|
30 public class ProxyServer implements Runnable {
|
|
31
|
|
32 ServerAuthenticator auth;
|
|
33 ProxyMessage msg = null;
|
|
34
|
|
35 Socket sock = null, remote_sock = null;
|
|
36 ServerSocket ss = null;
|
|
37 UDPRelayServer relayServer = null;
|
|
38 InputStream in, remote_in;
|
|
39 OutputStream out, remote_out;
|
|
40
|
|
41 int mode;
|
|
42 static final int START_MODE = 0;
|
|
43 static final int ACCEPT_MODE = 1;
|
|
44 static final int PIPE_MODE = 2;
|
|
45 static final int ABORT_MODE = 3;
|
|
46
|
|
47 static final int BUF_SIZE = 8192;
|
|
48
|
|
49 Thread pipe_thread1, pipe_thread2;
|
|
50 long lastReadTime;
|
|
51
|
|
52 protected static int iddleTimeout = 180000; //3 minutes
|
|
53 static int acceptTimeout = 180000; //3 minutes
|
|
54
|
|
55 static PrintStream log = null;
|
|
56 static Proxy proxy;
|
|
57
|
|
58
|
|
59 //Public Constructors
|
|
60 /////////////////////
|
|
61
|
|
62
|
|
63 /**
|
|
64 Creates a proxy server with given Authentication scheme.
|
|
65 @param auth Authentication scheme to be used.
|
|
66 */
|
|
67 public ProxyServer(ServerAuthenticator auth) {
|
|
68 this.auth = auth;
|
|
69 }
|
|
70
|
|
71 //Other constructors
|
|
72 ////////////////////
|
|
73
|
|
74 protected ProxyServer(ServerAuthenticator auth, Socket s) {
|
|
75 this.auth = auth;
|
|
76 this.sock = s;
|
|
77 mode = START_MODE;
|
|
78 }
|
|
79
|
|
80 //Public methods
|
|
81 /////////////////
|
|
82
|
|
83 /**
|
|
84 Set the logging stream. Specifying null disables logging.
|
|
85 */
|
|
86 public static void setLog(OutputStream out) {
|
|
87 if (out == null) {
|
|
88 log = null;
|
|
89 }
|
|
90 else {
|
|
91 log = new PrintStream(out, true);
|
|
92 }
|
|
93
|
|
94 UDPRelayServer.log = log;
|
|
95 }
|
|
96
|
|
97 /**
|
|
98 Set proxy.
|
|
99 <p>
|
|
100 Allows Proxy chaining so that one Proxy server is connected to another
|
|
101 and so on. If proxy supports SOCKSv4, then only some SOCKSv5 requests
|
|
102 can be handled, UDP would not work, however CONNECT and BIND will be
|
|
103 translated.
|
|
104
|
|
105 @param p Proxy which should be used to handle user requests.
|
|
106 */
|
|
107 public static void setProxy(Proxy p) {
|
|
108 proxy = p;
|
|
109 UDPRelayServer.proxy = proxy;
|
|
110 }
|
|
111
|
|
112 /**
|
|
113 Get proxy.
|
|
114 @return Proxy wich is used to handle user requests.
|
|
115 */
|
|
116 public static Proxy getProxy() {
|
|
117 return proxy;
|
|
118 }
|
|
119
|
|
120 /**
|
|
121 Sets the timeout for connections, how long shoud server wait
|
|
122 for data to arrive before dropping the connection.<br>
|
|
123 Zero timeout implies infinity.<br>
|
|
124 Default timeout is 3 minutes.
|
|
125 */
|
|
126 public static void setIddleTimeout(int timeout) {
|
|
127 iddleTimeout = timeout;
|
|
128 }
|
|
129 /**
|
|
130 Sets the timeout for BIND command, how long the server should
|
|
131 wait for the incoming connection.<br>
|
|
132 Zero timeout implies infinity.<br>
|
|
133 Default timeout is 3 minutes.
|
|
134 */
|
|
135 public static void setAcceptTimeout(int timeout) {
|
|
136 acceptTimeout = timeout;
|
|
137 }
|
|
138
|
|
139 /**
|
|
140 Sets the timeout for UDPRelay server.<br>
|
|
141 Zero timeout implies infinity.<br>
|
|
142 Default timeout is 3 minutes.
|
|
143 */
|
|
144 public static void setUDPTimeout(int timeout) {
|
|
145 UDPRelayServer.setTimeout(timeout);
|
|
146 }
|
|
147
|
|
148 /**
|
|
149 Sets the size of the datagrams used in the UDPRelayServer.<br>
|
|
150 Default size is 64K, a bit more than maximum possible size of the
|
|
151 datagram.
|
|
152 */
|
|
153 public static void setDatagramSize(int size) {
|
|
154 UDPRelayServer.setDatagramSize(size);
|
|
155 }
|
|
156
|
|
157
|
|
158 /**
|
|
159 Start the Proxy server at given port.<br>
|
|
160 This methods blocks.
|
|
161 */
|
|
162 public void start(int port) {
|
|
163 start(port, 5, null);
|
|
164 }
|
|
165
|
|
166 /**
|
|
167 Create a server with the specified port, listen backlog, and local
|
|
168 IP address to bind to. The localIP argument can be used on a multi-homed
|
|
169 host for a ServerSocket that will only accept connect requests to one of
|
|
170 its addresses. If localIP is null, it will default accepting connections
|
|
171 on any/all local addresses. The port must be between 0 and 65535,
|
|
172 inclusive. <br>
|
|
173 This methods blocks.
|
|
174 */
|
|
175 public void start(int port, int backlog, InetAddress localIP) {
|
|
176 try {
|
|
177 ss = new ServerSocket(port, backlog, localIP);
|
|
178 log("Starting SOCKS Proxy on:" + ss.getInetAddress().getHostAddress() + ":"
|
|
179 + ss.getLocalPort());
|
|
180
|
|
181 while (true) {
|
|
182 Socket s = ss.accept();
|
|
183 log("Accepted from:" + s.getInetAddress().getHostName() + ":"
|
|
184 + s.getPort());
|
|
185 ProxyServer ps = new ProxyServer(auth, s);
|
|
186 (new Thread(ps)).start();
|
|
187 }
|
|
188 }
|
|
189 catch (IOException ioe) {
|
|
190 ioe.printStackTrace();
|
|
191 }
|
|
192 finally {
|
|
193 }
|
|
194 }
|
|
195
|
|
196 /**
|
|
197 Stop server operation.It would be wise to interrupt thread running the
|
|
198 server afterwards.
|
|
199 */
|
|
200 public void stop() {
|
|
201 try {
|
|
202 if (ss != null) ss.close();
|
|
203 }
|
|
204 catch (IOException ioe) {
|
|
205 }
|
|
206 }
|
|
207
|
|
208 //Runnable interface
|
|
209 ////////////////////
|
|
210 public void run() {
|
|
211 switch (mode) {
|
|
212 case START_MODE:
|
|
213 try {
|
|
214 startSession();
|
|
215 }
|
|
216 catch (IOException ioe) {
|
|
217 handleException(ioe);
|
|
218 //ioe.printStackTrace();
|
|
219 }
|
|
220 finally {
|
|
221 abort();
|
|
222
|
|
223 if (auth != null) auth.endSession();
|
|
224
|
|
225 log("Main thread(client->remote)stopped.");
|
|
226 }
|
|
227
|
|
228 break;
|
|
229
|
|
230 case ACCEPT_MODE:
|
|
231 try {
|
|
232 doAccept();
|
|
233 mode = PIPE_MODE;
|
|
234 pipe_thread1.interrupt(); //Tell other thread that connection have
|
|
235 //been accepted.
|
|
236 pipe(remote_in, out);
|
|
237 }
|
|
238 catch (IOException ioe) {
|
|
239 //log("Accept exception:"+ioe);
|
|
240 handleException(ioe);
|
|
241 }
|
|
242 finally {
|
|
243 abort();
|
|
244 log("Accept thread(remote->client) stopped");
|
|
245 }
|
|
246
|
|
247 break;
|
|
248
|
|
249 case PIPE_MODE:
|
|
250 try {
|
|
251 pipe(remote_in, out);
|
|
252 }
|
|
253 catch (IOException ioe) {
|
|
254 }
|
|
255 finally {
|
|
256 abort();
|
|
257 log("Support thread(remote->client) stopped");
|
|
258 }
|
|
259
|
|
260 break;
|
|
261
|
|
262 case ABORT_MODE:
|
|
263 break;
|
|
264
|
|
265 default:
|
|
266 log("Unexpected MODE " + mode);
|
|
267 }
|
|
268 }
|
|
269
|
|
270 //Private methods
|
|
271 /////////////////
|
|
272 private void startSession() throws IOException {
|
|
273 sock.setSoTimeout(iddleTimeout);
|
|
274
|
|
275 try {
|
|
276 auth = auth.startSession(sock);
|
|
277 }
|
|
278 catch (IOException ioe) {
|
|
279 log("Auth throwed exception:" + ioe);
|
|
280 auth = null;
|
|
281 return;
|
|
282 }
|
|
283
|
|
284 if (auth == null) { //Authentication failed
|
|
285 log("Authentication failed");
|
|
286 return;
|
|
287 }
|
|
288
|
|
289 in = auth.getInputStream();
|
|
290 out = auth.getOutputStream();
|
|
291 msg = readMsg(in);
|
|
292 handleRequest(msg);
|
|
293 }
|
|
294
|
|
295 protected void handleRequest(ProxyMessage msg)
|
|
296 throws IOException {
|
|
297 if (!auth.checkRequest(msg)) throw new
|
|
298 SocksException(Proxy.SOCKS_FAILURE);
|
|
299
|
|
300 if (msg.ip == null) {
|
|
301 if (msg instanceof Socks5Message) {
|
|
302 msg.ip = InetAddress.getByName(msg.host);
|
|
303 }
|
|
304 else
|
|
305 throw new SocksException(Proxy.SOCKS_FAILURE);
|
|
306 }
|
|
307
|
|
308 log(msg);
|
|
309
|
|
310 switch (msg.command) {
|
|
311 case Proxy.SOCKS_CMD_CONNECT:
|
|
312 onConnect(msg);
|
|
313 break;
|
|
314
|
|
315 case Proxy.SOCKS_CMD_BIND:
|
|
316 onBind(msg);
|
|
317 break;
|
|
318
|
|
319 case Proxy.SOCKS_CMD_UDP_ASSOCIATE:
|
|
320 onUDP(msg);
|
|
321 break;
|
|
322
|
|
323 default:
|
|
324 throw new SocksException(Proxy.SOCKS_CMD_NOT_SUPPORTED);
|
|
325 }
|
|
326 }
|
|
327
|
|
328 private void handleException(IOException ioe) {
|
|
329 //If we couldn't read the request, return;
|
|
330 if (msg == null) return;
|
|
331
|
|
332 //If have been aborted by other thread
|
|
333 if (mode == ABORT_MODE) return;
|
|
334
|
|
335 //If the request was successfully completed, but exception happened later
|
|
336 if (mode == PIPE_MODE) return;
|
|
337
|
|
338 int error_code = Proxy.SOCKS_FAILURE;
|
|
339
|
|
340 if (ioe instanceof SocksException)
|
|
341 error_code = ((SocksException)ioe).errCode;
|
|
342 else if (ioe instanceof NoRouteToHostException)
|
|
343 error_code = Proxy.SOCKS_HOST_UNREACHABLE;
|
|
344 else if (ioe instanceof ConnectException)
|
|
345 error_code = Proxy.SOCKS_CONNECTION_REFUSED;
|
|
346 else if (ioe instanceof InterruptedIOException)
|
|
347 error_code = Proxy.SOCKS_TTL_EXPIRE;
|
|
348
|
|
349 if (error_code > Proxy.SOCKS_ADDR_NOT_SUPPORTED || error_code < 0) {
|
|
350 error_code = Proxy.SOCKS_FAILURE;
|
|
351 }
|
|
352
|
|
353 sendErrorMessage(error_code);
|
|
354 }
|
|
355
|
|
356 private void onConnect(ProxyMessage msg) throws IOException {
|
|
357 Socket s;
|
|
358 ProxyMessage response = null;
|
|
359 s = new Socket(msg.ip, msg.port);
|
|
360 log("Connected to " + s.getInetAddress() + ":" + s.getPort());
|
|
361
|
|
362 if (msg instanceof Socks5Message) {
|
|
363 response = new Socks5Message(Proxy.SOCKS_SUCCESS,
|
|
364 s.getLocalAddress(),
|
|
365 s.getLocalPort());
|
|
366 }
|
|
367 else {
|
|
368 response = new Socks4Message(Socks4Message.REPLY_OK,
|
|
369 s.getLocalAddress(), s.getLocalPort());
|
|
370 }
|
|
371
|
|
372 response.write(out);
|
|
373 startPipe(s);
|
|
374 }
|
|
375
|
|
376 private void onBind(ProxyMessage msg) throws IOException {
|
|
377 ProxyMessage response = null;
|
|
378
|
|
379 if (proxy == null)
|
|
380 ss = new ServerSocket(0);
|
|
381 else
|
|
382 ss = new SocksServerSocket(proxy, msg.ip, msg.port);
|
|
383
|
|
384 ss.setSoTimeout(acceptTimeout);
|
|
385 log("Trying accept on " + ss.getInetAddress() + ":" + ss.getLocalPort());
|
|
386
|
|
387 if (msg.version == 5)
|
|
388 response = new Socks5Message(Proxy.SOCKS_SUCCESS, ss.getInetAddress(),
|
|
389 ss.getLocalPort());
|
|
390 else
|
|
391 response = new Socks4Message(Socks4Message.REPLY_OK,
|
|
392 ss.getInetAddress(),
|
|
393 ss.getLocalPort());
|
|
394
|
|
395 response.write(out);
|
|
396 mode = ACCEPT_MODE;
|
|
397 pipe_thread1 = Thread.currentThread();
|
|
398 pipe_thread2 = new Thread(this);
|
|
399 pipe_thread2.start();
|
|
400 //Make timeout infinit.
|
|
401 sock.setSoTimeout(0);
|
|
402 int eof = 0;
|
|
403
|
|
404 try {
|
|
405 while ((eof = in.read()) >= 0) {
|
|
406 if (mode != ACCEPT_MODE) {
|
|
407 if (mode != PIPE_MODE) return; //Accept failed
|
|
408
|
|
409 remote_out.write(eof);
|
|
410 break;
|
|
411 }
|
|
412 }
|
|
413 }
|
|
414 catch (EOFException eofe) {
|
|
415 //System.out.println("EOF exception");
|
|
416 return;//Connection closed while we were trying to accept.
|
|
417 }
|
|
418 catch (InterruptedIOException iioe) {
|
|
419 //Accept thread interrupted us.
|
|
420 //System.out.println("Interrupted");
|
|
421 if (mode != PIPE_MODE)
|
|
422 return;//If accept thread was not successfull return.
|
|
423 }
|
|
424 finally {
|
|
425 //System.out.println("Finnaly!");
|
|
426 }
|
|
427
|
|
428 if (eof < 0) //Connection closed while we were trying to accept;
|
|
429 return;
|
|
430
|
|
431 //Do not restore timeout, instead timeout is set on the
|
|
432 //remote socket. It does not make any difference.
|
|
433 pipe(in, remote_out);
|
|
434 }
|
|
435
|
|
436 private void onUDP(ProxyMessage msg) throws IOException {
|
|
437 if (msg.ip.getHostAddress().equals("0.0.0.0"))
|
|
438 msg.ip = sock.getInetAddress();
|
|
439
|
|
440 log("Creating UDP relay server for " + msg.ip + ":" + msg.port);
|
|
441 relayServer = new UDPRelayServer(msg.ip, msg.port,
|
|
442 Thread.currentThread(), sock, auth);
|
|
443 ProxyMessage response;
|
|
444 response = new Socks5Message(Proxy.SOCKS_SUCCESS,
|
|
445 relayServer.relayIP, relayServer.relayPort);
|
|
446 response.write(out);
|
|
447 relayServer.start();
|
|
448 //Make timeout infinit.
|
|
449 sock.setSoTimeout(0);
|
|
450
|
|
451 try {
|
|
452 while (in.read() >= 0) /*do nothing*/;
|
|
453 }
|
|
454 catch (EOFException eofe) {
|
|
455 }
|
|
456 }
|
|
457
|
|
458 //Private methods
|
|
459 //////////////////
|
|
460
|
|
461 private void doAccept() throws IOException {
|
|
462 Socket s;
|
|
463 long startTime = System.currentTimeMillis();
|
|
464
|
|
465 while (true) {
|
|
466 s = ss.accept();
|
|
467
|
|
468 if (s.getInetAddress().equals(msg.ip)) {
|
|
469 //got the connection from the right host
|
|
470 //Close listenning socket.
|
|
471 ss.close();
|
|
472 break;
|
|
473 }
|
|
474 else if (ss instanceof SocksServerSocket) {
|
|
475 //We can't accept more then one connection
|
|
476 s.close();
|
|
477 ss.close();
|
|
478 throw new SocksException(Proxy.SOCKS_FAILURE);
|
|
479 }
|
|
480 else {
|
|
481 if (acceptTimeout != 0) { //If timeout is not infinit
|
|
482 int newTimeout = acceptTimeout - (int)(System.currentTimeMillis() -
|
|
483 startTime);
|
|
484
|
|
485 if (newTimeout <= 0) throw new InterruptedIOException(
|
|
486 "In doAccept()");
|
|
487
|
|
488 ss.setSoTimeout(newTimeout);
|
|
489 }
|
|
490
|
|
491 s.close(); //Drop all connections from other hosts
|
|
492 }
|
|
493 }
|
|
494
|
|
495 //Accepted connection
|
|
496 remote_sock = s;
|
|
497 remote_in = s.getInputStream();
|
|
498 remote_out = s.getOutputStream();
|
|
499 //Set timeout
|
|
500 remote_sock.setSoTimeout(iddleTimeout);
|
|
501 log("Accepted from " + s.getInetAddress() + ":" + s.getPort());
|
|
502 ProxyMessage response;
|
|
503
|
|
504 if (msg.version == 5)
|
|
505 response = new Socks5Message(Proxy.SOCKS_SUCCESS, s.getInetAddress(),
|
|
506 s.getPort());
|
|
507 else
|
|
508 response = new Socks4Message(Socks4Message.REPLY_OK,
|
|
509 s.getInetAddress(), s.getPort());
|
|
510
|
|
511 response.write(out);
|
|
512 }
|
|
513
|
|
514 protected ProxyMessage readMsg(InputStream in) throws IOException {
|
|
515 PushbackInputStream push_in;
|
|
516
|
|
517 if (in instanceof PushbackInputStream)
|
|
518 push_in = (PushbackInputStream) in;
|
|
519 else
|
|
520 push_in = new PushbackInputStream(in);
|
|
521
|
|
522 int version = push_in.read();
|
|
523 push_in.unread(version);
|
|
524 ProxyMessage msg;
|
|
525
|
|
526 if (version == 5) {
|
|
527 msg = new Socks5Message(push_in, false);
|
|
528 }
|
|
529 else if (version == 4) {
|
|
530 msg = new Socks4Message(push_in, false);
|
|
531 }
|
|
532 else {
|
|
533 throw new SocksException(Proxy.SOCKS_FAILURE);
|
|
534 }
|
|
535
|
|
536 return msg;
|
|
537 }
|
|
538
|
|
539 private void startPipe(Socket s) {
|
|
540 mode = PIPE_MODE;
|
|
541 remote_sock = s;
|
|
542
|
|
543 try {
|
|
544 remote_in = s.getInputStream();
|
|
545 remote_out = s.getOutputStream();
|
|
546 pipe_thread1 = Thread.currentThread();
|
|
547 pipe_thread2 = new Thread(this);
|
|
548 pipe_thread2.start();
|
|
549 pipe(in, remote_out);
|
|
550 }
|
|
551 catch (IOException ioe) {
|
|
552 }
|
|
553 }
|
|
554
|
|
555 private void sendErrorMessage(int error_code) {
|
|
556 ProxyMessage err_msg;
|
|
557
|
|
558 if (msg instanceof Socks4Message)
|
|
559 err_msg = new Socks4Message(Socks4Message.REPLY_REJECTED);
|
|
560 else
|
|
561 err_msg = new Socks5Message(error_code);
|
|
562
|
|
563 try {
|
|
564 err_msg.write(out);
|
|
565 }
|
|
566 catch (IOException ioe) {}
|
|
567 }
|
|
568
|
|
569 private synchronized void abort() {
|
|
570 if (mode == ABORT_MODE) return;
|
|
571
|
|
572 mode = ABORT_MODE;
|
|
573
|
|
574 try {
|
|
575 log("Aborting operation");
|
|
576
|
|
577 if (remote_sock != null) remote_sock.close();
|
|
578
|
|
579 if (sock != null) sock.close();
|
|
580
|
|
581 if (relayServer != null) relayServer.stop();
|
|
582
|
|
583 if (ss != null) ss.close();
|
|
584
|
|
585 if (pipe_thread1 != null) pipe_thread1.interrupt();
|
|
586
|
|
587 if (pipe_thread2 != null) pipe_thread2.interrupt();
|
|
588 }
|
|
589 catch (IOException ioe) {}
|
|
590 }
|
|
591
|
|
592 static final void log(String s) {
|
|
593 if (log != null) {
|
|
594 log.println(s);
|
|
595 log.flush();
|
|
596 }
|
|
597 }
|
|
598
|
|
599 static final void log(ProxyMessage msg) {
|
|
600 log("Request version:" + msg.version +
|
|
601 "\tCommand: " + command2String(msg.command));
|
|
602 log("IP:" + msg.ip + "\tPort:" + msg.port +
|
|
603 (msg.version == 4 ? "\tUser:" + msg.user : ""));
|
|
604 }
|
|
605
|
|
606 private void pipe(InputStream in, OutputStream out) throws IOException {
|
|
607 lastReadTime = System.currentTimeMillis();
|
|
608 byte[] buf = new byte[BUF_SIZE];
|
|
609 int len = 0;
|
|
610
|
|
611 while (len >= 0) {
|
|
612 try {
|
|
613 if (len != 0) {
|
|
614 out.write(buf, 0, len);
|
|
615 out.flush();
|
|
616 }
|
|
617
|
|
618 len = in.read(buf);
|
|
619 lastReadTime = System.currentTimeMillis();
|
|
620 }
|
|
621 catch (InterruptedIOException iioe) {
|
|
622 if (iddleTimeout == 0) return; //Other thread interrupted us.
|
|
623
|
|
624 long timeSinceRead = System.currentTimeMillis() - lastReadTime;
|
|
625
|
|
626 if (timeSinceRead >= iddleTimeout - 1000) //-1s for adjustment.
|
|
627 return;
|
|
628
|
|
629 len = 0;
|
|
630 }
|
|
631 }
|
|
632 }
|
|
633 static final String command_names[] = {"CONNECT", "BIND", "UDP_ASSOCIATE"};
|
|
634
|
|
635 static final String command2String(int cmd) {
|
|
636 if (cmd > 0 && cmd < 4) return command_names[cmd - 1];
|
|
637 else return "Unknown Command " + cmd;
|
|
638 }
|
|
639 }
|