tor-browser

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

head_trr.js (36615B)


      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 /* import-globals-from head_cache.js */
      8 /* import-globals-from head_cookies.js */
      9 /* import-globals-from head_channels.js */
     10 /* globals require, __dirname, global, Buffer, process, setTimeout */
     11 
     12 var { NodeHTTP2Server: TRRNodeHttp2Server, NodeServer: TRRNodeServer } =
     13  ChromeUtils.importESModule("resource://testing-common/NodeServer.sys.mjs");
     14 
     15 const { AppConstants: TRRAppConstants } = ChromeUtils.importESModule(
     16  "resource://gre/modules/AppConstants.sys.mjs"
     17 );
     18 
     19 /// Sets the TRR related prefs and adds the certificate we use for the HTTP2
     20 /// server.
     21 function trr_test_setup() {
     22  dump("start!\n");
     23 
     24  let h2Port = Services.env.get("MOZHTTP2_PORT");
     25  Assert.notEqual(h2Port, null);
     26  Assert.notEqual(h2Port, "");
     27 
     28  // Set to allow the cert presented by our H2 server
     29  do_get_profile();
     30 
     31  Services.prefs.setBoolPref("network.http.http2.enabled", true);
     32  // the TRR server is on 127.0.0.1
     33  if (TRRAppConstants.platform == "android") {
     34    Services.prefs.setCharPref("network.trr.bootstrapAddr", "10.0.2.2");
     35  } else {
     36    Services.prefs.setCharPref("network.trr.bootstrapAddr", "127.0.0.1");
     37  }
     38 
     39  // make all native resolve calls "secretly" resolve localhost instead
     40  Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
     41 
     42  Services.prefs.setBoolPref("network.trr.wait-for-portal", false);
     43  // don't confirm that TRR is working, just go!
     44  Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
     45  // some tests rely on the cache not being cleared on pref change.
     46  // we specifically test that this works
     47  Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
     48 
     49  // Turn off strict fallback mode and TRR retry for most tests,
     50  // it is tested specifically.
     51  Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
     52  Services.prefs.setBoolPref("network.trr.retry_on_recoverable_errors", false);
     53 
     54  // Turn off temp blocklist feature in tests. When enabled we may issue a
     55  // lookup to resolve a parent name when blocklisting, which may bleed into
     56  // and interfere with subsequent tasks.
     57  Services.prefs.setBoolPref("network.trr.temp_blocklist", false);
     58 
     59  // We intentionally don't set the TRR mode. Each test should set it
     60  // after setup in the first test.
     61 
     62  return h2Port;
     63 }
     64 
     65 /// Clears the prefs that we're likely to set while testing TRR code
     66 function trr_clear_prefs() {
     67  Services.prefs.clearUserPref("network.trr.mode");
     68  Services.prefs.clearUserPref("network.trr.uri");
     69  Services.prefs.clearUserPref("network.trr.credentials");
     70  Services.prefs.clearUserPref("network.trr.wait-for-portal");
     71  Services.prefs.clearUserPref("network.trr.allow-rfc1918");
     72  Services.prefs.clearUserPref("network.trr.useGET");
     73  Services.prefs.clearUserPref("network.trr.confirmationNS");
     74  Services.prefs.clearUserPref("network.trr.bootstrapAddr");
     75  Services.prefs.clearUserPref("network.trr.temp_blocklist_duration_sec");
     76  Services.prefs.clearUserPref("network.trr.request_timeout_ms");
     77  Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
     78  Services.prefs.clearUserPref("network.trr.disable-ECS");
     79  Services.prefs.clearUserPref("network.trr.early-AAAA");
     80  Services.prefs.clearUserPref("network.trr.excluded-domains");
     81  Services.prefs.clearUserPref("network.trr.builtin-excluded-domains");
     82  Services.prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
     83  Services.prefs.clearUserPref("captivedetect.canonicalURL");
     84 
     85  Services.prefs.clearUserPref("network.http.http2.enabled");
     86  Services.prefs.clearUserPref("network.dns.localDomains");
     87  Services.prefs.clearUserPref("network.dns.native-is-localhost");
     88  Services.prefs.clearUserPref(
     89    "network.trr.send_empty_accept-encoding_headers"
     90  );
     91  Services.prefs.clearUserPref("network.trr.strict_native_fallback");
     92  Services.prefs.clearUserPref("network.trr.temp_blocklist");
     93 }
     94 
     95 /// This class sends a DNS query and can be awaited as a promise to get the
     96 /// response.
     97 class TRRDNSListener {
     98  constructor(...args) {
     99    if (args.length < 2) {
    100      Assert.ok(false, "TRRDNSListener requires at least two arguments");
    101    }
    102    this.name = args[0];
    103    if (typeof args[1] == "object") {
    104      this.options = args[1];
    105    } else {
    106      this.options = {
    107        expectedAnswer: args[1],
    108        expectedSuccess: args[2] ?? true,
    109        delay: args[3],
    110        trrServer: args[4] ?? "",
    111        expectEarlyFail: args[5] ?? "",
    112        flags: args[6] ?? 0,
    113        type: args[7] ?? Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
    114        port: args[8] ?? -1,
    115      };
    116    }
    117    this.expectedAnswer = this.options.expectedAnswer ?? undefined;
    118    this.expectedSuccess = this.options.expectedSuccess ?? true;
    119    this.delay = this.options.delay;
    120    this.promise = new Promise(resolve => {
    121      this.resolve = resolve;
    122    });
    123    this.type = this.options.type ?? Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT;
    124    let trrServer = this.options.trrServer || "";
    125    let port = this.options.port || -1;
    126 
    127    // This may be called in a child process that doesn't have Services available.
    128    // eslint-disable-next-line mozilla/use-services
    129    const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
    130      Ci.nsIThreadManager
    131    );
    132    const currentThread = threadManager.currentThread;
    133 
    134    this.additionalInfo =
    135      trrServer == "" && port == -1
    136        ? null
    137        : Services.dns.newAdditionalInfo(trrServer, port);
    138    try {
    139      this.request = Services.dns.asyncResolve(
    140        this.name,
    141        this.type,
    142        this.options.flags || 0,
    143        this.additionalInfo,
    144        this,
    145        currentThread,
    146        this.options.originAttributes || {} // defaultOriginAttributes
    147      );
    148      Assert.ok(!this.options.expectEarlyFail, "asyncResolve ok");
    149    } catch (e) {
    150      Assert.ok(this.options.expectEarlyFail, "asyncResolve fail");
    151      this.resolve({ error: e });
    152    }
    153  }
    154 
    155  onLookupComplete(inRequest, inRecord, inStatus) {
    156    Assert.equal(
    157      inRequest,
    158      this.request,
    159      "Checking that this is the correct callback"
    160    );
    161 
    162    // If we don't expect success here, just resolve and the caller will
    163    // decide what to do with the results.
    164    if (!this.expectedSuccess) {
    165      this.resolve({ inRequest, inRecord, inStatus });
    166      return;
    167    }
    168 
    169    Assert.equal(inStatus, Cr.NS_OK, "Checking status");
    170 
    171    if (this.type != Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT) {
    172      this.resolve({ inRequest, inRecord, inStatus });
    173      return;
    174    }
    175 
    176    inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
    177    let answer = inRecord.getNextAddrAsString();
    178    Assert.equal(
    179      answer,
    180      this.expectedAnswer,
    181      `Checking result for ${this.name}`
    182    );
    183    inRecord.rewind(); // In case the caller also checks the addresses
    184 
    185    if (this.delay !== undefined) {
    186      Assert.greaterOrEqual(
    187        inRecord.trrFetchDurationNetworkOnly,
    188        this.delay,
    189        `the response should take at least ${this.delay}`
    190      );
    191 
    192      Assert.greaterOrEqual(
    193        inRecord.trrFetchDuration,
    194        this.delay,
    195        `the response should take at least ${this.delay}`
    196      );
    197 
    198      if (this.delay == 0) {
    199        // The response timing should be really 0
    200        Assert.equal(
    201          inRecord.trrFetchDurationNetworkOnly,
    202          0,
    203          `the response time should be 0`
    204        );
    205 
    206        Assert.equal(
    207          inRecord.trrFetchDuration,
    208          this.delay,
    209          `the response time should be 0`
    210        );
    211      }
    212    }
    213 
    214    this.resolve({ inRequest, inRecord, inStatus });
    215  }
    216 
    217  QueryInterface(aIID) {
    218    if (aIID.equals(Ci.nsIDNSListener) || aIID.equals(Ci.nsISupports)) {
    219      return this;
    220    }
    221    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
    222  }
    223 
    224  // Implement then so we can await this as a promise.
    225  then() {
    226    return this.promise.then.apply(this.promise, arguments);
    227  }
    228 
    229  cancel(aStatus = Cr.NS_ERROR_ABORT) {
    230    Services.dns.cancelAsyncResolve(
    231      this.name,
    232      this.type,
    233      this.options.flags || 0,
    234      this.resolverInfo,
    235      this,
    236      aStatus,
    237      {}
    238    );
    239  }
    240 }
    241 
    242 // This is for reteriiving the raw bytes from a DNS answer.
    243 function answerHandler(req, resp) {
    244  let searchParams = new URL(req.url, "http://example.com").searchParams;
    245  if (!searchParams.get("host")) {
    246    resp.writeHead(400);
    247    resp.end("Missing search parameter");
    248    return;
    249  }
    250 
    251  function processRequest(req1, resp1) {
    252    let domain = searchParams.get("host");
    253    let type = searchParams.get("type");
    254    let response = global.dns_query_answers[`${domain}/${type}`] || {};
    255    let buf = global.dnsPacket.encode({
    256      type: "response",
    257      id: 0,
    258      flags: 0,
    259      questions: [],
    260      answers: response.answers || [],
    261      additionals: response.additionals || [],
    262    });
    263    let writeResponse = (resp2, buf2) => {
    264      try {
    265        let data = buf2.toString("hex");
    266        resp2.setHeader("Content-Length", data.length);
    267        resp2.writeHead(200, { "Content-Type": "plain/text" });
    268        resp2.write(data);
    269        resp2.end("");
    270      } catch (e) {}
    271    };
    272 
    273    writeResponse(resp1, buf, response);
    274  }
    275 
    276  processRequest(req, resp);
    277 }
    278 
    279 /// This is the default handler for /dns-query
    280 /// It implements basic functionality for parsing the DoH packet, then
    281 /// queries global.dns_query_answers for available answers for the DNS query.
    282 function trrQueryHandler(req, resp) {
    283  let requestBody = Buffer.from("");
    284  let method = req.headers[global.http2.constants.HTTP2_HEADER_METHOD];
    285  let contentLength = req.headers["content-length"];
    286 
    287  if (method == "POST") {
    288    req.on("data", chunk => {
    289      requestBody = Buffer.concat([requestBody, chunk]);
    290      if (requestBody.length == contentLength) {
    291        processRequest(req, resp, requestBody);
    292      }
    293    });
    294  } else if (method == "GET") {
    295    let searchParams = new URL(req.url, "http://example.com").searchParams;
    296    if (!searchParams.get("dns")) {
    297      resp.writeHead(400);
    298      resp.end("Missing dns parameter");
    299      return;
    300    }
    301 
    302    requestBody = Buffer.from(searchParams.get("dns"), "base64");
    303    processRequest(req, resp, requestBody);
    304  } else {
    305    // unexpected method.
    306    resp.writeHead(405);
    307    resp.end("Unexpected method");
    308  }
    309 
    310  function processRequest(req1, resp1, payload) {
    311    let dnsQuery = global.dnsPacket.decode(payload);
    312    let domain = dnsQuery.questions[0].name;
    313    let type = dnsQuery.questions[0].type;
    314    let response = global.dns_query_answers[`${domain}/${type}`] || {};
    315    let delay = response.delay || 0;
    316    let searchParams = new URL(req1.url, "http://example.com").searchParams;
    317    if (searchParams.get("conncycle")) {
    318      if (domain.startsWith("newconn")) {
    319        // If we haven't seen a req for this newconn name before,
    320        // or if we've seen one for the same name on the same port,
    321        // synthesize a timeout.
    322        if (
    323          !global.gDoHNewConnLog[domain] ||
    324          global.gDoHNewConnLog[domain] == req1.socket.remotePort
    325        ) {
    326          delay = 1000;
    327        }
    328        if (!global.gDoHNewConnLog[domain]) {
    329          global.gDoHNewConnLog[domain] = req1.socket.remotePort;
    330        }
    331      }
    332      global.gDoHPortsLog.push([domain, req1.socket.remotePort]);
    333    }
    334 
    335    if (!global.dns_query_counts[domain]) {
    336      global.dns_query_counts[domain] = {};
    337    }
    338    global.dns_query_counts[domain][type] =
    339      global.dns_query_counts[domain][type] + 1 || 1;
    340 
    341    let flags = global.dnsPacket.RECURSION_DESIRED;
    342    if (!response.answers && !response.flags) {
    343      flags |= 2; // SERVFAIL
    344    }
    345    flags |= response.flags || 0;
    346    let buf = global.dnsPacket.encode({
    347      type: "response",
    348      id: dnsQuery.id,
    349      flags,
    350      questions: dnsQuery.questions,
    351      answers: response.answers || [],
    352      additionals: response.additionals || [],
    353    });
    354 
    355    let writeResponse = (resp2, buf2, context) => {
    356      try {
    357        if (context.error) {
    358          // If the error is a valid HTTP response number just write it out.
    359          if (context.error < 600) {
    360            resp2.writeHead(context.error);
    361            resp2.end("Intentional error");
    362            return;
    363          }
    364 
    365          // Bigger error means force close the session
    366          req1.stream.session.close();
    367          return;
    368        }
    369        resp2.setHeader("Content-Length", buf2.length);
    370        resp2.writeHead(200, { "Content-Type": "application/dns-message" });
    371        resp2.write(buf2);
    372        resp2.end("");
    373      } catch (e) {}
    374    };
    375 
    376    if (delay) {
    377      // This function is handled within the httpserver where setTimeout is
    378      // available.
    379      // eslint-disable-next-line no-undef
    380      setTimeout(
    381        arg => {
    382          writeResponse(arg[0], arg[1], arg[2]);
    383        },
    384        delay,
    385        [resp1, buf, response]
    386      );
    387      return;
    388    }
    389 
    390    writeResponse(resp1, buf, response);
    391  }
    392 }
    393 
    394 function dohHandler(req, res) {
    395  let u = global.url.parse(req.url, true);
    396 
    397  function handleAuth() {
    398    // There's a Set-Cookie: header in the response for "/dns" , which this
    399    // request subsequently would include if the http channel wasn't
    400    // anonymous. Thus, if there's a cookie in this request, we know Firefox
    401    // mishaved. If there's not, we're fine.
    402    if (req.headers.cookie) {
    403      res.writeHead(403);
    404      res.end("cookie for me, not for you");
    405      return false;
    406    }
    407    if (req.headers.authorization != "user:password") {
    408      res.writeHead(401);
    409      res.end("bad boy!");
    410      return false;
    411    }
    412 
    413    return true;
    414  }
    415 
    416  function createDNSAnswer(response, packet, responseIP, requestPayload) {
    417    // This shuts down the connection so we can test if the client reconnects
    418    if (packet.questions.length && packet.questions[0].name == "closeme.com") {
    419      // response.stream.connection.close("INTERNAL_ERROR", response.stream.id);
    420      req.stream.session.close();
    421      return null;
    422    }
    423 
    424    let answers = [];
    425 
    426    if (u.query.httpssvc) {
    427      responseIP = "none";
    428      answers.push({
    429        name: packet.questions[0].name,
    430        type: packet.questions[0].type,
    431        ttl: 55,
    432        class: "IN",
    433        flush: false,
    434        data: {
    435          priority: 1,
    436          name: "h3pool",
    437          values: [
    438            { key: "alpn", value: ["h2", "h3"] },
    439            { key: "no-default-alpn" },
    440            { key: "port", value: 8888 },
    441            { key: "ipv4hint", value: "1.2.3.4" },
    442            { key: "echconfig", value: "123..." },
    443            { key: "ipv6hint", value: "::1" },
    444            { key: 30, value: "somelargestring" },
    445            { key: "odoh", value: "456..." },
    446          ],
    447        },
    448      });
    449      answers.push({
    450        name: packet.questions[0].name,
    451        type: packet.questions[0].type,
    452        ttl: 55,
    453        class: "IN",
    454        flush: false,
    455        data: {
    456          priority: 2,
    457          name: ".",
    458          values: [
    459            { key: "alpn", value: "h2" },
    460            { key: "ipv4hint", value: ["1.2.3.4", "5.6.7.8"] },
    461            { key: "echconfig", value: "abc..." },
    462            { key: "ipv6hint", value: ["::1", "fe80::794f:6d2c:3d5e:7836"] },
    463            { key: "odoh", value: "def..." },
    464          ],
    465        },
    466      });
    467      answers.push({
    468        name: packet.questions[0].name,
    469        type: packet.questions[0].type,
    470        ttl: 55,
    471        class: "IN",
    472        flush: false,
    473        data: {
    474          priority: 3,
    475          name: "hello",
    476          values: [],
    477        },
    478      });
    479    } else if (u.query.httpssvc_as_altsvc) {
    480      responseIP = "none";
    481      if (packet.questions[0].type == "HTTPS") {
    482        let priority = 1;
    483        if (packet.questions[0].name === "foo.notexisted.com") {
    484          priority = 0;
    485        }
    486        answers.push({
    487          name: packet.questions[0].name,
    488          type: packet.questions[0].type,
    489          ttl: 55,
    490          class: "IN",
    491          flush: false,
    492          data: {
    493            priority,
    494            name: packet.questions[0].name,
    495            values: [
    496              { key: "alpn", value: "h2" },
    497              { key: "port", value: global.serverPort },
    498              { key: 30, value: "somelargestring" },
    499            ],
    500          },
    501        });
    502      } else {
    503        answers.push({
    504          name: packet.questions[0].name,
    505          type: "A",
    506          ttl: 55,
    507          flush: false,
    508          data: "127.0.0.1",
    509        });
    510      }
    511    } else if (u.query.httpssvc_use_iphint) {
    512      responseIP = "none";
    513      answers.push({
    514        name: packet.questions[0].name,
    515        type: "HTTPS",
    516        ttl: 55,
    517        class: "IN",
    518        flush: false,
    519        data: {
    520          priority: 1,
    521          name: ".",
    522          values: [
    523            { key: "alpn", value: "h2" },
    524            { key: "port", value: global.serverPort },
    525            { key: "ipv4hint", value: "127.0.0.1" },
    526          ],
    527        },
    528      });
    529    }
    530 
    531    if (packet.questions.length && packet.questions[0].name.endsWith(".pd")) {
    532      // Bug 1543811: test edns padding extension. Return whether padding was
    533      // included via the first half of the ip address (1.1 vs 2.2) and the
    534      // size of the request in the second half of the ip address allowing to
    535      // verify that the correct amount of padding was added.
    536      if (
    537        !!packet.additionals.length &&
    538        packet.additionals[0].type == "OPT" &&
    539        packet.additionals[0].options.some(o => o.type === "PADDING")
    540      ) {
    541        // add padding to the response, because the client must be able ignore it
    542        answers.push({
    543          name: ".",
    544          type: "PADDING",
    545          data: Buffer.from(
    546            // PADDING_PADDING_PADDING
    547            "50414444494e475f50414444494e475f50414444494e47",
    548            "hex"
    549          ),
    550        });
    551        responseIP =
    552          "1.1." +
    553          ((requestPayload.length >> 8) & 0xff) +
    554          "." +
    555          (requestPayload.length & 0xff);
    556      } else {
    557        responseIP =
    558          "2.2." +
    559          ((requestPayload.length >> 8) & 0xff) +
    560          "." +
    561          (requestPayload.length & 0xff);
    562      }
    563    }
    564 
    565    if (u.query.corruptedAnswer) {
    566      // DNS response header is 12 bytes, we check for this minimum length
    567      // at the start of decoding so this is the simplest way to force
    568      // a decode error.
    569      return "\xFF\xFF\xFF\xFF";
    570    }
    571 
    572    // Because we send two TRR requests (A and AAAA), skip the first two
    573    // requests when testing retry.
    574    if (u.query.retryOnDecodeFailure && global.gDoHRequestCount < 2) {
    575      global.gDoHRequestCount++;
    576      return "\xFF\xFF\xFF\xFF";
    577    }
    578 
    579    function responseData() {
    580      if (
    581        !!packet.questions.length &&
    582        packet.questions[0].name == "confirm.example.com" &&
    583        packet.questions[0].type == "NS"
    584      ) {
    585        return "ns.example.com";
    586      }
    587 
    588      return responseIP;
    589    }
    590 
    591    if (
    592      responseIP != "none" &&
    593      responseType(packet, responseIP) == packet.questions[0].type
    594    ) {
    595      answers.push({
    596        name: u.query.hostname ? u.query.hostname : packet.questions[0].name,
    597        ttl: 55,
    598        type: responseType(packet, responseIP),
    599        flush: false,
    600        data: responseData(),
    601      });
    602    }
    603 
    604    // for use with test_dns_by_type_resolve.js
    605    if (packet.questions[0].type == "TXT") {
    606      answers.push({
    607        name: packet.questions[0].name,
    608        type: packet.questions[0].type,
    609        ttl: 55,
    610        class: "IN",
    611        flush: false,
    612        data: Buffer.from(
    613          "62586B67646D39705932556761584D6762586B676347467A63336476636D513D",
    614          "hex"
    615        ),
    616      });
    617    }
    618 
    619    if (u.query.cnameloop) {
    620      answers.push({
    621        name: "cname.example.com",
    622        type: "CNAME",
    623        ttl: 55,
    624        class: "IN",
    625        flush: false,
    626        data: "pointing-elsewhere.example.com",
    627      });
    628    }
    629 
    630    if (req.headers["accept-language"] || req.headers["user-agent"]) {
    631      // If we get this header, don't send back any response. This should
    632      // cause the tests to fail. This is easier then actually sending back
    633      // the header value into test_trr.js
    634      answers = [];
    635    }
    636 
    637    let buf = global.dnsPacket.encode({
    638      type: "response",
    639      id: packet.id,
    640      flags: global.dnsPacket.RECURSION_DESIRED,
    641      questions: packet.questions,
    642      answers,
    643    });
    644 
    645    return buf;
    646  }
    647 
    648  function responseType(packet, responseIP) {
    649    if (
    650      !!packet.questions.length &&
    651      packet.questions[0].name == "confirm.example.com" &&
    652      packet.questions[0].type == "NS"
    653    ) {
    654      return "NS";
    655    }
    656 
    657    return global.ip.isV4Format(responseIP) ? "A" : "AAAA";
    658  }
    659 
    660  function getDelayFromPacket(packet, type) {
    661    let delay = 0;
    662    if (packet.questions[0].type == "A") {
    663      delay = parseInt(u.query.delayIPv4);
    664    } else if (packet.questions[0].type == "AAAA") {
    665      delay = parseInt(u.query.delayIPv6);
    666    }
    667 
    668    if (u.query.slowConfirm && type == "NS") {
    669      delay += 1000;
    670    }
    671 
    672    return delay;
    673  }
    674 
    675  function writeDNSResponse(response, buf, delay, contentType) {
    676    function writeResponse(resp, buffer) {
    677      resp.setHeader("Set-Cookie", "trackyou=yes; path=/; max-age=100000;");
    678      resp.setHeader("Content-Type", contentType);
    679      if (req.headers["accept-encoding"].includes("gzip")) {
    680        global.zlib.gzip(buffer, function (err, result) {
    681          resp.setHeader("Content-Encoding", "gzip");
    682          resp.setHeader("Content-Length", result.length);
    683          try {
    684            resp.writeHead(200);
    685            resp.end(result);
    686          } catch (e) {
    687            // connection was closed by the time we started writing.
    688          }
    689        });
    690      } else {
    691        const output = Buffer.from(buffer, "utf-8");
    692        resp.setHeader("Content-Length", output.length);
    693        try {
    694          resp.writeHead(200);
    695          resp.write(output);
    696          resp.end("");
    697        } catch (e) {
    698          // connection was closed by the time we started writing.
    699        }
    700      }
    701    }
    702 
    703    if (delay) {
    704      setTimeout(
    705        arg => {
    706          writeResponse(arg[0], arg[1]);
    707        },
    708        delay + 1,
    709        [response, buf]
    710      );
    711      return;
    712    }
    713 
    714    writeResponse(response, buf);
    715  }
    716 
    717  let responseIP = u.query.responseIP;
    718  if (!responseIP) {
    719    responseIP = "5.5.5.5";
    720  }
    721 
    722  let redirect = u.query.redirect;
    723  if (redirect) {
    724    responseIP = redirect;
    725    if (u.query.dns) {
    726      res.setHeader(
    727        "Location",
    728        "https://localhost:" +
    729          global.serverPort +
    730          "/doh?responseIP=" +
    731          responseIP +
    732          "&dns=" +
    733          u.query.dns
    734      );
    735    } else {
    736      res.setHeader(
    737        "Location",
    738        "https://localhost:" +
    739          global.serverPort +
    740          "/doh?responseIP=" +
    741          responseIP
    742      );
    743    }
    744    res.writeHead(307);
    745    res.end("");
    746    return;
    747  }
    748 
    749  if (u.query.auth) {
    750    if (!handleAuth()) {
    751      return;
    752    }
    753  }
    754 
    755  if (u.query.noResponse) {
    756    return;
    757  }
    758 
    759  if (u.query.push) {
    760    // push.example.org has AAAA entry 2018::2018
    761    let pcontent = global.dnsPacket.encode({
    762      id: 0,
    763      type: "response",
    764      flags: global.dnsPacket.RECURSION_DESIRED,
    765      questions: [{ name: "push.example.org", type: "AAAA", class: "IN" }],
    766      answers: [
    767        {
    768          name: "push.example.org",
    769          type: "AAAA",
    770          ttl: 55,
    771          class: "IN",
    772          flush: false,
    773          data: "2018::2018",
    774        },
    775      ],
    776    });
    777    let push = res.push({
    778      hostname: "foo.example.com:" + global.serverPort,
    779      port: global.serverPort,
    780      path: "/dns-pushed-response?dns=AAAAAAABAAAAAAAABHB1c2gHZXhhbXBsZQNvcmcAABwAAQ",
    781      method: "GET",
    782      headers: {
    783        accept: "application/dns-message",
    784      },
    785    });
    786    push.writeHead(200, {
    787      "content-type": "application/dns-message",
    788      pushed: "yes",
    789      "content-length": pcontent.length,
    790      "X-Connection-Http2": "yes",
    791    });
    792    push.end(pcontent);
    793  }
    794 
    795  let payload = Buffer.from("");
    796 
    797  function emitResponse(response, requestPayload, decodedPacket, delay) {
    798    let packet = decodedPacket || global.dnsPacket.decode(requestPayload);
    799    let answer = createDNSAnswer(response, packet, responseIP, requestPayload);
    800    if (!answer) {
    801      return;
    802    }
    803    writeDNSResponse(
    804      response,
    805      answer,
    806      delay || getDelayFromPacket(packet, responseType(packet, responseIP)),
    807      "application/dns-message"
    808    );
    809  }
    810 
    811  if (u.query.dns) {
    812    payload = Buffer.from(u.query.dns, "base64");
    813    emitResponse(res, payload);
    814    return;
    815  }
    816 
    817  req.on("data", function receiveData(chunk) {
    818    payload = Buffer.concat([payload, chunk]);
    819  });
    820  req.on("end", function finishedData() {
    821    // parload is empty when we send redirect response.
    822    if (payload.length) {
    823      let packet = global.dnsPacket.decode(payload);
    824      emitResponse(res, payload, packet);
    825    }
    826  });
    827 }
    828 
    829 function cnameHandler(req, res) {
    830  // asking for cname.example.com
    831 
    832  function createCNameContent(payload) {
    833    let packet = global.dnsPacket.decode(payload);
    834    if (
    835      packet.questions[0].name == "cname.example.com" &&
    836      packet.questions[0].type == "A"
    837    ) {
    838      return global.dnsPacket.encode({
    839        id: 0,
    840        type: "response",
    841        flags: global.dnsPacket.RECURSION_DESIRED,
    842        questions: [{ name: packet.questions[0].name, type: "A", class: "IN" }],
    843        answers: [
    844          {
    845            name: packet.questions[0].name,
    846            ttl: 55,
    847            type: "CNAME",
    848            flush: false,
    849            data: "pointing-elsewhere.example.com",
    850          },
    851        ],
    852      });
    853    }
    854    if (
    855      packet.questions[0].name == "pointing-elsewhere.example.com" &&
    856      packet.questions[0].type == "A"
    857    ) {
    858      return global.dnsPacket.encode({
    859        id: 0,
    860        type: "response",
    861        flags: global.dnsPacket.RECURSION_DESIRED,
    862        questions: [{ name: packet.questions[0].name, type: "A", class: "IN" }],
    863        answers: [
    864          {
    865            name: packet.questions[0].name,
    866            ttl: 55,
    867            type: "A",
    868            flush: false,
    869            data: "99.88.77.66",
    870          },
    871        ],
    872      });
    873    }
    874 
    875    return global.dnsPacket.encode({
    876      id: 0,
    877      type: "response",
    878      flags:
    879        global.dnsPacket.RECURSION_DESIRED |
    880        global.dnsPacket.rcodes.toRcode("NXDOMAIN"),
    881      questions: [
    882        {
    883          name: packet.questions[0].name,
    884          type: packet.questions[0].type,
    885          class: "IN",
    886        },
    887      ],
    888      answers: [],
    889    });
    890  }
    891 
    892  function emitResponse(response, payload) {
    893    let pcontent = createCNameContent(payload);
    894    response.setHeader("Content-Type", "application/dns-message");
    895    response.setHeader("Content-Length", pcontent.length);
    896    response.writeHead(200);
    897    response.write(pcontent);
    898    response.end("");
    899  }
    900 
    901  let payload = Buffer.from("");
    902  req.on("data", function receiveData(chunk) {
    903    payload = Buffer.concat([payload, chunk]);
    904  });
    905  req.on("end", function finishedData() {
    906    emitResponse(res, payload);
    907  });
    908 }
    909 
    910 function cnameAHandler(req, res) {
    911  function createCNameARecord() {
    912    // test23 asks for cname-a.example.com
    913    // this responds with a CNAME to here.example.com *and* an A record
    914    // for here.example.com
    915    let rContent;
    916 
    917    rContent = Buffer.from(
    918      "0000" +
    919        "0100" +
    920        "0001" + // QDCOUNT
    921        "0002" + // ANCOUNT
    922        "00000000" + // NSCOUNT + ARCOUNT
    923        "07636E616D652d61" + // cname-a
    924        "076578616D706C6503636F6D00" + // .example.com
    925        "00010001" + // question type (A) + question class (IN)
    926        // answer record 1
    927        "C00C" + // name pointer to cname-a.example.com
    928        "0005" + // type (CNAME)
    929        "0001" + // class
    930        "00000037" + // TTL
    931        "0012" + // RDLENGTH
    932        "0468657265" + // here
    933        "076578616D706C6503636F6D00" + // .example.com
    934        // answer record 2, the A entry for the CNAME above
    935        "0468657265" + // here
    936        "076578616D706C6503636F6D00" + // .example.com
    937        "0001" + // type (A)
    938        "0001" + // class
    939        "00000037" + // TTL
    940        "0004" + // RDLENGTH
    941        "09080706", // IPv4 address
    942      "hex"
    943    );
    944 
    945    return rContent;
    946  }
    947 
    948  let rContent = createCNameARecord();
    949  res.setHeader("Content-Type", "application/dns-message");
    950  res.setHeader("Content-Length", rContent.length);
    951  res.writeHead(200);
    952  res.write(rContent);
    953  res.end("");
    954 }
    955 
    956 function getRequestCount(domain, type) {
    957  if (!global.dns_query_counts[domain]) {
    958    return 0;
    959  }
    960  return global.dns_query_counts[domain][type] || 0;
    961 }
    962 
    963 // A convenient wrapper around NodeServer
    964 class TRRServer extends TRRNodeHttp2Server {
    965  /// Starts the server
    966  /// @port - default 0
    967  ///    when provided, will attempt to listen on that port.
    968  async start(port = 0) {
    969    await super.start(port);
    970    await this.execute(`( () => {
    971      // key: string "name/type"
    972      // value: array [answer1, answer2]
    973      global.dns_query_answers = {};
    974 
    975      // key: domain
    976      // value: a map containing {key: type, value: number of requests}
    977      global.dns_query_counts = {};
    978 
    979      global.gDoHPortsLog = [];
    980      global.gDoHNewConnLog = {};
    981      global.gDoHRequestCount = 0;
    982 
    983      global.dnsPacket = require(\`\${__dirname}/../dns-packet\`);
    984      global.ip = require(\`\${__dirname}/../node_ip\`);
    985      global.http2 = require("http2");
    986      global.url = require("url");
    987      global.zlib = require("zlib");
    988    })()`);
    989    await this.registerPathHandler("/dns-query", trrQueryHandler);
    990    await this.registerPathHandler("/dnsAnswer", answerHandler);
    991    await this.registerPathHandler("/doh", dohHandler);
    992    await this.registerPathHandler("/reset-doh-request-count", (req, res) => {
    993      global.gDoHRequestCount = 0;
    994      res.setHeader("Content-Type", "text/plain");
    995      res.setHeader("Content-Length", "ok".length);
    996      res.writeHead(200);
    997      res.write("ok");
    998      res.end("");
    999    });
   1000    await this.registerPathHandler("/", (req, res) => {
   1001      if (req.httpVersionMajor === 2) {
   1002        res.setHeader("X-Connection-Http2", "yes");
   1003        res.setHeader("X-Http2-StreamId", "" + req.stream.id);
   1004      } else {
   1005        res.setHeader("X-Connection-Http2", "no");
   1006      }
   1007      res.setHeader("Content-Type", "text/plain");
   1008      res.writeHead(404);
   1009      res.end("");
   1010    });
   1011    await this.registerPathHandler("/dns-cname", cnameHandler);
   1012    await this.registerPathHandler("/dns-cname-a", cnameAHandler);
   1013    await this.registerPathHandler("/server-timing", (req, res) => {
   1014      if (req.httpVersionMajor === 2) {
   1015        res.setHeader("X-Connection-Http2", "yes");
   1016        res.setHeader("X-Http2-StreamId", "" + req.stream.id);
   1017      } else {
   1018        res.setHeader("X-Connection-Http2", "no");
   1019      }
   1020 
   1021      res.setHeader("Content-Type", "text/plain");
   1022      res.setHeader("Content-Length", "12");
   1023      res.setHeader("Trailer", "Server-Timing");
   1024      res.setHeader(
   1025        "Server-Timing",
   1026        "metric; dur=123.4; desc=description, metric2; dur=456.78; desc=description1"
   1027      );
   1028      res.write("data reached");
   1029      res.addTrailers({
   1030        "Server-Timing":
   1031          "metric3; dur=789.11; desc=description2, metric4; dur=1112.13; desc=description3",
   1032      });
   1033      res.end();
   1034    });
   1035    await this.registerPathHandler("/redirect_to_http", (req, res) => {
   1036      let u = global.url.parse(req.url, true);
   1037      res.setHeader(
   1038        "Location",
   1039        `http://test.httpsrr.redirect.com:${u.query.port}/redirect_to_http?port=${u.query.port}`
   1040      );
   1041      res.writeHead(307);
   1042      res.end("");
   1043    });
   1044    await this.registerPathHandler("/origin_header", (req, res) => {
   1045      if (req.httpVersionMajor === 2) {
   1046        res.setHeader("X-Connection-Http2", "yes");
   1047        res.setHeader("X-Http2-StreamId", "" + req.stream.id);
   1048      } else {
   1049        res.setHeader("X-Connection-Http2", "no");
   1050      }
   1051 
   1052      let originHeader = req.headers.origin;
   1053      res.setHeader("Content-Length", originHeader.length);
   1054      res.setHeader("Content-Type", "text/plain");
   1055      res.writeHead(200);
   1056      res.write(originHeader);
   1057      res.end();
   1058    });
   1059 
   1060    await this.execute(getRequestCount);
   1061    await this.execute(`global.serverPort = ${this.port()}`);
   1062  }
   1063 
   1064  /// @name : string - name we're providing answers for. eg: foo.example.com
   1065  /// @type : string - the DNS query type. eg: "A", "AAAA", "CNAME", etc
   1066  /// @response : a map containing the response
   1067  ///   answers: array of answers (hashmap) that dnsPacket can parse
   1068  ///    eg: [{
   1069  ///          name: "bar.example.com",
   1070  ///          ttl: 55,
   1071  ///          type: "A",
   1072  ///          flush: false,
   1073  ///          data: "1.2.3.4",
   1074  ///        }]
   1075  ///   additionals - array of answers (hashmap) to be added to the additional section
   1076  ///   delay: int - if not 0 the response will be sent with after `delay` ms.
   1077  ///   flags: int - flags to be set on the answer
   1078  ///   error: int - HTTP status. If truthy then the response will send this status
   1079  async registerDoHAnswers(name, type, response = {}) {
   1080    let text = `global.dns_query_answers["${name}/${type}"] = ${JSON.stringify(
   1081      response
   1082    )}`;
   1083    return this.execute(text);
   1084  }
   1085 
   1086  async requestCount(domain, type) {
   1087    return this.execute(`getRequestCount("${domain}", "${type}")`);
   1088  }
   1089 }
   1090 
   1091 // Implements a basic HTTP2 proxy server
   1092 class TRRProxyCode {
   1093  static async startServer(endServerPort) {
   1094    const fs = require("fs");
   1095    const options = {
   1096      key: fs.readFileSync(__dirname + "/http2-cert.key"),
   1097      cert: fs.readFileSync(__dirname + "/http2-cert.pem"),
   1098    };
   1099 
   1100    const http2 = require("http2");
   1101    global.proxy = http2.createSecureServer(options);
   1102    this.setupProxy();
   1103    global.endServerPort = endServerPort;
   1104 
   1105    await global.proxy.listen(0);
   1106 
   1107    let serverPort = global.proxy.address().port;
   1108    return serverPort;
   1109  }
   1110 
   1111  static closeProxy() {
   1112    global.proxy.closeSockets();
   1113    return new Promise(resolve => {
   1114      global.proxy.close(resolve);
   1115    });
   1116  }
   1117 
   1118  static proxyRequestCount() {
   1119    return global.proxy_stream_count;
   1120  }
   1121 
   1122  static setupProxy() {
   1123    if (!global.proxy) {
   1124      throw new Error("proxy is null");
   1125    }
   1126 
   1127    global.proxy_stream_count = 0;
   1128 
   1129    // We need to track active connections so we can forcefully close keep-alive
   1130    // connections when shutting down the proxy.
   1131    global.proxy.socketIndex = 0;
   1132    global.proxy.socketMap = {};
   1133    global.proxy.on("connection", function (socket) {
   1134      let index = global.proxy.socketIndex++;
   1135      global.proxy.socketMap[index] = socket;
   1136      socket.on("close", function () {
   1137        delete global.proxy.socketMap[index];
   1138      });
   1139    });
   1140    global.proxy.closeSockets = function () {
   1141      for (let i in global.proxy.socketMap) {
   1142        global.proxy.socketMap[i].destroy();
   1143      }
   1144    };
   1145 
   1146    global.proxy.on("stream", (stream, headers) => {
   1147      if (headers[":method"] !== "CONNECT") {
   1148        // Only accept CONNECT requests
   1149        stream.respond({ ":status": 405 });
   1150        stream.end();
   1151        return;
   1152      }
   1153      global.proxy_stream_count++;
   1154      const net = require("net");
   1155      const socket = net.connect(global.endServerPort, "127.0.0.1", () => {
   1156        try {
   1157          stream.respond({ ":status": 200 });
   1158          socket.pipe(stream);
   1159          stream.pipe(socket);
   1160        } catch (exception) {
   1161          console.log(exception);
   1162          stream.close();
   1163        }
   1164      });
   1165      socket.on("error", error => {
   1166        throw new Error(
   1167          `Unxpected error when conneting the HTTP/2 server from the HTTP/2 proxy during CONNECT handling: '${error}'`
   1168        );
   1169      });
   1170    });
   1171  }
   1172 }
   1173 
   1174 class TRRProxy {
   1175  // Starts the proxy
   1176  async start(port) {
   1177    info("TRRProxy start!");
   1178    this.processId = await TRRNodeServer.fork();
   1179    info("processid=" + this.processId);
   1180    await this.execute(TRRProxyCode);
   1181    this.port = await this.execute(`TRRProxyCode.startServer(${port})`);
   1182    Assert.notEqual(this.port, null);
   1183  }
   1184 
   1185  // Executes a command in the context of the node server
   1186  async execute(command) {
   1187    return TRRNodeServer.execute(this.processId, command);
   1188  }
   1189 
   1190  // Stops the server
   1191  async stop() {
   1192    if (this.processId) {
   1193      await TRRNodeServer.execute(this.processId, `TRRProxyCode.closeProxy()`);
   1194      await TRRNodeServer.kill(this.processId);
   1195    }
   1196  }
   1197 
   1198  async request_count() {
   1199    let data = await TRRNodeServer.execute(
   1200      this.processId,
   1201      `TRRProxyCode.proxyRequestCount()`
   1202    );
   1203    return parseInt(data);
   1204  }
   1205 }