tor-browser

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

test_http3_dns_retry.js (9387B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 var { setTimeout } = ChromeUtils.importESModule(
      8  "resource://gre/modules/Timer.sys.mjs"
      9 );
     10 
     11 let h2Port;
     12 let h3Port;
     13 let trrServer;
     14 
     15 const certOverrideService = Cc[
     16  "@mozilla.org/security/certoverride;1"
     17 ].getService(Ci.nsICertOverrideService);
     18 
     19 add_setup(async function setup() {
     20  h2Port = Services.env.get("MOZHTTP2_PORT");
     21  Assert.notEqual(h2Port, null);
     22  Assert.notEqual(h2Port, "");
     23 
     24  h3Port = Services.env.get("MOZHTTP3_PORT");
     25  Assert.notEqual(h3Port, null);
     26  Assert.notEqual(h3Port, "");
     27 
     28  trr_test_setup();
     29 
     30  if (mozinfo.socketprocess_networking) {
     31    Cc["@mozilla.org/network/protocol;1?name=http"].getService(
     32      Ci.nsIHttpProtocolHandler
     33    );
     34    Services.dns; // Needed to trigger socket process.
     35    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
     36    await new Promise(resolve => setTimeout(resolve, 1000));
     37  }
     38 
     39  Services.prefs.setIntPref("network.trr.mode", 2); // TRR first
     40  Services.prefs.setBoolPref("network.http.http3.enable", true);
     41  Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
     42  Services.prefs.setBoolPref(
     43    "network.http.http3.block_loopback_ipv6_addr",
     44    true
     45  );
     46  Services.prefs.setBoolPref(
     47    "network.http.http3.retry_different_ip_family",
     48    true
     49  );
     50  Services.prefs.setBoolPref("network.dns.get-ttl", false);
     51 
     52  certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
     53    true
     54  );
     55 
     56  registerCleanupFunction(async () => {
     57    certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
     58      false
     59    );
     60    trr_clear_prefs();
     61    Services.prefs.clearUserPref(
     62      "network.http.http3.retry_different_ip_family"
     63    );
     64    Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
     65    Services.prefs.clearUserPref("network.http.http3.block_loopback_ipv6_addr");
     66    Services.prefs.clearUserPref("network.dns.get-ttl");
     67    if (trrServer) {
     68      await trrServer.stop();
     69    }
     70  });
     71 });
     72 
     73 function makeChan(url) {
     74  let chan = NetUtil.newChannel({
     75    uri: url,
     76    loadUsingSystemPrincipal: true,
     77    contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
     78  }).QueryInterface(Ci.nsIHttpChannel);
     79  chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
     80  return chan;
     81 }
     82 
     83 function channelOpenPromise(chan, flags) {
     84  // eslint-disable-next-line no-async-promise-executor
     85  return new Promise(async resolve => {
     86    function finish(req, buffer) {
     87      resolve([req, buffer]);
     88    }
     89    chan.asyncOpen(new ChannelListener(finish, null, flags));
     90  });
     91 }
     92 
     93 async function registerDoHAnswers(host, ipv4Answers, ipv6Answers, httpsRecord) {
     94  trrServer = new TRRServer();
     95  await trrServer.start();
     96 
     97  Services.prefs.setIntPref("network.trr.mode", 3);
     98  Services.prefs.setCharPref(
     99    "network.trr.uri",
    100    `https://foo.example.com:${trrServer.port()}/dns-query`
    101  );
    102 
    103  await trrServer.registerDoHAnswers(host, "HTTPS", {
    104    answers: httpsRecord,
    105  });
    106 
    107  await trrServer.registerDoHAnswers(host, "AAAA", {
    108    answers: ipv6Answers,
    109  });
    110 
    111  await trrServer.registerDoHAnswers(host, "A", {
    112    answers: ipv4Answers,
    113  });
    114 
    115  Services.dns.clearCache(true);
    116 }
    117 
    118 // Test if we retry IPv4 address for Http/3 properly.
    119 add_task(async function test_retry_with_ipv4() {
    120  let host = "test.http3_retry.com";
    121  let ipv4answers = [
    122    {
    123      name: host,
    124      ttl: 55,
    125      type: "A",
    126      flush: false,
    127      data: "127.0.0.1",
    128    },
    129  ];
    130  // The UDP socket will return connection refused error because we set
    131  // "network.http.http3.block_loopback_ipv6_addr" to true.
    132  let ipv6answers = [
    133    {
    134      name: host,
    135      ttl: 55,
    136      type: "AAAA",
    137      flush: false,
    138      data: "::1",
    139    },
    140  ];
    141  let httpsRecord = [
    142    {
    143      name: host,
    144      ttl: 55,
    145      type: "HTTPS",
    146      flush: false,
    147      data: {
    148        priority: 1,
    149        name: host,
    150        values: [
    151          { key: "alpn", value: "h3" },
    152          { key: "port", value: h3Port },
    153        ],
    154      },
    155    },
    156  ];
    157 
    158  await registerDoHAnswers(host, ipv4answers, ipv6answers, httpsRecord);
    159 
    160  let chan = makeChan(`https://${host}`);
    161  let [req] = await channelOpenPromise(chan);
    162  Assert.equal(req.protocolVersion, "h3");
    163 
    164  await trrServer.stop();
    165 });
    166 
    167 add_task(async function test_retry_with_ipv4_disabled() {
    168  let host = "test.http3_retry_ipv4_blocked.com";
    169  let ipv4answers = [
    170    {
    171      name: host,
    172      ttl: 55,
    173      type: "A",
    174      flush: false,
    175      data: "127.0.0.1",
    176    },
    177  ];
    178  // The UDP socket will return connection refused error because we set
    179  // "network.http.http3.block_loopback_ipv6_addr" to true.
    180  let ipv6answers = [
    181    {
    182      name: host,
    183      ttl: 55,
    184      type: "AAAA",
    185      flush: false,
    186      data: "::1",
    187    },
    188  ];
    189  let httpsRecord = [
    190    {
    191      name: host,
    192      ttl: 55,
    193      type: "HTTPS",
    194      flush: false,
    195      data: {
    196        priority: 1,
    197        name: host,
    198        values: [
    199          { key: "alpn", value: "h3" },
    200          { key: "port", value: h3Port },
    201        ],
    202      },
    203    },
    204  ];
    205 
    206  await registerDoHAnswers(host, ipv4answers, ipv6answers, httpsRecord);
    207 
    208  let chan = makeChan(`https://${host}`);
    209  chan.QueryInterface(Ci.nsIHttpChannelInternal);
    210  chan.setIPv4Disabled();
    211 
    212  await channelOpenPromise(chan, CL_EXPECT_FAILURE);
    213  await trrServer.stop();
    214 });
    215 
    216 // See bug 1837252. There is no way to observe the outcome of this test, because
    217 // the crash in bug 1837252 is only triggered by speculative connection.
    218 // The outcome of this test is no crash.
    219 add_task(async function test_retry_with_ipv4_failed() {
    220  let host = "test.http3_retry_failed.com";
    221  // Return a wrong answer intentionally.
    222  let ipv4answers = [
    223    {
    224      name: host,
    225      ttl: 55,
    226      type: "AAAA",
    227      flush: false,
    228      data: "127.0.0.1",
    229    },
    230  ];
    231  // The UDP socket will return connection refused error because we set
    232  // "network.http.http3.block_loopback_ipv6_addr" to true.
    233  let ipv6answers = [
    234    {
    235      name: host,
    236      ttl: 55,
    237      type: "AAAA",
    238      flush: false,
    239      data: "::1",
    240    },
    241  ];
    242  let httpsRecord = [
    243    {
    244      name: host,
    245      ttl: 55,
    246      type: "HTTPS",
    247      flush: false,
    248      data: {
    249        priority: 1,
    250        name: host,
    251        values: [
    252          { key: "alpn", value: "h3" },
    253          { key: "port", value: h3Port },
    254        ],
    255      },
    256    },
    257  ];
    258 
    259  await registerDoHAnswers(host, ipv4answers, ipv6answers, httpsRecord);
    260 
    261  // This speculative connection is used to trigger the mechanism to retry
    262  // Http/3 connection with a IPv4 address.
    263  // We want to make the connection entry's IP preference known,
    264  // so DnsAndConnectSocket::mRetryWithDifferentIPFamily will be set to true
    265  // before the second speculative connection.
    266  let uri = Services.io.newURI(`https://test.http3_retry_failed.com`);
    267  Services.io.speculativeConnect(
    268    uri,
    269    Services.scriptSecurityManager.getSystemPrincipal(),
    270    null,
    271    false
    272  );
    273 
    274  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    275  await new Promise(resolve => setTimeout(resolve, 3000));
    276 
    277  // When this speculative connection is created, the connection entry is
    278  // already set to prefer IPv4. Since we provided an invalid A response,
    279  // DnsAndConnectSocket::OnLookupComplete is called with an error.
    280  // Since DnsAndConnectSocket::mRetryWithDifferentIPFamily is true, we do
    281  // retry DNS lookup. During retry, we should not create UDP connection.
    282  Services.io.speculativeConnect(
    283    uri,
    284    Services.scriptSecurityManager.getSystemPrincipal(),
    285    null,
    286    false
    287  );
    288 
    289  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    290  await new Promise(resolve => setTimeout(resolve, 3000));
    291  await trrServer.stop();
    292 });
    293 
    294 add_task(async function test_retry_with_0rtt() {
    295  let host = "test.http3_retry_0rtt.com";
    296  let ipv4answers = [
    297    {
    298      name: host,
    299      ttl: 55,
    300      type: "A",
    301      flush: false,
    302      data: "127.0.0.1",
    303    },
    304  ];
    305  // The UDP socket will return connection refused error because we set
    306  // "network.http.http3.block_loopback_ipv6_addr" to true.
    307  let ipv6answers = [
    308    {
    309      name: host,
    310      ttl: 55,
    311      type: "AAAA",
    312      flush: false,
    313      data: "::1",
    314    },
    315  ];
    316  let httpsRecord = [
    317    {
    318      name: host,
    319      ttl: 55,
    320      type: "HTTPS",
    321      flush: false,
    322      data: {
    323        priority: 1,
    324        name: host,
    325        values: [
    326          { key: "alpn", value: "h3" },
    327          { key: "port", value: h3Port },
    328        ],
    329      },
    330    },
    331  ];
    332 
    333  await registerDoHAnswers(host, ipv4answers, ipv6answers, httpsRecord);
    334 
    335  let chan = makeChan(`https://${host}`);
    336  chan.QueryInterface(Ci.nsIHttpChannelInternal);
    337  chan.setIPv6Disabled();
    338 
    339  let [req] = await channelOpenPromise(chan);
    340  Assert.equal(req.protocolVersion, "h3");
    341 
    342  // Make sure the h3 connection created by the previous test is cleared.
    343  Services.obs.notifyObservers(null, "net:cancel-all-connections");
    344  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    345  await new Promise(resolve => setTimeout(resolve, 1000));
    346 
    347  chan = makeChan(`https://${host}`);
    348  chan.QueryInterface(Ci.nsIHttpChannelInternal);
    349 
    350  [req] = await channelOpenPromise(chan);
    351  Assert.equal(req.protocolVersion, "h3");
    352 
    353  await trrServer.stop();
    354 });