Mercurial > 510Connectbot
comparison src/net/sourceforge/jsocks/ProxyServer.java @ 0:0ce5cc452d02
initial version
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Thu, 22 May 2014 10:41:19 -0700 |
parents | |
children | 205ee2873330 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:0ce5cc452d02 |
---|---|
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 } |