tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_speculative_connect.js (13346B)


      1 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
      2 /* vim: set ts=4 sts=4 et sw=4 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 "use strict";
      8 
      9 var CC = Components.Constructor;
     10 const ServerSocket = CC(
     11  "@mozilla.org/network/server-socket;1",
     12  "nsIServerSocket",
     13  "init"
     14 );
     15 var serv;
     16 var ios;
     17 
     18 /**
     19 * Example local IP addresses (literal IP address hostname).
     20 *
     21 * Note: for IPv6 Unique Local and Link Local, a wider range of addresses is
     22 * set aside than those most commonly used. Technically, link local addresses
     23 * include those beginning with fe80:: through febf::, although in practise
     24 * only fe80:: is used. Necko code blocks speculative connections for the wider
     25 * range; hence, this test considers that range too.
     26 */
     27 var localIPv4Literals = [
     28  // IPv4 RFC1918 \
     29  "10.0.0.1",
     30  "10.10.10.10",
     31  "10.255.255.255", // 10/8
     32  "172.16.0.1",
     33  "172.23.172.12",
     34  "172.31.255.255", // 172.16/20
     35  "192.168.0.1",
     36  "192.168.192.168",
     37  "192.168.255.255", // 192.168/16
     38  // IPv4 Link Local
     39  "169.254.0.1",
     40  "169.254.192.154",
     41  "169.254.255.255", // 169.254/16
     42 ];
     43 var localIPv6Literals = [
     44  // IPv6 Unique Local fc00::/7
     45  "fc00::1",
     46  "fdfe:dcba:9876:abcd:ef01:2345:6789:abcd",
     47  "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
     48  // IPv6 Link Local fe80::/10
     49  "fe80::1",
     50  "fe80::abcd:ef01:2345:6789",
     51  "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
     52 ];
     53 var localIPLiterals = localIPv4Literals.concat(localIPv6Literals);
     54 
     55 /**
     56 * Test function list and descriptions.
     57 */
     58 var testList = [
     59  test_localhost_http_speculative_connect,
     60  test_localhost_https_speculative_connect,
     61  test_hostnames_resolving_to_local_addresses,
     62  test_proxies_with_local_addresses,
     63  test_speculative_connect_with_proxy_filter,
     64 ];
     65 
     66 var testDescription = [
     67  "Expect pass with localhost, http",
     68  "Expect pass with localhost, https",
     69  "Expect failure with resolved local IPs",
     70  "Expect failure for proxies with local IPs",
     71  "Expect failure without notification callbacks",
     72 ];
     73 
     74 var testIdx = 0;
     75 var hostIdx = 0;
     76 
     77 /**
     78 * TestServer
     79 *
     80 * Implements nsIServerSocket for test_speculative_connect.
     81 */
     82 function TestServer() {
     83  this.listener = ServerSocket(-1, true, -1);
     84  this.listener.asyncListen(this);
     85 }
     86 
     87 TestServer.prototype = {
     88  QueryInterface: ChromeUtils.generateQI(["nsIServerSocket"]),
     89  onSocketAccepted() {
     90    try {
     91      this.listener.close();
     92    } catch (e) {}
     93    Assert.ok(true);
     94    next_test();
     95  },
     96 
     97  onStopListening() {},
     98 };
     99 
    100 /**
    101 * TestFailedStreamCallback
    102 *
    103 * Implements nsI[Input|Output]StreamCallback for socket layer tests.
    104 * Expect failure in all cases
    105 */
    106 function TestFailedStreamCallback(transport, hostname, next) {
    107  this.transport = transport;
    108  this.hostname = hostname;
    109  this.next = next;
    110  this.dummyContent = "G";
    111  this.closed = false;
    112 }
    113 
    114 TestFailedStreamCallback.prototype = {
    115  QueryInterface: ChromeUtils.generateQI([
    116    "nsIInputStreamCallback",
    117    "nsIOutputStreamCallback",
    118  ]),
    119  processException(e) {
    120    if (this.closed) {
    121      return;
    122    }
    123    do_check_instanceof(e, Ci.nsIException);
    124    // A refusal to connect speculatively should throw an error.
    125    Assert.equal(e.result, Cr.NS_ERROR_CONNECTION_REFUSED);
    126    this.closed = true;
    127    this.transport.close(Cr.NS_BINDING_ABORTED);
    128    this.next();
    129  },
    130  onOutputStreamReady(outstream) {
    131    info("outputstream handler.");
    132    Assert.notEqual(typeof outstream, undefined);
    133    try {
    134      outstream.write(this.dummyContent, this.dummyContent.length);
    135    } catch (e) {
    136      this.processException(e);
    137      return;
    138    }
    139    info("no exception on write. Wait for read.");
    140  },
    141  onInputStreamReady(instream) {
    142    info("inputstream handler.");
    143    Assert.notEqual(typeof instream, undefined);
    144    try {
    145      instream.available();
    146    } catch (e) {
    147      this.processException(e);
    148      return;
    149    }
    150    do_throw("Speculative Connect should have failed for " + this.hostname);
    151    this.transport.close(Cr.NS_BINDING_ABORTED);
    152    this.next();
    153  },
    154 };
    155 
    156 /**
    157 * test_localhost_http_speculative_connect
    158 *
    159 * Tests a basic positive case using nsIOService.SpeculativeConnect:
    160 * connecting to localhost via http.
    161 */
    162 function test_localhost_http_speculative_connect() {
    163  serv = new TestServer();
    164  var ssm = Services.scriptSecurityManager;
    165  var URI = ios.newURI(
    166    "http://localhost:" + serv.listener.port + "/just/a/test"
    167  );
    168  var principal = ssm.createContentPrincipal(URI, {});
    169 
    170  ios
    171    .QueryInterface(Ci.nsISpeculativeConnect)
    172    .speculativeConnect(URI, principal, null, false);
    173 }
    174 
    175 /**
    176 * test_localhost_https_speculative_connect
    177 *
    178 * Tests a basic positive case using nsIOService.SpeculativeConnect:
    179 * connecting to localhost via https.
    180 */
    181 function test_localhost_https_speculative_connect() {
    182  serv = new TestServer();
    183  var ssm = Services.scriptSecurityManager;
    184  var URI = ios.newURI(
    185    "https://localhost:" + serv.listener.port + "/just/a/test"
    186  );
    187  var principal = ssm.createContentPrincipal(URI, {});
    188 
    189  ios
    190    .QueryInterface(Ci.nsISpeculativeConnect)
    191    .speculativeConnect(URI, principal, null, false);
    192 }
    193 
    194 /* Speculative connections should not be allowed for hosts with local IP
    195 * addresses (Bug 853423). That list includes:
    196 *  -- IPv4 RFC1918 and Link Local Addresses.
    197 *  -- IPv6 Unique and Link Local Addresses.
    198 *
    199 * Two tests are required:
    200 *  1. Verify IP Literals passed to the SpeculativeConnect API.
    201 *  2. Verify hostnames that need to be resolved at the socket layer.
    202 */
    203 
    204 /**
    205 * test_hostnames_resolving_to_addresses
    206 *
    207 * Common test function for resolved hostnames. Takes a list of hosts, a
    208 * boolean to determine if the test is expected to succeed or fail, and a
    209 * function to call the next test case.
    210 */
    211 function test_hostnames_resolving_to_addresses(host, next) {
    212  info(host);
    213  var sts = Cc["@mozilla.org/network/socket-transport-service;1"].getService(
    214    Ci.nsISocketTransportService
    215  );
    216  Assert.notEqual(typeof sts, undefined);
    217  var transport = sts.createTransport([], host, 80, null, null);
    218  Assert.notEqual(typeof transport, undefined);
    219 
    220  transport.connectionFlags = Ci.nsISocketTransport.DISABLE_RFC1918;
    221  transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 1);
    222  transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, 1);
    223  Assert.equal(1, transport.getTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT));
    224 
    225  var outStream = transport.openOutputStream(
    226    Ci.nsITransport.OPEN_UNBUFFERED,
    227    0,
    228    0
    229  );
    230  var inStream = transport.openInputStream(0, 0, 0);
    231  Assert.notEqual(typeof outStream, undefined);
    232  Assert.notEqual(typeof inStream, undefined);
    233 
    234  var callback = new TestFailedStreamCallback(transport, host, next);
    235  Assert.notEqual(typeof callback, undefined);
    236 
    237  // Need to get main thread pointer to ensure nsSocketTransport::AsyncWait
    238  // adds callback to ns*StreamReadyEvent on main thread, and doesn't
    239  // addref off the main thread.
    240  var gThreadManager = Services.tm;
    241  var mainThread = gThreadManager.currentThread;
    242 
    243  try {
    244    outStream
    245      .QueryInterface(Ci.nsIAsyncOutputStream)
    246      .asyncWait(callback, 0, 0, mainThread);
    247    inStream
    248      .QueryInterface(Ci.nsIAsyncInputStream)
    249      .asyncWait(callback, 0, 0, mainThread);
    250  } catch (e) {
    251    do_throw("asyncWait should not fail!");
    252  }
    253 }
    254 
    255 /**
    256 * test_hostnames_resolving_to_local_addresses
    257 *
    258 * Creates an nsISocketTransport and simulates a speculative connect request
    259 * for a hostname that resolves to a local IP address.
    260 * Runs asynchronously; on test success (i.e. failure to connect), the callback
    261 * will call this function again until all hostnames in the test list are done.
    262 *
    263 * Note: This test also uses an IP literal for the hostname. This should be ok,
    264 * as the socket layer will ask for the hostname to be resolved anyway, and DNS
    265 * code should return a numerical version of the address internally.
    266 */
    267 function test_hostnames_resolving_to_local_addresses() {
    268  if (hostIdx >= localIPLiterals.length) {
    269    // No more local IP addresses; move on.
    270    next_test();
    271    return;
    272  }
    273  var host = localIPLiterals[hostIdx++];
    274  // Test another local IP address when the current one is done.
    275  var next = test_hostnames_resolving_to_local_addresses;
    276  test_hostnames_resolving_to_addresses(host, next);
    277 }
    278 
    279 /**
    280 * test_speculative_connect_with_host_list
    281 *
    282 * Common test function for resolved proxy hosts. Takes a list of hosts, a
    283 * boolean to determine if the test is expected to succeed or fail, and a
    284 * function to call the next test case.
    285 */
    286 function test_proxies(proxyHost, next) {
    287  info("Proxy: " + proxyHost);
    288  var sts = Cc["@mozilla.org/network/socket-transport-service;1"].getService(
    289    Ci.nsISocketTransportService
    290  );
    291  Assert.notEqual(typeof sts, undefined);
    292  var pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
    293  Assert.notEqual(typeof pps, undefined);
    294 
    295  var proxyInfo = pps.newProxyInfo("http", proxyHost, 8080, "", "", 0, 1, null);
    296  Assert.notEqual(typeof proxyInfo, undefined);
    297 
    298  var transport = sts.createTransport([], "dummyHost", 80, proxyInfo, null);
    299  Assert.notEqual(typeof transport, undefined);
    300 
    301  transport.connectionFlags = Ci.nsISocketTransport.DISABLE_RFC1918;
    302 
    303  transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 1);
    304  Assert.equal(1, transport.getTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT));
    305  transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, 1);
    306 
    307  var outStream = transport.openOutputStream(
    308    Ci.nsITransport.OPEN_UNBUFFERED,
    309    0,
    310    0
    311  );
    312  var inStream = transport.openInputStream(0, 0, 0);
    313  Assert.notEqual(typeof outStream, undefined);
    314  Assert.notEqual(typeof inStream, undefined);
    315 
    316  var callback = new TestFailedStreamCallback(transport, proxyHost, next);
    317  Assert.notEqual(typeof callback, undefined);
    318 
    319  // Need to get main thread pointer to ensure nsSocketTransport::AsyncWait
    320  // adds callback to ns*StreamReadyEvent on main thread, and doesn't
    321  // addref off the main thread.
    322  var gThreadManager = Services.tm;
    323  var mainThread = gThreadManager.currentThread;
    324 
    325  try {
    326    outStream
    327      .QueryInterface(Ci.nsIAsyncOutputStream)
    328      .asyncWait(callback, 0, 0, mainThread);
    329    inStream
    330      .QueryInterface(Ci.nsIAsyncInputStream)
    331      .asyncWait(callback, 0, 0, mainThread);
    332  } catch (e) {
    333    do_throw("asyncWait should not fail!");
    334  }
    335 }
    336 
    337 /**
    338 * test_proxies_with_local_addresses
    339 *
    340 * Creates an nsISocketTransport and simulates a speculative connect request
    341 * for a proxy that resolves to a local IP address.
    342 * Runs asynchronously; on test success (i.e. failure to connect), the callback
    343 * will call this function again until all proxies in the test list are done.
    344 *
    345 * Note: This test also uses an IP literal for the proxy. This should be ok,
    346 * as the socket layer will ask for the proxy to be resolved anyway, and DNS
    347 * code should return a numerical version of the address internally.
    348 */
    349 function test_proxies_with_local_addresses() {
    350  if (hostIdx >= localIPLiterals.length) {
    351    // No more local IP addresses; move on.
    352    next_test();
    353    return;
    354  }
    355  var host = localIPLiterals[hostIdx++];
    356  // Test another local IP address when the current one is done.
    357  var next = test_proxies_with_local_addresses;
    358  test_proxies(host, next);
    359 }
    360 
    361 class ProxyFilter {
    362  constructor(type, host, port, flags) {
    363    this._type = type;
    364    this._host = host;
    365    this._port = port;
    366    this._flags = flags;
    367    this.QueryInterface = ChromeUtils.generateQI(["nsIProtocolProxyFilter"]);
    368  }
    369  applyFilter(uri, pi, cb) {
    370    const pps =
    371      Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
    372    cb.onProxyFilterResult(
    373      pps.newProxyInfo(
    374        this._type,
    375        this._host,
    376        this._port,
    377        "",
    378        "",
    379        this._flags,
    380        1000,
    381        null
    382      )
    383    );
    384  }
    385 }
    386 
    387 function test_speculative_connect_with_proxy_filter() {
    388  let filter = new ProxyFilter("https", "localhost", 80, 0);
    389  let pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
    390  pps.registerFilter(filter, 10);
    391  let URI = ios.newURI("https://not-exist-dommain.com");
    392  let principal = Services.scriptSecurityManager.createContentPrincipal(
    393    URI,
    394    {}
    395  );
    396 
    397  Assert.throws(
    398    () =>
    399      ios
    400        .QueryInterface(Ci.nsISpeculativeConnect)
    401        .speculativeConnect(URI, principal, null, false),
    402    /NS_ERROR_FAILURE/,
    403    "speculativeConnect should throw when no callback is provided and a proxy filter is registered"
    404  );
    405  pps.unregisterFilter(filter);
    406  next_test();
    407 }
    408 
    409 /**
    410 * next_test
    411 *
    412 * Calls the next test in testList. Each test is responsible for calling this
    413 * function when its test cases are complete.
    414 */
    415 function next_test() {
    416  if (testIdx >= testList.length) {
    417    // No more tests; we're done.
    418    do_test_finished();
    419    return;
    420  }
    421  info("SpeculativeConnect: " + testDescription[testIdx]);
    422  hostIdx = 0;
    423  // Start next test in list.
    424  testList[testIdx++]();
    425 }
    426 
    427 /**
    428 * run_test
    429 *
    430 * Main entry function for test execution.
    431 */
    432 function run_test() {
    433  ios = Services.io;
    434 
    435  Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
    436  registerCleanupFunction(() => {
    437    Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
    438  });
    439 
    440  do_test_pending();
    441  next_test();
    442 }