netwerk/test/unit/test_socks.js

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

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

mercurial