|
1 const CC = Components.Constructor; |
|
2 |
|
3 const ServerSocket = CC("@mozilla.org/network/server-socket;1", |
|
4 "nsIServerSocket", |
|
5 "init"); |
|
6 const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", |
|
7 "nsIBinaryInputStream", |
|
8 "setInputStream"); |
|
9 const DirectoryService = CC("@mozilla.org/file/directory_service;1", |
|
10 "nsIProperties"); |
|
11 const Process = CC("@mozilla.org/process/util;1", "nsIProcess", "init"); |
|
12 |
|
13 const currentThread = Cc["@mozilla.org/thread-manager;1"] |
|
14 .getService().currentThread; |
|
15 |
|
16 var socks_test_server = null; |
|
17 var socks_listen_port = -1; |
|
18 |
|
19 function getAvailableBytes(input) |
|
20 { |
|
21 var len = 0; |
|
22 |
|
23 try { |
|
24 len = input.available(); |
|
25 } catch (e) { |
|
26 } |
|
27 |
|
28 return len; |
|
29 } |
|
30 |
|
31 function runScriptSubprocess(script, args) |
|
32 { |
|
33 // logic copied from ted's crashreporter unit test |
|
34 var ds = new DirectoryService(); |
|
35 var bin = ds.get("CurProcD", Ci.nsILocalFile); |
|
36 |
|
37 bin.append("xpcshell"); |
|
38 if (!bin.exists()) { |
|
39 bin.leafName = "xpcshell.exe"; |
|
40 do_check_true(bin.exists()); |
|
41 if (!bin.exists()) |
|
42 do_throw("Can't find xpcshell binary"); |
|
43 } |
|
44 |
|
45 var script = do_get_file(script); |
|
46 var proc = new Process(bin); |
|
47 var args = [script.path].concat(args); |
|
48 |
|
49 proc.run(false, args, args.length); |
|
50 |
|
51 return proc; |
|
52 } |
|
53 |
|
54 function buf2ip(buf) |
|
55 { |
|
56 if (buf.length == 16) { |
|
57 var ip = (buf[0] << 4 | buf[1]).toString(16) + ':' + |
|
58 (buf[2] << 4 | buf[3]).toString(16) + ':' + |
|
59 (buf[4] << 4 | buf[5]).toString(16) + ':' + |
|
60 (buf[6] << 4 | buf[7]).toString(16) + ':' + |
|
61 (buf[8] << 4 | buf[9]).toString(16) + ':' + |
|
62 (buf[10] << 4 | buf[11]).toString(16) + ':' + |
|
63 (buf[12] << 4 | buf[13]).toString(16) + ':' + |
|
64 (buf[14] << 4 | buf[15]).toString(16); |
|
65 for (var i = 8; i >= 2; i--) { |
|
66 var re = new RegExp("(^|:)(0(:|$)){" + i + "}"); |
|
67 var shortip = ip.replace(re, '::'); |
|
68 if (shortip != ip) { |
|
69 return shortip; |
|
70 } |
|
71 } |
|
72 return ip; |
|
73 } else { |
|
74 return buf.join('.'); |
|
75 } |
|
76 } |
|
77 |
|
78 function buf2int(buf) |
|
79 { |
|
80 var n = 0; |
|
81 |
|
82 for (var i in buf) { |
|
83 n |= buf[i] << ((buf.length - i - 1) * 8); |
|
84 } |
|
85 |
|
86 return n; |
|
87 } |
|
88 |
|
89 function buf2str(buf) |
|
90 { |
|
91 return String.fromCharCode.apply(null, buf); |
|
92 } |
|
93 |
|
94 const STATE_WAIT_GREETING = 1; |
|
95 const STATE_WAIT_SOCKS4_REQUEST = 2; |
|
96 const STATE_WAIT_SOCKS4_USERNAME = 3; |
|
97 const STATE_WAIT_SOCKS4_HOSTNAME = 4; |
|
98 const STATE_WAIT_SOCKS5_GREETING = 5; |
|
99 const STATE_WAIT_SOCKS5_REQUEST = 6; |
|
100 const STATE_WAIT_PONG = 7; |
|
101 const STATE_GOT_PONG = 8; |
|
102 |
|
103 function SocksClient(server, client_in, client_out) |
|
104 { |
|
105 this.server = server; |
|
106 this.type = ''; |
|
107 this.username = ''; |
|
108 this.dest_name = ''; |
|
109 this.dest_addr = []; |
|
110 this.dest_port = []; |
|
111 |
|
112 this.client_in = client_in; |
|
113 this.client_out = client_out; |
|
114 this.inbuf = []; |
|
115 this.outbuf = String(); |
|
116 this.state = STATE_WAIT_GREETING; |
|
117 this.waitRead(this.client_in); |
|
118 } |
|
119 SocksClient.prototype = { |
|
120 onInputStreamReady: function(input) |
|
121 { |
|
122 var len = getAvailableBytes(input); |
|
123 |
|
124 if (len == 0) { |
|
125 print('server: client closed!'); |
|
126 do_check_eq(this.state, STATE_GOT_PONG); |
|
127 this.server.testCompleted(this); |
|
128 return; |
|
129 } |
|
130 |
|
131 var bin = new BinaryInputStream(input); |
|
132 var data = bin.readByteArray(len); |
|
133 this.inbuf = this.inbuf.concat(data); |
|
134 |
|
135 switch (this.state) { |
|
136 case STATE_WAIT_GREETING: |
|
137 this.checkSocksGreeting(); |
|
138 break; |
|
139 case STATE_WAIT_SOCKS4_REQUEST: |
|
140 this.checkSocks4Request(); |
|
141 break; |
|
142 case STATE_WAIT_SOCKS4_USERNAME: |
|
143 this.checkSocks4Username(); |
|
144 break; |
|
145 case STATE_WAIT_SOCKS4_HOSTNAME: |
|
146 this.checkSocks4Hostname(); |
|
147 break; |
|
148 case STATE_WAIT_SOCKS5_GREETING: |
|
149 this.checkSocks5Greeting(); |
|
150 break; |
|
151 case STATE_WAIT_SOCKS5_REQUEST: |
|
152 this.checkSocks5Request(); |
|
153 break; |
|
154 case STATE_WAIT_PONG: |
|
155 this.checkPong(); |
|
156 break; |
|
157 default: |
|
158 do_throw("server: read in invalid state!"); |
|
159 } |
|
160 |
|
161 this.waitRead(input); |
|
162 }, |
|
163 |
|
164 onOutputStreamReady: function(output) |
|
165 { |
|
166 var len = output.write(this.outbuf, this.outbuf.length); |
|
167 if (len != this.outbuf.length) { |
|
168 this.outbuf = this.outbuf.substring(len); |
|
169 this.waitWrite(output); |
|
170 } else |
|
171 this.outbuf = String(); |
|
172 }, |
|
173 |
|
174 waitRead: function(input) |
|
175 { |
|
176 input.asyncWait(this, 0, 0, currentThread); |
|
177 }, |
|
178 |
|
179 waitWrite: function(output) |
|
180 { |
|
181 output.asyncWait(this, 0, 0, currentThread); |
|
182 }, |
|
183 |
|
184 write: function(buf) |
|
185 { |
|
186 this.outbuf += buf; |
|
187 this.waitWrite(this.client_out); |
|
188 }, |
|
189 |
|
190 checkSocksGreeting: function() |
|
191 { |
|
192 if (this.inbuf.length == 0) |
|
193 return; |
|
194 |
|
195 if (this.inbuf[0] == 4) { |
|
196 print('server: got socks 4'); |
|
197 this.type = 'socks4'; |
|
198 this.state = STATE_WAIT_SOCKS4_REQUEST; |
|
199 this.checkSocks4Request(); |
|
200 } else if (this.inbuf[0] == 5) { |
|
201 print('server: got socks 5'); |
|
202 this.type = 'socks'; |
|
203 this.state = STATE_WAIT_SOCKS5_GREETING; |
|
204 this.checkSocks5Greeting(); |
|
205 } else { |
|
206 do_throw("Unknown socks protocol!"); |
|
207 } |
|
208 }, |
|
209 |
|
210 checkSocks4Request: function() |
|
211 { |
|
212 if (this.inbuf.length < 8) |
|
213 return; |
|
214 |
|
215 do_check_eq(this.inbuf[1], 0x01); |
|
216 |
|
217 this.dest_port = this.inbuf.slice(2, 4); |
|
218 this.dest_addr = this.inbuf.slice(4, 8); |
|
219 |
|
220 this.inbuf = this.inbuf.slice(8); |
|
221 this.state = STATE_WAIT_SOCKS4_USERNAME; |
|
222 this.checkSocks4Username(); |
|
223 }, |
|
224 |
|
225 readString: function() |
|
226 { |
|
227 var i = this.inbuf.indexOf(0); |
|
228 var str = null; |
|
229 |
|
230 if (i >= 0) { |
|
231 var buf = this.inbuf.slice(0,i); |
|
232 str = buf2str(buf); |
|
233 this.inbuf = this.inbuf.slice(i+1); |
|
234 } |
|
235 |
|
236 return str; |
|
237 }, |
|
238 |
|
239 checkSocks4Username: function() |
|
240 { |
|
241 var str = this.readString(); |
|
242 |
|
243 if (str == null) |
|
244 return; |
|
245 |
|
246 this.username = str; |
|
247 if (this.dest_addr[0] == 0 && |
|
248 this.dest_addr[1] == 0 && |
|
249 this.dest_addr[2] == 0 && |
|
250 this.dest_addr[3] != 0) { |
|
251 this.state = STATE_WAIT_SOCKS4_HOSTNAME; |
|
252 this.checkSocks4Hostname(); |
|
253 } else { |
|
254 this.sendSocks4Response(); |
|
255 } |
|
256 }, |
|
257 |
|
258 checkSocks4Hostname: function() |
|
259 { |
|
260 var str = this.readString(); |
|
261 |
|
262 if (str == null) |
|
263 return; |
|
264 |
|
265 this.dest_name = str; |
|
266 this.sendSocks4Response(); |
|
267 }, |
|
268 |
|
269 sendSocks4Response: function() |
|
270 { |
|
271 this.outbuf = '\x00\x5a\x00\x00\x00\x00\x00\x00'; |
|
272 this.sendPing(); |
|
273 }, |
|
274 |
|
275 checkSocks5Greeting: function() |
|
276 { |
|
277 if (this.inbuf.length < 2) |
|
278 return; |
|
279 var nmethods = this.inbuf[1]; |
|
280 if (this.inbuf.length < 2 + nmethods) |
|
281 return; |
|
282 |
|
283 do_check_true(nmethods >= 1); |
|
284 var methods = this.inbuf.slice(2, 2 + nmethods); |
|
285 do_check_true(0 in methods); |
|
286 |
|
287 this.inbuf = []; |
|
288 this.state = STATE_WAIT_SOCKS5_REQUEST; |
|
289 this.write('\x05\x00'); |
|
290 }, |
|
291 |
|
292 checkSocks5Request: function() |
|
293 { |
|
294 if (this.inbuf.length < 4) |
|
295 return; |
|
296 |
|
297 do_check_eq(this.inbuf[0], 0x05); |
|
298 do_check_eq(this.inbuf[1], 0x01); |
|
299 do_check_eq(this.inbuf[2], 0x00); |
|
300 |
|
301 var atype = this.inbuf[3]; |
|
302 var len; |
|
303 var name = false; |
|
304 |
|
305 switch (atype) { |
|
306 case 0x01: |
|
307 len = 4; |
|
308 break; |
|
309 case 0x03: |
|
310 len = this.inbuf[4]; |
|
311 name = true; |
|
312 break; |
|
313 case 0x04: |
|
314 len = 16; |
|
315 break; |
|
316 default: |
|
317 do_throw("Unknown address type " + atype); |
|
318 } |
|
319 |
|
320 if (name) { |
|
321 if (this.inbuf.length < 4 + len + 1 + 2) |
|
322 return; |
|
323 |
|
324 buf = this.inbuf.slice(5, 5 + len); |
|
325 this.dest_name = buf2str(buf); |
|
326 len += 1; |
|
327 } else { |
|
328 if (this.inbuf.length < 4 + len + 2) |
|
329 return; |
|
330 |
|
331 this.dest_addr = this.inbuf.slice(4, 4 + len); |
|
332 } |
|
333 |
|
334 len += 4; |
|
335 this.dest_port = this.inbuf.slice(len, len + 2); |
|
336 this.inbuf = this.inbuf.slice(len + 2); |
|
337 this.sendSocks5Response(); |
|
338 }, |
|
339 |
|
340 sendSocks5Response: function() |
|
341 { |
|
342 if (this.dest_addr.length == 16) { |
|
343 // send a successful response with the address, [::1]:80 |
|
344 this.outbuf += '\x05\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x80'; |
|
345 } else { |
|
346 // send a successful response with the address, 127.0.0.1:80 |
|
347 this.outbuf += '\x05\x00\x00\x01\x7f\x00\x00\x01\x00\x80'; |
|
348 } |
|
349 this.sendPing(); |
|
350 }, |
|
351 |
|
352 sendPing: function() |
|
353 { |
|
354 print('server: sending ping'); |
|
355 this.state = STATE_WAIT_PONG; |
|
356 this.outbuf += "PING!"; |
|
357 this.inbuf = []; |
|
358 this.waitWrite(this.client_out); |
|
359 this.waitRead(this.client_in); |
|
360 }, |
|
361 |
|
362 checkPong: function() |
|
363 { |
|
364 var pong = buf2str(this.inbuf); |
|
365 do_check_eq(pong, "PONG!"); |
|
366 this.state = STATE_GOT_PONG; |
|
367 this.waitRead(this.client_in); |
|
368 }, |
|
369 |
|
370 close: function() |
|
371 { |
|
372 this.client_in.close(); |
|
373 this.client_out.close(); |
|
374 } |
|
375 }; |
|
376 |
|
377 function SocksTestServer() |
|
378 { |
|
379 this.listener = ServerSocket(-1, true, -1); |
|
380 socks_listen_port = this.listener.port; |
|
381 print('server: listening on', socks_listen_port); |
|
382 this.listener.asyncListen(this); |
|
383 this.test_cases = []; |
|
384 this.client_connections = []; |
|
385 this.client_subprocess = null; |
|
386 // port is used as the ID for test cases |
|
387 this.test_port_id = 8000; |
|
388 this.tests_completed = 0; |
|
389 } |
|
390 SocksTestServer.prototype = { |
|
391 addTestCase: function(test) |
|
392 { |
|
393 test.finished = false; |
|
394 test.port = this.test_port_id++; |
|
395 this.test_cases.push(test); |
|
396 }, |
|
397 |
|
398 pickTest: function(id) |
|
399 { |
|
400 for (var i in this.test_cases) { |
|
401 var test = this.test_cases[i]; |
|
402 if (test.port == id) { |
|
403 this.tests_completed++; |
|
404 return test; |
|
405 } |
|
406 } |
|
407 do_throw("No test case with id " + id); |
|
408 }, |
|
409 |
|
410 testCompleted: function(client) |
|
411 { |
|
412 var port_id = buf2int(client.dest_port); |
|
413 var test = this.pickTest(port_id); |
|
414 |
|
415 print('server: test finished', test.port); |
|
416 do_check_true(test != null); |
|
417 do_check_eq(test.expectedType || test.type, client.type); |
|
418 do_check_eq(test.port, port_id); |
|
419 |
|
420 if (test.remote_dns) |
|
421 do_check_eq(test.host, client.dest_name); |
|
422 else |
|
423 do_check_eq(test.host, buf2ip(client.dest_addr)); |
|
424 |
|
425 if (this.test_cases.length == this.tests_completed) { |
|
426 print('server: all tests completed'); |
|
427 this.close(); |
|
428 do_test_finished(); |
|
429 } |
|
430 }, |
|
431 |
|
432 runClientSubprocess: function() |
|
433 { |
|
434 var argv = []; |
|
435 |
|
436 // marshaled: socks_ver|server_port|dest_host|dest_port|<remote|local> |
|
437 for each (var test in this.test_cases) { |
|
438 var arg = test.type + '|' + |
|
439 String(socks_listen_port) + '|' + |
|
440 test.host + '|' + test.port + '|'; |
|
441 if (test.remote_dns) |
|
442 arg += 'remote'; |
|
443 else |
|
444 arg += 'local'; |
|
445 print('server: using test case', arg); |
|
446 argv.push(arg); |
|
447 } |
|
448 |
|
449 this.client_subprocess = runScriptSubprocess( |
|
450 'socks_client_subprocess.js', argv); |
|
451 }, |
|
452 |
|
453 onSocketAccepted: function(socket, trans) |
|
454 { |
|
455 print('server: got client connection'); |
|
456 var input = trans.openInputStream(0, 0, 0); |
|
457 var output = trans.openOutputStream(0, 0, 0); |
|
458 var client = new SocksClient(this, input, output); |
|
459 this.client_connections.push(client); |
|
460 }, |
|
461 |
|
462 onStopListening: function(socket) |
|
463 { |
|
464 }, |
|
465 |
|
466 close: function() |
|
467 { |
|
468 if (this.client_subprocess) { |
|
469 try { |
|
470 this.client_subprocess.kill(); |
|
471 } catch (x) { |
|
472 do_note_exception(x, 'Killing subprocess failed'); |
|
473 } |
|
474 this.client_subprocess = null; |
|
475 } |
|
476 for each (var client in this.client_connections) |
|
477 client.close(); |
|
478 this.client_connections = []; |
|
479 if (this.listener) { |
|
480 this.listener.close(); |
|
481 this.listener = null; |
|
482 } |
|
483 } |
|
484 }; |
|
485 |
|
486 function test_timeout() |
|
487 { |
|
488 socks_test_server.close(); |
|
489 do_throw("SOCKS test took too long!"); |
|
490 } |
|
491 |
|
492 function run_test() |
|
493 { |
|
494 socks_test_server = new SocksTestServer(); |
|
495 |
|
496 socks_test_server.addTestCase({ |
|
497 type: "socks4", |
|
498 host: '127.0.0.1', |
|
499 remote_dns: false, |
|
500 }); |
|
501 socks_test_server.addTestCase({ |
|
502 type: "socks4", |
|
503 host: '12345.xxx', |
|
504 remote_dns: true, |
|
505 }); |
|
506 socks_test_server.addTestCase({ |
|
507 type: "socks4", |
|
508 expectedType: "socks", |
|
509 host: '::1', |
|
510 remote_dns: false, |
|
511 }); |
|
512 socks_test_server.addTestCase({ |
|
513 type: "socks", |
|
514 host: '127.0.0.1', |
|
515 remote_dns: false, |
|
516 }); |
|
517 socks_test_server.addTestCase({ |
|
518 type: "socks", |
|
519 host: 'abcdefg.xxx', |
|
520 remote_dns: true, |
|
521 }); |
|
522 socks_test_server.addTestCase({ |
|
523 type: "socks", |
|
524 host: '::1', |
|
525 remote_dns: false, |
|
526 }); |
|
527 socks_test_server.runClientSubprocess(); |
|
528 |
|
529 do_timeout(120 * 1000, test_timeout); |
|
530 do_test_pending(); |
|
531 } |