netwerk/test/unit/test_socks.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/netwerk/test/unit/test_socks.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,531 @@
     1.4 +const CC = Components.Constructor;
     1.5 +
     1.6 +const ServerSocket = CC("@mozilla.org/network/server-socket;1",
     1.7 +                        "nsIServerSocket",
     1.8 +                        "init");
     1.9 +const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
    1.10 +                           "nsIBinaryInputStream",
    1.11 +                           "setInputStream");
    1.12 +const DirectoryService = CC("@mozilla.org/file/directory_service;1",
    1.13 +                            "nsIProperties");
    1.14 +const Process = CC("@mozilla.org/process/util;1", "nsIProcess", "init");
    1.15 +
    1.16 +const currentThread = Cc["@mozilla.org/thread-manager;1"]
    1.17 +                      .getService().currentThread;
    1.18 +
    1.19 +var socks_test_server = null;
    1.20 +var socks_listen_port = -1;
    1.21 +
    1.22 +function getAvailableBytes(input)
    1.23 +{
    1.24 +  var len = 0;
    1.25 +
    1.26 +  try {
    1.27 +    len = input.available();  
    1.28 +  } catch (e) {
    1.29 +  }
    1.30 +  
    1.31 +  return len;
    1.32 +}
    1.33 +
    1.34 +function runScriptSubprocess(script, args)
    1.35 +{
    1.36 +  // logic copied from ted's crashreporter unit test
    1.37 +  var ds = new DirectoryService();
    1.38 +  var bin = ds.get("CurProcD", Ci.nsILocalFile);
    1.39 +
    1.40 +  bin.append("xpcshell");
    1.41 +  if (!bin.exists()) {
    1.42 +    bin.leafName = "xpcshell.exe";
    1.43 +    do_check_true(bin.exists());
    1.44 +    if (!bin.exists())
    1.45 +      do_throw("Can't find xpcshell binary");
    1.46 +  }
    1.47 +
    1.48 +  var script = do_get_file(script);
    1.49 +  var proc = new Process(bin);
    1.50 +  var args = [script.path].concat(args);
    1.51 +
    1.52 +  proc.run(false, args, args.length);
    1.53 +
    1.54 +  return proc;
    1.55 +}
    1.56 +
    1.57 +function buf2ip(buf)
    1.58 +{
    1.59 +  if (buf.length == 16) {
    1.60 +    var ip = (buf[0]  << 4 | buf[1]).toString(16) + ':' +
    1.61 +             (buf[2]  << 4 | buf[3]).toString(16) + ':' +
    1.62 +             (buf[4]  << 4 | buf[5]).toString(16) + ':' +
    1.63 +             (buf[6]  << 4 | buf[7]).toString(16) + ':' +
    1.64 +             (buf[8]  << 4 | buf[9]).toString(16) + ':' +
    1.65 +             (buf[10] << 4 | buf[11]).toString(16) + ':' +
    1.66 +             (buf[12] << 4 | buf[13]).toString(16) + ':' +
    1.67 +             (buf[14] << 4 | buf[15]).toString(16);
    1.68 +    for (var i = 8; i >= 2; i--) {
    1.69 +      var re = new RegExp("(^|:)(0(:|$)){" + i + "}");
    1.70 +      var shortip = ip.replace(re, '::');
    1.71 +      if (shortip != ip) {
    1.72 +        return shortip;
    1.73 +      }
    1.74 +    }
    1.75 +    return ip;
    1.76 +  } else {
    1.77 +    return buf.join('.');
    1.78 +  }
    1.79 +}
    1.80 +
    1.81 +function buf2int(buf)
    1.82 +{
    1.83 +  var n = 0;
    1.84 +  
    1.85 +  for (var i in buf) {
    1.86 +    n |= buf[i] << ((buf.length - i - 1) * 8);
    1.87 +  }
    1.88 +
    1.89 +  return n;
    1.90 +}
    1.91 +
    1.92 +function buf2str(buf)
    1.93 +{
    1.94 +  return String.fromCharCode.apply(null, buf);
    1.95 +}
    1.96 +
    1.97 +const STATE_WAIT_GREETING = 1;
    1.98 +const STATE_WAIT_SOCKS4_REQUEST = 2;
    1.99 +const STATE_WAIT_SOCKS4_USERNAME = 3;
   1.100 +const STATE_WAIT_SOCKS4_HOSTNAME = 4;
   1.101 +const STATE_WAIT_SOCKS5_GREETING = 5;
   1.102 +const STATE_WAIT_SOCKS5_REQUEST = 6;
   1.103 +const STATE_WAIT_PONG = 7;
   1.104 +const STATE_GOT_PONG = 8;
   1.105 +
   1.106 +function SocksClient(server, client_in, client_out)
   1.107 +{
   1.108 +  this.server = server;
   1.109 +  this.type = '';
   1.110 +  this.username = '';
   1.111 +  this.dest_name = '';
   1.112 +  this.dest_addr = [];
   1.113 +  this.dest_port = [];
   1.114 +
   1.115 +  this.client_in = client_in;
   1.116 +  this.client_out = client_out;
   1.117 +  this.inbuf = [];
   1.118 +  this.outbuf = String();
   1.119 +  this.state = STATE_WAIT_GREETING;
   1.120 +  this.waitRead(this.client_in);
   1.121 +}
   1.122 +SocksClient.prototype = {
   1.123 +  onInputStreamReady: function(input)
   1.124 +  {
   1.125 +    var len = getAvailableBytes(input);
   1.126 +
   1.127 +    if (len == 0) {
   1.128 +      print('server: client closed!');
   1.129 +      do_check_eq(this.state, STATE_GOT_PONG);
   1.130 +      this.server.testCompleted(this);
   1.131 +      return;
   1.132 +    }
   1.133 +
   1.134 +    var bin = new BinaryInputStream(input);
   1.135 +    var data = bin.readByteArray(len);
   1.136 +    this.inbuf = this.inbuf.concat(data);
   1.137 +
   1.138 +    switch (this.state) {
   1.139 +      case STATE_WAIT_GREETING:
   1.140 +        this.checkSocksGreeting();
   1.141 +        break;
   1.142 +      case STATE_WAIT_SOCKS4_REQUEST:
   1.143 +        this.checkSocks4Request();
   1.144 +        break;
   1.145 +      case STATE_WAIT_SOCKS4_USERNAME:
   1.146 +        this.checkSocks4Username();
   1.147 +        break;
   1.148 +      case STATE_WAIT_SOCKS4_HOSTNAME:
   1.149 +        this.checkSocks4Hostname();
   1.150 +        break;
   1.151 +      case STATE_WAIT_SOCKS5_GREETING:
   1.152 +        this.checkSocks5Greeting();
   1.153 +        break;
   1.154 +      case STATE_WAIT_SOCKS5_REQUEST:
   1.155 +        this.checkSocks5Request();
   1.156 +        break;
   1.157 +      case STATE_WAIT_PONG:
   1.158 +        this.checkPong();
   1.159 +        break;
   1.160 +      default:
   1.161 +        do_throw("server: read in invalid state!");
   1.162 +    }
   1.163 +
   1.164 +    this.waitRead(input);
   1.165 +  },
   1.166 +
   1.167 +  onOutputStreamReady: function(output)
   1.168 +  {
   1.169 +    var len = output.write(this.outbuf, this.outbuf.length);
   1.170 +    if (len != this.outbuf.length) {
   1.171 +      this.outbuf = this.outbuf.substring(len);
   1.172 +      this.waitWrite(output);
   1.173 +    } else
   1.174 +      this.outbuf = String();
   1.175 +  },
   1.176 +
   1.177 +  waitRead: function(input)
   1.178 +  {
   1.179 +    input.asyncWait(this, 0, 0, currentThread);
   1.180 +  },
   1.181 +
   1.182 +  waitWrite: function(output)
   1.183 +  {
   1.184 +    output.asyncWait(this, 0, 0, currentThread);
   1.185 +  },
   1.186 +
   1.187 +  write: function(buf)
   1.188 +  {
   1.189 +    this.outbuf += buf;
   1.190 +    this.waitWrite(this.client_out);
   1.191 +  },
   1.192 +
   1.193 +  checkSocksGreeting: function()
   1.194 +  {
   1.195 +    if (this.inbuf.length == 0)
   1.196 +      return;
   1.197 +
   1.198 +    if (this.inbuf[0] == 4) {
   1.199 +      print('server: got socks 4');
   1.200 +      this.type = 'socks4';
   1.201 +      this.state = STATE_WAIT_SOCKS4_REQUEST;
   1.202 +      this.checkSocks4Request();
   1.203 +    } else if (this.inbuf[0] == 5) {
   1.204 +      print('server: got socks 5');
   1.205 +      this.type = 'socks';
   1.206 +      this.state = STATE_WAIT_SOCKS5_GREETING;
   1.207 +      this.checkSocks5Greeting();
   1.208 +    } else {
   1.209 +      do_throw("Unknown socks protocol!");
   1.210 +    }
   1.211 +  },
   1.212 +
   1.213 +  checkSocks4Request: function()
   1.214 +  {
   1.215 +    if (this.inbuf.length < 8)
   1.216 +      return;
   1.217 +
   1.218 +    do_check_eq(this.inbuf[1], 0x01);
   1.219 +    
   1.220 +    this.dest_port = this.inbuf.slice(2, 4);
   1.221 +    this.dest_addr = this.inbuf.slice(4, 8);
   1.222 +
   1.223 +    this.inbuf = this.inbuf.slice(8);
   1.224 +    this.state = STATE_WAIT_SOCKS4_USERNAME;
   1.225 +    this.checkSocks4Username();
   1.226 +  },
   1.227 +
   1.228 +  readString: function()
   1.229 +  {
   1.230 +    var i = this.inbuf.indexOf(0);
   1.231 +    var str = null;
   1.232 +
   1.233 +    if (i >= 0) {
   1.234 +      var buf = this.inbuf.slice(0,i);
   1.235 +      str = buf2str(buf);
   1.236 +      this.inbuf = this.inbuf.slice(i+1);
   1.237 +    }
   1.238 +
   1.239 +    return str;
   1.240 +  },
   1.241 +
   1.242 +  checkSocks4Username: function()
   1.243 +  {
   1.244 +    var str = this.readString();
   1.245 +
   1.246 +    if (str == null)
   1.247 +      return;
   1.248 +
   1.249 +    this.username = str;
   1.250 +    if (this.dest_addr[0] == 0 &&
   1.251 +        this.dest_addr[1] == 0 &&
   1.252 +        this.dest_addr[2] == 0 &&
   1.253 +        this.dest_addr[3] != 0) {
   1.254 +      this.state = STATE_WAIT_SOCKS4_HOSTNAME;
   1.255 +      this.checkSocks4Hostname();
   1.256 +    } else {
   1.257 +      this.sendSocks4Response();
   1.258 +    }
   1.259 +  },
   1.260 +
   1.261 +  checkSocks4Hostname: function()
   1.262 +  {
   1.263 +    var str = this.readString();
   1.264 +
   1.265 +    if (str == null)
   1.266 +      return;
   1.267 +
   1.268 +    this.dest_name = str;
   1.269 +    this.sendSocks4Response();
   1.270 +  },
   1.271 +
   1.272 +  sendSocks4Response: function()
   1.273 +  {
   1.274 +    this.outbuf = '\x00\x5a\x00\x00\x00\x00\x00\x00';
   1.275 +    this.sendPing();
   1.276 +  },
   1.277 +
   1.278 +  checkSocks5Greeting: function()
   1.279 +  {
   1.280 +    if (this.inbuf.length < 2)
   1.281 +      return;
   1.282 +    var nmethods = this.inbuf[1];
   1.283 +    if (this.inbuf.length < 2 + nmethods)
   1.284 +      return;
   1.285 +
   1.286 +    do_check_true(nmethods >= 1);  
   1.287 +    var methods = this.inbuf.slice(2, 2 + nmethods);
   1.288 +    do_check_true(0 in methods);
   1.289 +
   1.290 +    this.inbuf = [];
   1.291 +    this.state = STATE_WAIT_SOCKS5_REQUEST;
   1.292 +    this.write('\x05\x00');
   1.293 +  },
   1.294 +  
   1.295 +  checkSocks5Request: function()
   1.296 +  {
   1.297 +    if (this.inbuf.length < 4)
   1.298 +      return;
   1.299 +
   1.300 +    do_check_eq(this.inbuf[0], 0x05);
   1.301 +    do_check_eq(this.inbuf[1], 0x01);
   1.302 +    do_check_eq(this.inbuf[2], 0x00);
   1.303 +
   1.304 +    var atype = this.inbuf[3];
   1.305 +    var len;
   1.306 +    var name = false;
   1.307 +
   1.308 +    switch (atype) {
   1.309 +      case 0x01:
   1.310 +        len = 4;
   1.311 +        break;
   1.312 +      case 0x03:
   1.313 +        len = this.inbuf[4];
   1.314 +        name = true;
   1.315 +        break;
   1.316 +      case 0x04:
   1.317 +        len = 16;
   1.318 +        break;  
   1.319 +      default:
   1.320 +        do_throw("Unknown address type " + atype);
   1.321 +    }
   1.322 +    
   1.323 +    if (name) {
   1.324 +      if (this.inbuf.length < 4 + len + 1 + 2)
   1.325 +        return;
   1.326 +
   1.327 +      buf = this.inbuf.slice(5, 5 + len);
   1.328 +      this.dest_name = buf2str(buf);
   1.329 +      len += 1;
   1.330 +    } else {
   1.331 +      if (this.inbuf.length < 4 + len + 2)
   1.332 +        return;
   1.333 +
   1.334 +      this.dest_addr = this.inbuf.slice(4, 4 + len);
   1.335 +    }
   1.336 +
   1.337 +    len += 4;
   1.338 +    this.dest_port = this.inbuf.slice(len, len + 2);
   1.339 +    this.inbuf = this.inbuf.slice(len + 2);
   1.340 +    this.sendSocks5Response();
   1.341 +  },
   1.342 +
   1.343 +  sendSocks5Response: function()
   1.344 +  {
   1.345 +    if (this.dest_addr.length == 16) {
   1.346 +      // send a successful response with the address, [::1]:80
   1.347 +      this.outbuf += '\x05\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x80';
   1.348 +    } else {
   1.349 +      // send a successful response with the address, 127.0.0.1:80
   1.350 +      this.outbuf += '\x05\x00\x00\x01\x7f\x00\x00\x01\x00\x80';
   1.351 +    }
   1.352 +    this.sendPing();
   1.353 +  },
   1.354 +
   1.355 +  sendPing: function()
   1.356 +  {
   1.357 +    print('server: sending ping');
   1.358 +    this.state = STATE_WAIT_PONG;
   1.359 +    this.outbuf += "PING!";
   1.360 +    this.inbuf = [];
   1.361 +    this.waitWrite(this.client_out);
   1.362 +    this.waitRead(this.client_in);
   1.363 +  },
   1.364 +
   1.365 +  checkPong: function()
   1.366 +  {
   1.367 +    var pong = buf2str(this.inbuf);
   1.368 +    do_check_eq(pong, "PONG!");
   1.369 +    this.state = STATE_GOT_PONG;
   1.370 +    this.waitRead(this.client_in);
   1.371 +  },
   1.372 +  
   1.373 +  close: function()
   1.374 +  {
   1.375 +    this.client_in.close();
   1.376 +    this.client_out.close();
   1.377 +  }
   1.378 +};
   1.379 +
   1.380 +function SocksTestServer()
   1.381 +{
   1.382 +  this.listener = ServerSocket(-1, true, -1);
   1.383 +  socks_listen_port = this.listener.port;
   1.384 +  print('server: listening on', socks_listen_port);
   1.385 +  this.listener.asyncListen(this);
   1.386 +  this.test_cases = [];
   1.387 +  this.client_connections = [];
   1.388 +  this.client_subprocess = null;
   1.389 +  // port is used as the ID for test cases
   1.390 +  this.test_port_id = 8000;
   1.391 +  this.tests_completed = 0;
   1.392 +}
   1.393 +SocksTestServer.prototype = {
   1.394 +  addTestCase: function(test)
   1.395 +  {
   1.396 +    test.finished = false;
   1.397 +    test.port = this.test_port_id++;
   1.398 +    this.test_cases.push(test);
   1.399 +  },
   1.400 +
   1.401 +  pickTest: function(id)
   1.402 +  {
   1.403 +    for (var i in this.test_cases) {
   1.404 +      var test = this.test_cases[i];
   1.405 +      if (test.port == id) {
   1.406 +        this.tests_completed++;
   1.407 +        return test;
   1.408 +      }
   1.409 +    }
   1.410 +    do_throw("No test case with id " + id);
   1.411 +  },
   1.412 +
   1.413 +  testCompleted: function(client)
   1.414 +  {
   1.415 +    var port_id = buf2int(client.dest_port);
   1.416 +    var test = this.pickTest(port_id);
   1.417 +    
   1.418 +    print('server: test finished', test.port);
   1.419 +    do_check_true(test != null);
   1.420 +    do_check_eq(test.expectedType || test.type, client.type);
   1.421 +    do_check_eq(test.port, port_id);
   1.422 +
   1.423 +    if (test.remote_dns)
   1.424 +      do_check_eq(test.host, client.dest_name);
   1.425 +    else
   1.426 +      do_check_eq(test.host, buf2ip(client.dest_addr));
   1.427 +    
   1.428 +    if (this.test_cases.length == this.tests_completed) {
   1.429 +      print('server: all tests completed');
   1.430 +      this.close();
   1.431 +      do_test_finished();
   1.432 +    }
   1.433 +  },
   1.434 +
   1.435 +  runClientSubprocess: function()
   1.436 +  {
   1.437 +    var argv = [];
   1.438 +
   1.439 +    // marshaled: socks_ver|server_port|dest_host|dest_port|<remote|local>
   1.440 +    for each (var test in this.test_cases) {
   1.441 +      var arg = test.type + '|' +
   1.442 +        String(socks_listen_port) + '|' +
   1.443 +        test.host + '|' + test.port + '|';
   1.444 +      if (test.remote_dns)
   1.445 +        arg += 'remote';
   1.446 +      else
   1.447 +        arg += 'local';
   1.448 +      print('server: using test case', arg);  
   1.449 +      argv.push(arg);      
   1.450 +    }
   1.451 +
   1.452 +    this.client_subprocess = runScriptSubprocess(
   1.453 +        'socks_client_subprocess.js', argv);
   1.454 +  },
   1.455 +
   1.456 +  onSocketAccepted: function(socket, trans)
   1.457 +  {
   1.458 +    print('server: got client connection');
   1.459 +    var input = trans.openInputStream(0, 0, 0);
   1.460 +    var output = trans.openOutputStream(0, 0, 0);
   1.461 +    var client = new SocksClient(this, input, output);
   1.462 +    this.client_connections.push(client);
   1.463 +  },
   1.464 +  
   1.465 +  onStopListening: function(socket)
   1.466 +  {  
   1.467 +  },
   1.468 +
   1.469 +  close: function()
   1.470 +  {
   1.471 +    if (this.client_subprocess) {
   1.472 +      try {
   1.473 +        this.client_subprocess.kill();      
   1.474 +      } catch (x) {
   1.475 +        do_note_exception(x, 'Killing subprocess failed');
   1.476 +      }
   1.477 +      this.client_subprocess = null;
   1.478 +    }
   1.479 +    for each (var client in this.client_connections)
   1.480 +      client.close();
   1.481 +    this.client_connections = [];
   1.482 +    if (this.listener) {
   1.483 +      this.listener.close();
   1.484 +      this.listener = null;
   1.485 +    }
   1.486 +  }
   1.487 +};
   1.488 +
   1.489 +function test_timeout()
   1.490 +{
   1.491 +  socks_test_server.close();
   1.492 +  do_throw("SOCKS test took too long!");
   1.493 +}
   1.494 +
   1.495 +function run_test()
   1.496 +{
   1.497 +  socks_test_server = new SocksTestServer();
   1.498 +
   1.499 +  socks_test_server.addTestCase({
   1.500 +        type: "socks4",
   1.501 +        host: '127.0.0.1',
   1.502 +        remote_dns: false,
   1.503 +  });
   1.504 +  socks_test_server.addTestCase({
   1.505 +        type: "socks4",
   1.506 +        host: '12345.xxx',
   1.507 +        remote_dns: true,
   1.508 +  });
   1.509 +  socks_test_server.addTestCase({
   1.510 +        type: "socks4",
   1.511 +        expectedType: "socks",
   1.512 +        host: '::1',
   1.513 +        remote_dns: false,
   1.514 +  });
   1.515 +  socks_test_server.addTestCase({
   1.516 +        type: "socks",
   1.517 +        host: '127.0.0.1',
   1.518 +        remote_dns: false,
   1.519 +  });
   1.520 +  socks_test_server.addTestCase({
   1.521 +        type: "socks",
   1.522 +        host: 'abcdefg.xxx',
   1.523 +        remote_dns: true,
   1.524 +  });
   1.525 +  socks_test_server.addTestCase({
   1.526 +        type: "socks",
   1.527 +        host: '::1',
   1.528 +        remote_dns: false,
   1.529 +  });
   1.530 +  socks_test_server.runClientSubprocess();
   1.531 +
   1.532 +  do_timeout(120 * 1000, test_timeout);
   1.533 +  do_test_pending();
   1.534 +}

mercurial