tor-browser

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

test_http2-proxy.js (27805B)


      1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et: */
      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 /**
      8 * This test checks following expectations when using HTTP/2 proxy:
      9 *
     10 * - when we request https access, we don't create different sessions for
     11 *   different origins, only new tunnels inside a single session
     12 * - when the isolation key (`proxy_isolation`) is changed, new single session
     13 *   is created for new requests to same origins as before
     14 * - error code returned from the tunnel (a proxy error - not end-server
     15 *   error!) doesn't kill the existing session
     16 * - check we are seeing expected nsresult error codes on channels
     17 *   (nsIChannel.status) corresponding to different proxy status code
     18 *   responses (502, 504, 407, ...)
     19 * - check we don't try to ask for credentials or otherwise authenticate to
     20 *   the proxy when 407 is returned and there is no Proxy-Authenticate
     21 *   response header sent
     22 * - a stream reset for a connect stream to the proxy does not cause session to
     23 *   be closed and the request through the proxy will failed.
     24 * - a "soft" stream error on a connection to the origin server will close the
     25 *   stream, but it will not close niether the HTTP/2 session to the proxy nor
     26 *   to the origin server.
     27 * - a "hard" stream error on a connection to the origin server will close the
     28 *   HTTP/2 session to the origin server, but it will not close the HTTP/2
     29 *   session to the proxy.
     30 */
     31 
     32 /* eslint-env node */
     33 /* global serverPort */
     34 
     35 "use strict";
     36 
     37 // We don't normally allow localhost channels to be proxied, but this
     38 // is easier than updating all the certs and/or domains.
     39 Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
     40 registerCleanupFunction(() => {
     41  Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
     42 });
     43 
     44 const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
     45 const { NodeServer } = ChromeUtils.importESModule(
     46  "resource://testing-common/NodeServer.sys.mjs"
     47 );
     48 
     49 let proxy_port;
     50 let filter;
     51 let proxy;
     52 
     53 // See moz-http2
     54 const proxy_auth = "authorization-token";
     55 let proxy_isolation;
     56 
     57 class ProxyFilter {
     58  constructor(type, host, port, flags) {
     59    this._type = type;
     60    this._host = host;
     61    this._port = port;
     62    this._flags = flags;
     63    this.QueryInterface = ChromeUtils.generateQI(["nsIProtocolProxyFilter"]);
     64  }
     65  applyFilter(uri, pi, cb) {
     66    cb.onProxyFilterResult(
     67      pps.newProxyInfo(
     68        this._type,
     69        this._host,
     70        this._port,
     71        proxy_auth,
     72        proxy_isolation,
     73        this._flags,
     74        1000,
     75        null
     76      )
     77    );
     78  }
     79 }
     80 
     81 class UnxpectedAuthPrompt2 {
     82  constructor(signal) {
     83    this.signal = signal;
     84    this.QueryInterface = ChromeUtils.generateQI(["nsIAuthPrompt2"]);
     85  }
     86  asyncPromptAuth() {
     87    this.signal.triggered = true;
     88    throw Components.Exception("", Cr.ERROR_UNEXPECTED);
     89  }
     90 }
     91 
     92 class SimpleAuthPrompt2 {
     93  constructor(signal) {
     94    this.signal = signal;
     95    this.QueryInterface = ChromeUtils.generateQI(["nsIAuthPrompt2"]);
     96  }
     97  asyncPromptAuth(channel, callback, context, encryptionLevel, authInfo) {
     98    this.signal.triggered = true;
     99    executeSoon(function () {
    100      authInfo.username = "user";
    101      authInfo.password = "pass";
    102      callback.onAuthAvailable(context, authInfo);
    103    });
    104  }
    105 }
    106 
    107 class AuthRequestor {
    108  constructor(prompt) {
    109    this.prompt = prompt;
    110    this.QueryInterface = ChromeUtils.generateQI(["nsIInterfaceRequestor"]);
    111  }
    112  getInterface(iid) {
    113    if (iid.equals(Ci.nsIAuthPrompt2)) {
    114      return this.prompt();
    115    }
    116    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
    117  }
    118 }
    119 
    120 function createPrincipal(url) {
    121  var ssm = Services.scriptSecurityManager;
    122  try {
    123    return ssm.createContentPrincipal(Services.io.newURI(url), {});
    124  } catch (e) {
    125    return null;
    126  }
    127 }
    128 
    129 function make_channel(url) {
    130  return NetUtil.newChannel({
    131    uri: url,
    132    loadingPrincipal: createPrincipal(url),
    133    securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
    134    // Using TYPE_DOCUMENT for the authentication dialog test, it'd be blocked for other types
    135    contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
    136  });
    137 }
    138 
    139 function get_response(channel, flags = CL_ALLOW_UNKNOWN_CL, delay = 0) {
    140  return new Promise(resolve => {
    141    var listener = new ChannelListener(
    142      (request, data) => {
    143        request.QueryInterface(Ci.nsIHttpChannel);
    144        const status = request.status;
    145        const http_code = status ? undefined : request.responseStatus;
    146        request.QueryInterface(Ci.nsIProxiedChannel);
    147        const proxy_connect_response_code =
    148          request.httpProxyConnectResponseCode;
    149        resolve({ status, http_code, data, proxy_connect_response_code });
    150      },
    151      null,
    152      flags
    153    );
    154    if (delay > 0) {
    155      do_timeout(delay, function () {
    156        channel.asyncOpen(listener);
    157      });
    158    } else {
    159      channel.asyncOpen(listener);
    160    }
    161  });
    162 }
    163 
    164 let initial_session_count = 0;
    165 
    166 class http2ProxyCode {
    167  static listen(server, envport) {
    168    if (!server) {
    169      return Promise.resolve(0);
    170    }
    171 
    172    let portSelection = 0;
    173    if (envport !== undefined) {
    174      try {
    175        portSelection = parseInt(envport, 10);
    176      } catch (e) {
    177        portSelection = -1;
    178      }
    179    }
    180    return new Promise(resolve => {
    181      server.listen(portSelection, "0.0.0.0", 2000, () => {
    182        resolve(server.address().port);
    183      });
    184    });
    185  }
    186 
    187  static startNewProxy() {
    188    const fs = require("fs");
    189    const options = {
    190      key: fs.readFileSync(__dirname + "/http2-cert.key"),
    191      cert: fs.readFileSync(__dirname + "/http2-cert.pem"),
    192    };
    193    const http2 = require("http2");
    194    global.proxy = http2.createSecureServer(options);
    195    this.setupProxy();
    196    return http2ProxyCode.listen(proxy).then(port => {
    197      return { port, success: true };
    198    });
    199  }
    200 
    201  static closeProxy() {
    202    proxy.closeSockets();
    203    return new Promise(resolve => {
    204      proxy.close(resolve);
    205    });
    206  }
    207 
    208  static proxySessionCount() {
    209    if (!proxy) {
    210      return 0;
    211    }
    212    return proxy.proxy_session_count;
    213  }
    214 
    215  static proxySessionToOriginServersCount() {
    216    if (!proxy) {
    217      return 0;
    218    }
    219    return proxy.sessionToOriginServersCount;
    220  }
    221 
    222  static setupProxy() {
    223    if (!proxy) {
    224      throw new Error("proxy is null");
    225    }
    226    proxy.proxy_session_count = 0;
    227    proxy.sessionToOriginServersCount = 0;
    228    proxy.on("session", () => {
    229      ++proxy.proxy_session_count;
    230    });
    231 
    232    // We need to track active connections so we can forcefully close keep-alive
    233    // connections when shutting down the proxy.
    234    proxy.socketIndex = 0;
    235    proxy.socketMap = {};
    236    proxy.on("connection", function (socket) {
    237      let index = proxy.socketIndex++;
    238      proxy.socketMap[index] = socket;
    239      socket.on("close", function () {
    240        delete proxy.socketMap[index];
    241      });
    242    });
    243    proxy.closeSockets = function () {
    244      for (let i in proxy.socketMap) {
    245        proxy.socketMap[i].destroy();
    246      }
    247    };
    248 
    249    proxy.on("stream", (stream, headers) => {
    250      if (headers[":method"] !== "CONNECT") {
    251        // Only accept CONNECT requests
    252        stream.respond({ ":status": 405 });
    253        stream.end();
    254        return;
    255      }
    256 
    257      const target = headers[":authority"];
    258 
    259      const authorization_token = headers["proxy-authorization"];
    260      if (target == "407.example.com:443") {
    261        stream.respond({ ":status": 407 });
    262        // Deliberately send no Proxy-Authenticate header
    263        stream.end();
    264        return;
    265      }
    266      if (target == "407.basic.example.com:443") {
    267        // we want to return a different response than 407 to not re-request
    268        // credentials (and thus loop) but also not 200 to not let the channel
    269        // attempt to waste time connecting a non-existing https server - hence
    270        // 418 I'm a teapot :)
    271        if ("Basic dXNlcjpwYXNz" == authorization_token) {
    272          stream.respond({ ":status": 418 });
    273          stream.end();
    274          return;
    275        }
    276        stream.respond({
    277          ":status": 407,
    278          "proxy-authenticate": "Basic realm='foo'",
    279        });
    280        stream.end();
    281        return;
    282      }
    283      if (target == "404.example.com:443") {
    284        // 404 Not Found, a response code that a proxy should return when the host can't be found
    285        stream.respond({ ":status": 404 });
    286        stream.end();
    287        return;
    288      }
    289      if (target == "429.example.com:443") {
    290        // 429 Too Many Requests, a response code that a proxy should return when receiving too many requests
    291        stream.respond({ ":status": 429 });
    292        stream.end();
    293        return;
    294      }
    295      if (target == "502.example.com:443") {
    296        // 502 Bad Gateway, a response code mostly resembling immediate connection error
    297        stream.respond({ ":status": 502 });
    298        stream.end();
    299        return;
    300      }
    301      if (target == "504.example.com:443") {
    302        // 504 Gateway Timeout, did not receive a timely response from an upstream server
    303        stream.respond({ ":status": 504 });
    304        stream.end();
    305        return;
    306      }
    307      if (target == "reset.example.com:443") {
    308        // always reset the stream.
    309        stream.close(0x0);
    310        return;
    311      }
    312 
    313      ++proxy.sessionToOriginServersCount;
    314      const net = require("net");
    315      const socket = net.connect(serverPort, "127.0.0.1", () => {
    316        try {
    317          stream.respond({ ":status": 200 });
    318          socket.pipe(stream);
    319          stream.pipe(socket);
    320        } catch (exception) {
    321          console.log(exception);
    322          stream.close();
    323        }
    324      });
    325      socket.on("error", error => {
    326        throw new Error(
    327          `Unexpected error when conneting the HTTP/2 server from the HTTP/2 proxy during CONNECT handling: '${error}'`
    328        );
    329      });
    330    });
    331  }
    332 }
    333 
    334 async function proxy_session_counter() {
    335  let data = await NodeServer.execute(
    336    processId,
    337    `http2ProxyCode.proxySessionCount()`
    338  );
    339  return parseInt(data) - initial_session_count;
    340 }
    341 async function proxy_session_to_origin_server_counter() {
    342  let data = await NodeServer.execute(
    343    processId,
    344    `http2ProxyCode.proxySessionToOriginServersCount()`
    345  );
    346  return parseInt(data) - initial_session_count;
    347 }
    348 let processId;
    349 add_task(async function setup() {
    350  // Set to allow the cert presented by our H2 server
    351  do_get_profile();
    352 
    353  // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
    354  // so add that cert to the trust list as a signing cert.
    355  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
    356    Ci.nsIX509CertDB
    357  );
    358  addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
    359 
    360  let server_port = Services.env.get("MOZHTTP2_PORT");
    361  Assert.notEqual(server_port, null);
    362  processId = await NodeServer.fork();
    363  await NodeServer.execute(processId, `serverPort = ${server_port}`);
    364  await NodeServer.execute(processId, http2ProxyCode);
    365  let newProxy = await NodeServer.execute(
    366    processId,
    367    `http2ProxyCode.startNewProxy()`
    368  );
    369  proxy_port = newProxy.port;
    370  Assert.notEqual(proxy_port, null);
    371 
    372  Services.prefs.setStringPref(
    373    "services.settings.server",
    374    `data:,#remote-settings-dummy/v1`
    375  );
    376 
    377  Services.prefs.setBoolPref("network.http.http2.enabled", true);
    378 
    379  // make all native resolve calls "secretly" resolve localhost instead
    380  Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
    381 
    382  filter = new ProxyFilter("https", "localhost", proxy_port, 0);
    383  pps.registerFilter(filter, 10);
    384 
    385  initial_session_count = await proxy_session_counter();
    386  info(`Initial proxy session count = ${initial_session_count}`);
    387 });
    388 
    389 registerCleanupFunction(async () => {
    390  Services.prefs.clearUserPref("services.settings.server");
    391  Services.prefs.clearUserPref("network.http.http2.enabled");
    392  Services.prefs.clearUserPref("network.dns.native-is-localhost");
    393 
    394  pps.unregisterFilter(filter);
    395 
    396  await NodeServer.execute(processId, `http2ProxyCode.closeProxy()`);
    397  await NodeServer.kill(processId);
    398 });
    399 
    400 /**
    401 * Test series beginning.
    402 */
    403 
    404 // Check we reach the h2 end server and keep only one session with the proxy for two different origin.
    405 // Here we use the first isolation token.
    406 add_task(async function proxy_success_one_session() {
    407  proxy_isolation = "TOKEN1";
    408 
    409  const foo = await get_response(
    410    make_channel(`https://foo.example.com/random-request-1`)
    411  );
    412  const alt1 = await get_response(
    413    make_channel(`https://alt1.example.com/random-request-2`)
    414  );
    415 
    416  Assert.equal(foo.status, Cr.NS_OK);
    417  Assert.equal(foo.proxy_connect_response_code, 200);
    418  Assert.equal(foo.http_code, 200);
    419  Assert.ok(foo.data.match("random-request-1"));
    420  Assert.ok(foo.data.match("You Win!"));
    421  Assert.equal(alt1.status, Cr.NS_OK);
    422  Assert.equal(alt1.proxy_connect_response_code, 200);
    423  Assert.equal(alt1.http_code, 200);
    424  Assert.ok(alt1.data.match("random-request-2"));
    425  Assert.ok(alt1.data.match("You Win!"));
    426  Assert.equal(
    427    await proxy_session_counter(),
    428    1,
    429    "Created just one session with the proxy"
    430  );
    431 });
    432 
    433 // The proxy responses with 407 instead of 200 Connected, make sure we get a proper error
    434 // code from the channel and not try to ask for any credentials.
    435 add_task(async function proxy_auth_failure() {
    436  const chan = make_channel(`https://407.example.com/`);
    437  const auth_prompt = { triggered: false };
    438  chan.notificationCallbacks = new AuthRequestor(
    439    () => new UnxpectedAuthPrompt2(auth_prompt)
    440  );
    441  const { status, http_code, proxy_connect_response_code } = await get_response(
    442    chan,
    443    CL_EXPECT_FAILURE
    444  );
    445 
    446  Assert.equal(status, Cr.NS_ERROR_PROXY_AUTHENTICATION_FAILED);
    447  Assert.equal(proxy_connect_response_code, 407);
    448  Assert.equal(http_code, undefined);
    449  Assert.equal(auth_prompt.triggered, false, "Auth prompt didn't trigger");
    450  Assert.equal(
    451    await proxy_session_counter(),
    452    1,
    453    "No new session created by 407"
    454  );
    455 });
    456 
    457 // The proxy responses with 407 with Proxy-Authenticate header presence. Make
    458 // sure that we prompt the auth prompt to ask for credentials.
    459 add_task(async function proxy_auth_basic() {
    460  const chan = make_channel(`https://407.basic.example.com/`);
    461  const auth_prompt = { triggered: false };
    462  chan.notificationCallbacks = new AuthRequestor(
    463    () => new SimpleAuthPrompt2(auth_prompt)
    464  );
    465  const { status, http_code, proxy_connect_response_code } = await get_response(
    466    chan,
    467    CL_EXPECT_FAILURE
    468  );
    469 
    470  // 418 indicates we pass the basic authentication.
    471  Assert.equal(status, Cr.NS_ERROR_PROXY_CONNECTION_REFUSED);
    472  Assert.equal(proxy_connect_response_code, 418);
    473  Assert.equal(http_code, undefined);
    474  Assert.equal(auth_prompt.triggered, true, "Auth prompt should trigger");
    475  Assert.equal(
    476    await proxy_session_counter(),
    477    1,
    478    "No new session created by 407"
    479  );
    480 });
    481 
    482 // 502 Bad gateway code returned by the proxy, still one session only, proper different code
    483 // from the channel.
    484 add_task(async function proxy_bad_gateway_failure() {
    485  const { status, http_code, proxy_connect_response_code } = await get_response(
    486    make_channel(`https://502.example.com/`),
    487    CL_EXPECT_FAILURE
    488  );
    489 
    490  Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
    491  Assert.equal(proxy_connect_response_code, 502);
    492  Assert.equal(http_code, undefined);
    493  Assert.equal(
    494    await proxy_session_counter(),
    495    1,
    496    "No new session created by 502 after 407"
    497  );
    498 });
    499 
    500 // Second 502 Bad gateway code returned by the proxy, still one session only with the proxy.
    501 add_task(async function proxy_bad_gateway_failure_two() {
    502  const { status, http_code, proxy_connect_response_code } = await get_response(
    503    make_channel(`https://502.example.com/`),
    504    CL_EXPECT_FAILURE
    505  );
    506 
    507  Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
    508  Assert.equal(proxy_connect_response_code, 502);
    509  Assert.equal(http_code, undefined);
    510  Assert.equal(
    511    await proxy_session_counter(),
    512    1,
    513    "No new session created by second 502"
    514  );
    515 });
    516 
    517 // 504 Gateway timeout code returned by the proxy, still one session only, proper different code
    518 // from the channel.
    519 add_task(async function proxy_gateway_timeout_failure() {
    520  const { status, http_code, proxy_connect_response_code } = await get_response(
    521    make_channel(`https://504.example.com/`),
    522    CL_EXPECT_FAILURE
    523  );
    524 
    525  Assert.equal(status, Cr.NS_ERROR_PROXY_GATEWAY_TIMEOUT);
    526  Assert.equal(proxy_connect_response_code, 504);
    527  Assert.equal(http_code, undefined);
    528  Assert.equal(
    529    await proxy_session_counter(),
    530    1,
    531    "No new session created by 504 after 502"
    532  );
    533 });
    534 
    535 // 404 Not Found means the proxy could not resolve the host.  As for other error responses
    536 // we still expect this not to close the existing session.
    537 add_task(async function proxy_host_not_found_failure() {
    538  const { status, http_code, proxy_connect_response_code } = await get_response(
    539    make_channel(`https://404.example.com/`),
    540    CL_EXPECT_FAILURE
    541  );
    542 
    543  Assert.equal(status, Cr.NS_ERROR_UNKNOWN_HOST);
    544  Assert.equal(proxy_connect_response_code, 404);
    545  Assert.equal(http_code, undefined);
    546  Assert.equal(
    547    await proxy_session_counter(),
    548    1,
    549    "No new session created by 404 after 504"
    550  );
    551 });
    552 
    553 add_task(async function proxy_too_many_requests_failure() {
    554  const { status, http_code, proxy_connect_response_code } = await get_response(
    555    make_channel(`https://429.example.com/`),
    556    CL_EXPECT_FAILURE
    557  );
    558 
    559  Assert.equal(status, Cr.NS_ERROR_PROXY_TOO_MANY_REQUESTS);
    560  Assert.equal(proxy_connect_response_code, 429);
    561  Assert.equal(http_code, undefined);
    562  Assert.equal(
    563    await proxy_session_counter(),
    564    1,
    565    "No new session created by 429 after 504"
    566  );
    567 });
    568 
    569 add_task(async function proxy_stream_reset_failure() {
    570  const { status, http_code, proxy_connect_response_code } = await get_response(
    571    make_channel(`https://reset.example.com/`),
    572    CL_EXPECT_FAILURE
    573  );
    574 
    575  Assert.equal(status, Cr.NS_ERROR_NET_INTERRUPT);
    576  Assert.equal(proxy_connect_response_code, 0);
    577  Assert.equal(http_code, undefined);
    578  Assert.equal(
    579    await proxy_session_counter(),
    580    1,
    581    "No new session created by 429 after 504"
    582  );
    583 });
    584 
    585 // The soft errors are not closing the session.
    586 add_task(async function origin_server_stream_soft_failure() {
    587  var current_num_sessions_to_origin_server =
    588    await proxy_session_to_origin_server_counter();
    589 
    590  const { status, http_code, proxy_connect_response_code } = await get_response(
    591    make_channel(`https://foo.example.com/illegalhpacksoft`),
    592    CL_EXPECT_FAILURE
    593  );
    594 
    595  Assert.equal(status, Cr.NS_ERROR_ILLEGAL_VALUE);
    596  Assert.equal(proxy_connect_response_code, 200);
    597  Assert.equal(http_code, undefined);
    598  Assert.equal(
    599    await proxy_session_counter(),
    600    1,
    601    "No session to the proxy closed by soft stream errors"
    602  );
    603  Assert.equal(
    604    await proxy_session_to_origin_server_counter(),
    605    current_num_sessions_to_origin_server,
    606    "No session to the origin server closed by soft stream errors"
    607  );
    608 });
    609 
    610 // The soft errors are not closing the session.
    611 add_task(
    612  async function origin_server_stream_soft_failure_multiple_streams_not_affected() {
    613    var current_num_sessions_to_origin_server =
    614      await proxy_session_to_origin_server_counter();
    615 
    616    let should_succeed = get_response(
    617      make_channel(`https://foo.example.com/750ms`)
    618    );
    619 
    620    const failed = await get_response(
    621      make_channel(`https://foo.example.com/illegalhpacksoft`),
    622      CL_EXPECT_FAILURE,
    623      20
    624    );
    625 
    626    const succeeded = await should_succeed;
    627 
    628    Assert.equal(failed.status, Cr.NS_ERROR_ILLEGAL_VALUE);
    629    Assert.equal(failed.proxy_connect_response_code, 200);
    630    Assert.equal(failed.http_code, undefined);
    631    Assert.equal(succeeded.status, Cr.NS_OK);
    632    Assert.equal(succeeded.proxy_connect_response_code, 200);
    633    Assert.equal(succeeded.http_code, 200);
    634    Assert.equal(
    635      await proxy_session_counter(),
    636      1,
    637      "No session to the proxy closed by soft stream errors"
    638    );
    639    Assert.equal(
    640      await proxy_session_to_origin_server_counter(),
    641      current_num_sessions_to_origin_server,
    642      "No session to the origin server closed by soft stream errors"
    643    );
    644  }
    645 );
    646 
    647 // Make sure that the above error codes don't kill the session to the proxy.
    648 add_task(async function proxy_success_still_one_session() {
    649  const foo = await get_response(
    650    make_channel(`https://foo.example.com/random-request-1`)
    651  );
    652  const alt1 = await get_response(
    653    make_channel(`https://alt1.example.com/random-request-2`)
    654  );
    655 
    656  Assert.equal(foo.status, Cr.NS_OK);
    657  Assert.equal(foo.http_code, 200);
    658  Assert.equal(foo.proxy_connect_response_code, 200);
    659  Assert.ok(foo.data.match("random-request-1"));
    660  Assert.equal(alt1.status, Cr.NS_OK);
    661  Assert.equal(alt1.proxy_connect_response_code, 200);
    662  Assert.equal(alt1.http_code, 200);
    663  Assert.ok(alt1.data.match("random-request-2"));
    664  Assert.equal(
    665    await proxy_session_counter(),
    666    1,
    667    "No new session to the proxy created after stream error codes"
    668  );
    669 });
    670 
    671 // Have a new isolation key, this means we are expected to create a new, and again one only,
    672 // session with the proxy to reach the end server.
    673 add_task(async function proxy_success_isolated_session() {
    674  Assert.notEqual(proxy_isolation, "TOKEN2");
    675  proxy_isolation = "TOKEN2";
    676 
    677  const foo = await get_response(
    678    make_channel(`https://foo.example.com/random-request-1`)
    679  );
    680  const alt1 = await get_response(
    681    make_channel(`https://alt1.example.com/random-request-2`)
    682  );
    683  const lh = await get_response(
    684    make_channel(`https://localhost/random-request-3`)
    685  );
    686 
    687  Assert.equal(foo.status, Cr.NS_OK);
    688  Assert.equal(foo.proxy_connect_response_code, 200);
    689  Assert.equal(foo.http_code, 200);
    690  Assert.ok(foo.data.match("random-request-1"));
    691  Assert.ok(foo.data.match("You Win!"));
    692  Assert.equal(alt1.status, Cr.NS_OK);
    693  Assert.equal(alt1.proxy_connect_response_code, 200);
    694  Assert.equal(alt1.http_code, 200);
    695  Assert.ok(alt1.data.match("random-request-2"));
    696  Assert.ok(alt1.data.match("You Win!"));
    697  Assert.equal(lh.status, Cr.NS_OK);
    698  Assert.equal(lh.proxy_connect_response_code, 200);
    699  Assert.equal(lh.http_code, 200);
    700  Assert.ok(lh.data.match("random-request-3"));
    701  Assert.ok(lh.data.match("You Win!"));
    702  Assert.equal(
    703    await proxy_session_counter(),
    704    2,
    705    "Just one new session seen after changing the isolation key"
    706  );
    707 });
    708 
    709 // Check that error codes are still handled the same way with new isolation, just in case.
    710 add_task(async function proxy_bad_gateway_failure_isolated() {
    711  const failure1 = await get_response(
    712    make_channel(`https://502.example.com/`),
    713    CL_EXPECT_FAILURE
    714  );
    715  const failure2 = await get_response(
    716    make_channel(`https://502.example.com/`),
    717    CL_EXPECT_FAILURE
    718  );
    719 
    720  Assert.equal(failure1.status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
    721  Assert.equal(failure1.proxy_connect_response_code, 502);
    722  Assert.equal(failure1.http_code, undefined);
    723  Assert.equal(failure2.status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
    724  Assert.equal(failure2.proxy_connect_response_code, 502);
    725  Assert.equal(failure2.http_code, undefined);
    726  Assert.equal(
    727    await proxy_session_counter(),
    728    2,
    729    "No new session created by 502"
    730  );
    731 });
    732 
    733 add_task(async function proxy_success_check_number_of_session() {
    734  const foo = await get_response(
    735    make_channel(`https://foo.example.com/random-request-1`)
    736  );
    737  const alt1 = await get_response(
    738    make_channel(`https://alt1.example.com/random-request-2`)
    739  );
    740  const lh = await get_response(
    741    make_channel(`https://localhost/random-request-3`)
    742  );
    743 
    744  Assert.equal(foo.status, Cr.NS_OK);
    745  Assert.equal(foo.proxy_connect_response_code, 200);
    746  Assert.equal(foo.http_code, 200);
    747  Assert.ok(foo.data.match("random-request-1"));
    748  Assert.ok(foo.data.match("You Win!"));
    749  Assert.equal(alt1.status, Cr.NS_OK);
    750  Assert.equal(alt1.proxy_connect_response_code, 200);
    751  Assert.equal(alt1.http_code, 200);
    752  Assert.ok(alt1.data.match("random-request-2"));
    753  Assert.ok(alt1.data.match("You Win!"));
    754  Assert.equal(lh.status, Cr.NS_OK);
    755  Assert.equal(lh.proxy_connect_response_code, 200);
    756  Assert.equal(lh.http_code, 200);
    757  Assert.ok(lh.data.match("random-request-3"));
    758  Assert.ok(lh.data.match("You Win!"));
    759  Assert.equal(
    760    await proxy_session_counter(),
    761    2,
    762    "The number of sessions has not changed"
    763  );
    764 });
    765 
    766 // The hard errors are closing the session.
    767 add_task(async function origin_server_stream_hard_failure() {
    768  var current_num_sessions_to_origin_server =
    769    await proxy_session_to_origin_server_counter();
    770  const { status, http_code, proxy_connect_response_code } = await get_response(
    771    make_channel(`https://foo.example.com/illegalhpackhard`),
    772    CL_EXPECT_FAILURE
    773  );
    774 
    775  Assert.equal(status, 0x804b0053);
    776  Assert.equal(proxy_connect_response_code, 200);
    777  Assert.equal(http_code, undefined);
    778  Assert.equal(
    779    await proxy_session_counter(),
    780    2,
    781    "No new session to the proxy."
    782  );
    783  Assert.equal(
    784    await proxy_session_to_origin_server_counter(),
    785    current_num_sessions_to_origin_server,
    786    "No new session to the origin server yet."
    787  );
    788 
    789  // Check the a new session ill be opened.
    790  const foo = await get_response(
    791    make_channel(`https://foo.example.com/random-request-1`)
    792  );
    793 
    794  Assert.equal(foo.status, Cr.NS_OK);
    795  Assert.equal(foo.proxy_connect_response_code, 200);
    796  Assert.equal(foo.http_code, 200);
    797  Assert.ok(foo.data.match("random-request-1"));
    798  Assert.ok(foo.data.match("You Win!"));
    799 
    800  Assert.equal(
    801    await proxy_session_counter(),
    802    2,
    803    "No new session to the proxy is created after a hard stream failure on the session to the origin server."
    804  );
    805  Assert.equal(
    806    await proxy_session_to_origin_server_counter(),
    807    current_num_sessions_to_origin_server + 1,
    808    "A new session to the origin server after a hard stream error"
    809  );
    810 });
    811 
    812 // The hard errors are closing the session.
    813 add_task(
    814  async function origin_server_stream_hard_failure_multiple_streams_affected() {
    815    var current_num_sessions_to_origin_server =
    816      await proxy_session_to_origin_server_counter();
    817    let should_fail = get_response(
    818      make_channel(`https://foo.example.com/750msNoData`),
    819      CL_EXPECT_FAILURE
    820    );
    821    const failed1 = await get_response(
    822      make_channel(`https://foo.example.com/illegalhpackhard`),
    823      CL_EXPECT_FAILURE,
    824      10
    825    );
    826 
    827    const failed2 = await should_fail;
    828 
    829    Assert.equal(failed1.status, 0x804b0053);
    830    Assert.equal(failed1.proxy_connect_response_code, 200);
    831    Assert.equal(failed1.http_code, undefined);
    832    Assert.equal(failed2.status, 0x804b0053);
    833    Assert.equal(failed2.proxy_connect_response_code, 200);
    834    Assert.equal(failed2.http_code, undefined);
    835    Assert.equal(
    836      await proxy_session_counter(),
    837      2,
    838      "No new session to the proxy"
    839    );
    840    Assert.equal(
    841      await proxy_session_to_origin_server_counter(),
    842      current_num_sessions_to_origin_server,
    843      "No session to the origin server yet."
    844    );
    845    // Check the a new session ill be opened.
    846    const foo = await get_response(
    847      make_channel(`https://foo.example.com/random-request-1`)
    848    );
    849 
    850    Assert.equal(foo.status, Cr.NS_OK);
    851    Assert.equal(foo.proxy_connect_response_code, 200);
    852    Assert.equal(foo.http_code, 200);
    853    Assert.ok(foo.data.match("random-request-1"));
    854    Assert.ok(foo.data.match("You Win!"));
    855 
    856    Assert.equal(
    857      await proxy_session_counter(),
    858      2,
    859      "No new session to the proxy is created after a hard stream failure on the session to the origin server."
    860    );
    861 
    862    Assert.equal(
    863      await proxy_session_to_origin_server_counter(),
    864      current_num_sessions_to_origin_server + 1,
    865      "A new session to the origin server after a hard stream error"
    866    );
    867  }
    868 );