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