tor-browser

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

commit 0c6ed1f292594e45bf32188d0608940f27e21afa
parent 834688be4e8718a10b2732b9b9eb724369cbc235
Author: Valentin Gosu <valentin.gosu@gmail.com>
Date:   Tue, 16 Dec 2025 08:47:57 +0000

Bug 2005731 - Move DoH handling from moz-http2.js to head_trr.js r=necko-reviewers,kershaw

Previously an exception thrown by any of the handlers of moz-http2.js
could bring down the server and make other tests that used also fail.
This change moves the DoH handling to TRRServer, so any failures only
affect that one test. Additionally, global state is limited to the
test that spawns the server, and can't leak to other unit tests.

Differential Revision: https://phabricator.services.mozilla.com/D276236

Diffstat:
Mnetwerk/test/unit/head_trr.js | 643++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mnetwerk/test/unit/test_trr.js | 22+++++++++++++---------
Mtesting/xpcshell/moz-http2/moz-http2.js | 650-------------------------------------------------------------------------------
3 files changed, 648 insertions(+), 667 deletions(-)

diff --git a/netwerk/test/unit/head_trr.js b/netwerk/test/unit/head_trr.js @@ -7,7 +7,7 @@ /* import-globals-from head_cache.js */ /* import-globals-from head_cookies.js */ /* import-globals-from head_channels.js */ -/* globals require, __dirname, global, Buffer, process */ +/* globals require, __dirname, global, Buffer, process, setTimeout */ var { NodeHTTP2Server: TRRNodeHttp2Server, NodeServer: TRRNodeServer } = ChromeUtils.importESModule("resource://testing-common/NodeServer.sys.mjs"); @@ -46,13 +46,6 @@ function trr_test_setup() { // we specifically test that this works Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false); - // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem - // so add that cert to the trust list as a signing cert. // the foo.example.com domain name. - let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( - Ci.nsIX509CertDB - ); - addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u"); - // Turn off strict fallback mode and TRR retry for most tests, // it is tested specifically. Services.prefs.setBoolPref("network.trr.strict_native_fallback", false); @@ -398,6 +391,568 @@ function trrQueryHandler(req, resp) { } } +function dohHandler(req, res) { + let u = global.url.parse(req.url, true); + + function handleAuth() { + // There's a Set-Cookie: header in the response for "/dns" , which this + // request subsequently would include if the http channel wasn't + // anonymous. Thus, if there's a cookie in this request, we know Firefox + // mishaved. If there's not, we're fine. + if (req.headers.cookie) { + res.writeHead(403); + res.end("cookie for me, not for you"); + return false; + } + if (req.headers.authorization != "user:password") { + res.writeHead(401); + res.end("bad boy!"); + return false; + } + + return true; + } + + function createDNSAnswer(response, packet, responseIP, requestPayload) { + // This shuts down the connection so we can test if the client reconnects + if (packet.questions.length && packet.questions[0].name == "closeme.com") { + // response.stream.connection.close("INTERNAL_ERROR", response.stream.id); + req.stream.session.close(); + return null; + } + + let answers = []; + + if (u.query.httpssvc) { + responseIP = "none"; + answers.push({ + name: packet.questions[0].name, + type: packet.questions[0].type, + ttl: 55, + class: "IN", + flush: false, + data: { + priority: 1, + name: "h3pool", + values: [ + { key: "alpn", value: ["h2", "h3"] }, + { key: "no-default-alpn" }, + { key: "port", value: 8888 }, + { key: "ipv4hint", value: "1.2.3.4" }, + { key: "echconfig", value: "123..." }, + { key: "ipv6hint", value: "::1" }, + { key: 30, value: "somelargestring" }, + { key: "odoh", value: "456..." }, + ], + }, + }); + answers.push({ + name: packet.questions[0].name, + type: packet.questions[0].type, + ttl: 55, + class: "IN", + flush: false, + data: { + priority: 2, + name: ".", + values: [ + { key: "alpn", value: "h2" }, + { key: "ipv4hint", value: ["1.2.3.4", "5.6.7.8"] }, + { key: "echconfig", value: "abc..." }, + { key: "ipv6hint", value: ["::1", "fe80::794f:6d2c:3d5e:7836"] }, + { key: "odoh", value: "def..." }, + ], + }, + }); + answers.push({ + name: packet.questions[0].name, + type: packet.questions[0].type, + ttl: 55, + class: "IN", + flush: false, + data: { + priority: 3, + name: "hello", + values: [], + }, + }); + } else if (u.query.httpssvc_as_altsvc) { + responseIP = "none"; + if (packet.questions[0].type == "HTTPS") { + let priority = 1; + if (packet.questions[0].name === "foo.notexisted.com") { + priority = 0; + } + answers.push({ + name: packet.questions[0].name, + type: packet.questions[0].type, + ttl: 55, + class: "IN", + flush: false, + data: { + priority, + name: packet.questions[0].name, + values: [ + { key: "alpn", value: "h2" }, + { key: "port", value: global.serverPort }, + { key: 30, value: "somelargestring" }, + ], + }, + }); + } else { + answers.push({ + name: packet.questions[0].name, + type: "A", + ttl: 55, + flush: false, + data: "127.0.0.1", + }); + } + } else if (u.query.httpssvc_use_iphint) { + responseIP = "none"; + answers.push({ + name: packet.questions[0].name, + type: "HTTPS", + ttl: 55, + class: "IN", + flush: false, + data: { + priority: 1, + name: ".", + values: [ + { key: "alpn", value: "h2" }, + { key: "port", value: global.serverPort }, + { key: "ipv4hint", value: "127.0.0.1" }, + ], + }, + }); + } + + if (packet.questions.length && packet.questions[0].name.endsWith(".pd")) { + // Bug 1543811: test edns padding extension. Return whether padding was + // included via the first half of the ip address (1.1 vs 2.2) and the + // size of the request in the second half of the ip address allowing to + // verify that the correct amount of padding was added. + if ( + !!packet.additionals.length && + packet.additionals[0].type == "OPT" && + packet.additionals[0].options.some(o => o.type === "PADDING") + ) { + // add padding to the response, because the client must be able ignore it + answers.push({ + name: ".", + type: "PADDING", + data: Buffer.from( + // PADDING_PADDING_PADDING + "50414444494e475f50414444494e475f50414444494e47", + "hex" + ), + }); + responseIP = + "1.1." + + ((requestPayload.length >> 8) & 0xff) + + "." + + (requestPayload.length & 0xff); + } else { + responseIP = + "2.2." + + ((requestPayload.length >> 8) & 0xff) + + "." + + (requestPayload.length & 0xff); + } + } + + if (u.query.corruptedAnswer) { + // DNS response header is 12 bytes, we check for this minimum length + // at the start of decoding so this is the simplest way to force + // a decode error. + return "\xFF\xFF\xFF\xFF"; + } + + // Because we send two TRR requests (A and AAAA), skip the first two + // requests when testing retry. + if (u.query.retryOnDecodeFailure && global.gDoHRequestCount < 2) { + global.gDoHRequestCount++; + return "\xFF\xFF\xFF\xFF"; + } + + function responseData() { + if ( + !!packet.questions.length && + packet.questions[0].name == "confirm.example.com" && + packet.questions[0].type == "NS" + ) { + return "ns.example.com"; + } + + return responseIP; + } + + if ( + responseIP != "none" && + responseType(packet, responseIP) == packet.questions[0].type + ) { + answers.push({ + name: u.query.hostname ? u.query.hostname : packet.questions[0].name, + ttl: 55, + type: responseType(packet, responseIP), + flush: false, + data: responseData(), + }); + } + + // for use with test_dns_by_type_resolve.js + if (packet.questions[0].type == "TXT") { + answers.push({ + name: packet.questions[0].name, + type: packet.questions[0].type, + ttl: 55, + class: "IN", + flush: false, + data: Buffer.from( + "62586B67646D39705932556761584D6762586B676347467A63336476636D513D", + "hex" + ), + }); + } + + if (u.query.cnameloop) { + answers.push({ + name: "cname.example.com", + type: "CNAME", + ttl: 55, + class: "IN", + flush: false, + data: "pointing-elsewhere.example.com", + }); + } + + if (req.headers["accept-language"] || req.headers["user-agent"]) { + // If we get this header, don't send back any response. This should + // cause the tests to fail. This is easier then actually sending back + // the header value into test_trr.js + answers = []; + } + + let buf = global.dnsPacket.encode({ + type: "response", + id: packet.id, + flags: global.dnsPacket.RECURSION_DESIRED, + questions: packet.questions, + answers, + }); + + return buf; + } + + function responseType(packet, responseIP) { + if ( + !!packet.questions.length && + packet.questions[0].name == "confirm.example.com" && + packet.questions[0].type == "NS" + ) { + return "NS"; + } + + return global.ip.isV4Format(responseIP) ? "A" : "AAAA"; + } + + function getDelayFromPacket(packet, type) { + let delay = 0; + if (packet.questions[0].type == "A") { + delay = parseInt(u.query.delayIPv4); + } else if (packet.questions[0].type == "AAAA") { + delay = parseInt(u.query.delayIPv6); + } + + if (u.query.slowConfirm && type == "NS") { + delay += 1000; + } + + return delay; + } + + function writeDNSResponse(response, buf, delay, contentType) { + function writeResponse(resp, buffer) { + resp.setHeader("Set-Cookie", "trackyou=yes; path=/; max-age=100000;"); + resp.setHeader("Content-Type", contentType); + if (req.headers["accept-encoding"].includes("gzip")) { + global.zlib.gzip(buffer, function (err, result) { + resp.setHeader("Content-Encoding", "gzip"); + resp.setHeader("Content-Length", result.length); + try { + resp.writeHead(200); + resp.end(result); + } catch (e) { + // connection was closed by the time we started writing. + } + }); + } else { + const output = Buffer.from(buffer, "utf-8"); + resp.setHeader("Content-Length", output.length); + try { + resp.writeHead(200); + resp.write(output); + resp.end(""); + } catch (e) { + // connection was closed by the time we started writing. + } + } + } + + if (delay) { + setTimeout( + arg => { + writeResponse(arg[0], arg[1]); + }, + delay + 1, + [response, buf] + ); + return; + } + + writeResponse(response, buf); + } + + let responseIP = u.query.responseIP; + if (!responseIP) { + responseIP = "5.5.5.5"; + } + + let redirect = u.query.redirect; + if (redirect) { + responseIP = redirect; + if (u.query.dns) { + res.setHeader( + "Location", + "https://localhost:" + + global.serverPort + + "/doh?responseIP=" + + responseIP + + "&dns=" + + u.query.dns + ); + } else { + res.setHeader( + "Location", + "https://localhost:" + + global.serverPort + + "/doh?responseIP=" + + responseIP + ); + } + res.writeHead(307); + res.end(""); + return; + } + + if (u.query.auth) { + if (!handleAuth()) { + return; + } + } + + if (u.query.noResponse) { + return; + } + + if (u.query.push) { + // push.example.org has AAAA entry 2018::2018 + let pcontent = global.dnsPacket.encode({ + id: 0, + type: "response", + flags: global.dnsPacket.RECURSION_DESIRED, + questions: [{ name: "push.example.org", type: "AAAA", class: "IN" }], + answers: [ + { + name: "push.example.org", + type: "AAAA", + ttl: 55, + class: "IN", + flush: false, + data: "2018::2018", + }, + ], + }); + let push = res.push({ + hostname: "foo.example.com:" + global.serverPort, + port: global.serverPort, + path: "/dns-pushed-response?dns=AAAAAAABAAAAAAAABHB1c2gHZXhhbXBsZQNvcmcAABwAAQ", + method: "GET", + headers: { + accept: "application/dns-message", + }, + }); + push.writeHead(200, { + "content-type": "application/dns-message", + pushed: "yes", + "content-length": pcontent.length, + "X-Connection-Http2": "yes", + }); + push.end(pcontent); + } + + let payload = Buffer.from(""); + + function emitResponse(response, requestPayload, decodedPacket, delay) { + let packet = decodedPacket || global.dnsPacket.decode(requestPayload); + let answer = createDNSAnswer(response, packet, responseIP, requestPayload); + if (!answer) { + return; + } + writeDNSResponse( + response, + answer, + delay || getDelayFromPacket(packet, responseType(packet, responseIP)), + "application/dns-message" + ); + } + + if (u.query.dns) { + payload = Buffer.from(u.query.dns, "base64"); + emitResponse(res, payload); + return; + } + + req.on("data", function receiveData(chunk) { + payload = Buffer.concat([payload, chunk]); + }); + req.on("end", function finishedData() { + // parload is empty when we send redirect response. + if (payload.length) { + let packet = global.dnsPacket.decode(payload); + emitResponse(res, payload, packet); + } + }); +} + +function cnameHandler(req, res) { + // asking for cname.example.com + + function createCNameContent(payload) { + let packet = global.dnsPacket.decode(payload); + if ( + packet.questions[0].name == "cname.example.com" && + packet.questions[0].type == "A" + ) { + return global.dnsPacket.encode({ + id: 0, + type: "response", + flags: global.dnsPacket.RECURSION_DESIRED, + questions: [{ name: packet.questions[0].name, type: "A", class: "IN" }], + answers: [ + { + name: packet.questions[0].name, + ttl: 55, + type: "CNAME", + flush: false, + data: "pointing-elsewhere.example.com", + }, + ], + }); + } + if ( + packet.questions[0].name == "pointing-elsewhere.example.com" && + packet.questions[0].type == "A" + ) { + return global.dnsPacket.encode({ + id: 0, + type: "response", + flags: global.dnsPacket.RECURSION_DESIRED, + questions: [{ name: packet.questions[0].name, type: "A", class: "IN" }], + answers: [ + { + name: packet.questions[0].name, + ttl: 55, + type: "A", + flush: false, + data: "99.88.77.66", + }, + ], + }); + } + + return global.dnsPacket.encode({ + id: 0, + type: "response", + flags: + global.dnsPacket.RECURSION_DESIRED | + global.dnsPacket.rcodes.toRcode("NXDOMAIN"), + questions: [ + { + name: packet.questions[0].name, + type: packet.questions[0].type, + class: "IN", + }, + ], + answers: [], + }); + } + + function emitResponse(response, payload) { + let pcontent = createCNameContent(payload); + response.setHeader("Content-Type", "application/dns-message"); + response.setHeader("Content-Length", pcontent.length); + response.writeHead(200); + response.write(pcontent); + response.end(""); + } + + let payload = Buffer.from(""); + req.on("data", function receiveData(chunk) { + payload = Buffer.concat([payload, chunk]); + }); + req.on("end", function finishedData() { + emitResponse(res, payload); + }); +} + +function cnameAHandler(req, res) { + function createCNameARecord() { + // test23 asks for cname-a.example.com + // this responds with a CNAME to here.example.com *and* an A record + // for here.example.com + let rContent; + + rContent = Buffer.from( + "0000" + + "0100" + + "0001" + // QDCOUNT + "0002" + // ANCOUNT + "00000000" + // NSCOUNT + ARCOUNT + "07636E616D652d61" + // cname-a + "076578616D706C6503636F6D00" + // .example.com + "00010001" + // question type (A) + question class (IN) + // answer record 1 + "C00C" + // name pointer to cname-a.example.com + "0005" + // type (CNAME) + "0001" + // class + "00000037" + // TTL + "0012" + // RDLENGTH + "0468657265" + // here + "076578616D706C6503636F6D00" + // .example.com + // answer record 2, the A entry for the CNAME above + "0468657265" + // here + "076578616D706C6503636F6D00" + // .example.com + "0001" + // type (A) + "0001" + // class + "00000037" + // TTL + "0004" + // RDLENGTH + "09080706", // IPv4 address + "hex" + ); + + return rContent; + } + + let rContent = createCNameARecord(); + res.setHeader("Content-Type", "application/dns-message"); + res.setHeader("Content-Length", rContent.length); + res.writeHead(200); + res.write(rContent); + res.end(""); +} + function getRequestCount(domain, type) { if (!global.dns_query_counts[domain]) { return 0; @@ -423,15 +978,87 @@ class TRRServer extends TRRNodeHttp2Server { global.gDoHPortsLog = []; global.gDoHNewConnLog = {}; + global.gDoHRequestCount = 0; global.dnsPacket = require(\`\${__dirname}/../dns-packet\`); global.ip = require(\`\${__dirname}/../node_ip\`); global.http2 = require("http2"); global.url = require("url"); + global.zlib = require("zlib"); })()`); await this.registerPathHandler("/dns-query", trrQueryHandler); await this.registerPathHandler("/dnsAnswer", answerHandler); + await this.registerPathHandler("/doh", dohHandler); + await this.registerPathHandler("/reset-doh-request-count", (req, res) => { + global.gDoHRequestCount = 0; + res.setHeader("Content-Type", "text/plain"); + res.setHeader("Content-Length", "ok".length); + res.writeHead(200); + res.write("ok"); + res.end(""); + }); + await this.registerPathHandler("/", (req, res) => { + if (req.httpVersionMajor === 2) { + res.setHeader("X-Connection-Http2", "yes"); + res.setHeader("X-Http2-StreamId", "" + req.stream.id); + } else { + res.setHeader("X-Connection-Http2", "no"); + } + res.setHeader("Content-Type", "text/plain"); + res.writeHead(404); + res.end(""); + }); + await this.registerPathHandler("/dns-cname", cnameHandler); + await this.registerPathHandler("/dns-cname-a", cnameAHandler); + await this.registerPathHandler("/server-timing", (req, res) => { + if (req.httpVersionMajor === 2) { + res.setHeader("X-Connection-Http2", "yes"); + res.setHeader("X-Http2-StreamId", "" + req.stream.id); + } else { + res.setHeader("X-Connection-Http2", "no"); + } + + res.setHeader("Content-Type", "text/plain"); + res.setHeader("Content-Length", "12"); + res.setHeader("Trailer", "Server-Timing"); + res.setHeader( + "Server-Timing", + "metric; dur=123.4; desc=description, metric2; dur=456.78; desc=description1" + ); + res.write("data reached"); + res.addTrailers({ + "Server-Timing": + "metric3; dur=789.11; desc=description2, metric4; dur=1112.13; desc=description3", + }); + res.end(); + }); + await this.registerPathHandler("/redirect_to_http", (req, res) => { + let u = global.url.parse(req.url, true); + res.setHeader( + "Location", + `http://test.httpsrr.redirect.com:${u.query.port}/redirect_to_http?port=${u.query.port}` + ); + res.writeHead(307); + res.end(""); + }); + await this.registerPathHandler("/origin_header", (req, res) => { + if (req.httpVersionMajor === 2) { + res.setHeader("X-Connection-Http2", "yes"); + res.setHeader("X-Http2-StreamId", "" + req.stream.id); + } else { + res.setHeader("X-Connection-Http2", "no"); + } + + let originHeader = req.headers.origin; + res.setHeader("Content-Length", originHeader.length); + res.setHeader("Content-Type", "text/plain"); + res.writeHead(200); + res.write(originHeader); + res.end(); + }); + await this.execute(getRequestCount); + await this.execute(`global.serverPort = ${this.port()}`); } /// @name : string - name we're providing answers for. eg: foo.example.com diff --git a/netwerk/test/unit/test_trr.js b/netwerk/test/unit/test_trr.js @@ -11,16 +11,20 @@ const { NodeServer } = ChromeUtils.importESModule( SetParentalControlEnabled(false); -function setup() { +let trrServer; +add_setup(async function setup() { Services.prefs.setBoolPref("network.dns.get-ttl", false); - h2Port = trr_test_setup(); -} - -setup(); -registerCleanupFunction(async () => { - trr_clear_prefs(); - Services.prefs.clearUserPref("network.dns.get-ttl"); - Services.prefs.clearUserPref("network.dns.disableIPv6"); + trr_test_setup(); + trrServer = new TRRServer(); + await trrServer.start(); + h2Port = trrServer.port(); + + registerCleanupFunction(async () => { + trr_clear_prefs(); + Services.prefs.clearUserPref("network.dns.get-ttl"); + Services.prefs.clearUserPref("network.dns.disableIPv6"); + await trrServer.stop(); + }); }); async function waitForConfirmation(expectedResponseIP, confirmationShouldFail) { diff --git a/testing/xpcshell/moz-http2/moz-http2.js b/testing/xpcshell/moz-http2/moz-http2.js @@ -15,12 +15,10 @@ var http2 = require(node_http2_root); var fs = require("fs"); var url = require("url"); var crypto = require("crypto"); -const dnsPacket = require(`${node_http2_root}/../dns-packet`); const ip = require(`${node_http2_root}/../node_ip`); const { fork } = require("child_process"); const { spawn } = require("child_process"); const path = require("path"); -const zlib = require("zlib"); // Hook into the decompression code to log the decompressed name-value pairs var compression_module = node_http2_root + "/lib/protocol/compressor"; @@ -208,8 +206,6 @@ var didRst = false; var rstConnection = null; var illegalheader_conn = null; -var gDoHRequestCount = 0; - // eslint-disable-next-line complexity function handleRequest(req, res) { var u = ""; @@ -219,314 +215,6 @@ function handleRequest(req, res) { var content = getHttpContent(u.pathname); var push; - function createCNameContent(payload) { - let packet = dnsPacket.decode(payload); - if ( - packet.questions[0].name == "cname.example.com" && - packet.questions[0].type == "A" - ) { - return dnsPacket.encode({ - id: 0, - type: "response", - flags: dnsPacket.RECURSION_DESIRED, - questions: [{ name: packet.questions[0].name, type: "A", class: "IN" }], - answers: [ - { - name: packet.questions[0].name, - ttl: 55, - type: "CNAME", - flush: false, - data: "pointing-elsewhere.example.com", - }, - ], - }); - } - if ( - packet.questions[0].name == "pointing-elsewhere.example.com" && - packet.questions[0].type == "A" - ) { - return dnsPacket.encode({ - id: 0, - type: "response", - flags: dnsPacket.RECURSION_DESIRED, - questions: [{ name: packet.questions[0].name, type: "A", class: "IN" }], - answers: [ - { - name: packet.questions[0].name, - ttl: 55, - type: "A", - flush: false, - data: "99.88.77.66", - }, - ], - }); - } - - return dnsPacket.encode({ - id: 0, - type: "response", - flags: dnsPacket.RECURSION_DESIRED | dnsPacket.rcodes.toRcode("NXDOMAIN"), - questions: [ - { - name: packet.questions[0].name, - type: packet.questions[0].type, - class: "IN", - }, - ], - answers: [], - }); - } - - function createCNameARecord() { - // test23 asks for cname-a.example.com - // this responds with a CNAME to here.example.com *and* an A record - // for here.example.com - let rContent; - - rContent = Buffer.from( - "0000" + - "0100" + - "0001" + // QDCOUNT - "0002" + // ANCOUNT - "00000000" + // NSCOUNT + ARCOUNT - "07636E616D652d61" + // cname-a - "076578616D706C6503636F6D00" + // .example.com - "00010001" + // question type (A) + question class (IN) - // answer record 1 - "C00C" + // name pointer to cname-a.example.com - "0005" + // type (CNAME) - "0001" + // class - "00000037" + // TTL - "0012" + // RDLENGTH - "0468657265" + // here - "076578616D706C6503636F6D00" + // .example.com - // answer record 2, the A entry for the CNAME above - "0468657265" + // here - "076578616D706C6503636F6D00" + // .example.com - "0001" + // type (A) - "0001" + // class - "00000037" + // TTL - "0004" + // RDLENGTH - "09080706", // IPv4 address - "hex" - ); - - return rContent; - } - - function responseType(packet, responseIP) { - if ( - !!packet.questions.length && - packet.questions[0].name == "confirm.example.com" && - packet.questions[0].type == "NS" - ) { - return "NS"; - } - - return ip.isV4Format(responseIP) ? "A" : "AAAA"; - } - - function handleAuth() { - // There's a Set-Cookie: header in the response for "/dns" , which this - // request subsequently would include if the http channel wasn't - // anonymous. Thus, if there's a cookie in this request, we know Firefox - // mishaved. If there's not, we're fine. - if (req.headers.cookie) { - res.writeHead(403); - res.end("cookie for me, not for you"); - return false; - } - if (req.headers.authorization != "user:password") { - res.writeHead(401); - res.end("bad boy!"); - return false; - } - - return true; - } - - function createDNSAnswer(response, packet, responseIP, requestPayload) { - // This shuts down the connection so we can test if the client reconnects - if (packet.questions.length && packet.questions[0].name == "closeme.com") { - response.stream.connection.close("INTERNAL_ERROR", response.stream.id); - return null; - } - - let answers = []; - if (packet.questions.length && packet.questions[0].name.endsWith(".pd")) { - // Bug 1543811: test edns padding extension. Return whether padding was - // included via the first half of the ip address (1.1 vs 2.2) and the - // size of the request in the second half of the ip address allowing to - // verify that the correct amount of padding was added. - if ( - !!packet.additionals.length && - packet.additionals[0].type == "OPT" && - packet.additionals[0].options.some(o => o.type === "PADDING") - ) { - // add padding to the response, because the client must be able ignore it - answers.push({ - name: ".", - type: "PADDING", - data: Buffer.from( - // PADDING_PADDING_PADDING - "50414444494e475f50414444494e475f50414444494e47", - "hex" - ), - }); - responseIP = - "1.1." + - ((requestPayload.length >> 8) & 0xff) + - "." + - (requestPayload.length & 0xff); - } else { - responseIP = - "2.2." + - ((requestPayload.length >> 8) & 0xff) + - "." + - (requestPayload.length & 0xff); - } - } - - if (u.query.corruptedAnswer) { - // DNS response header is 12 bytes, we check for this minimum length - // at the start of decoding so this is the simplest way to force - // a decode error. - return "\xFF\xFF\xFF\xFF"; - } - - // Because we send two TRR requests (A and AAAA), skip the first two - // requests when testing retry. - if (u.query.retryOnDecodeFailure && gDoHRequestCount < 2) { - gDoHRequestCount++; - return "\xFF\xFF\xFF\xFF"; - } - - function responseData() { - if ( - !!packet.questions.length && - packet.questions[0].name == "confirm.example.com" && - packet.questions[0].type == "NS" - ) { - return "ns.example.com"; - } - - return responseIP; - } - - if ( - responseIP != "none" && - responseType(packet, responseIP) == packet.questions[0].type - ) { - answers.push({ - name: u.query.hostname ? u.query.hostname : packet.questions[0].name, - ttl: 55, - type: responseType(packet, responseIP), - flush: false, - data: responseData(), - }); - } - - // for use with test_dns_by_type_resolve.js - if (packet.questions[0].type == "TXT") { - answers.push({ - name: packet.questions[0].name, - type: packet.questions[0].type, - ttl: 55, - class: "IN", - flush: false, - data: Buffer.from( - "62586B67646D39705932556761584D6762586B676347467A63336476636D513D", - "hex" - ), - }); - } - - if (u.query.cnameloop) { - answers.push({ - name: "cname.example.com", - type: "CNAME", - ttl: 55, - class: "IN", - flush: false, - data: "pointing-elsewhere.example.com", - }); - } - - if (req.headers["accept-language"] || req.headers["user-agent"]) { - // If we get this header, don't send back any response. This should - // cause the tests to fail. This is easier then actually sending back - // the header value into test_trr.js - answers = []; - } - - let buf = dnsPacket.encode({ - type: "response", - id: packet.id, - flags: dnsPacket.RECURSION_DESIRED, - questions: packet.questions, - answers, - }); - - return buf; - } - - function getDelayFromPacket(packet, type) { - let delay = 0; - if (packet.questions[0].type == "A") { - delay = parseInt(u.query.delayIPv4); - } else if (packet.questions[0].type == "AAAA") { - delay = parseInt(u.query.delayIPv6); - } - - if (u.query.slowConfirm && type == "NS") { - delay += 1000; - } - - return delay; - } - - function writeDNSResponse(response, buf, delay, contentType) { - function writeResponse(resp, buffer) { - resp.setHeader("Set-Cookie", "trackyou=yes; path=/; max-age=100000;"); - resp.setHeader("Content-Type", contentType); - if (req.headers["accept-encoding"].includes("gzip")) { - zlib.gzip(buffer, function (err, result) { - resp.setHeader("Content-Encoding", "gzip"); - resp.setHeader("Content-Length", result.length); - try { - resp.writeHead(200); - resp.end(result); - } catch (e) { - // connection was closed by the time we started writing. - } - }); - } else { - const output = Buffer.from(buffer, "utf-8"); - resp.setHeader("Content-Length", output.length); - try { - resp.writeHead(200); - resp.write(output); - resp.end(""); - } catch (e) { - // connection was closed by the time we started writing. - } - } - } - - if (delay) { - setTimeout( - arg => { - writeResponse(arg[0], arg[1]); - }, - delay + 1, - [response, buf] - ); - return; - } - - writeResponse(response, buf); - } - if (req.httpVersionMajor === 2) { res.setHeader("X-Connection-Http2", "yes"); res.setHeader("X-Http2-StreamId", "" + req.stream.id); @@ -815,328 +503,6 @@ function handleRequest(req, res) { "Alt-Svc", "h3-29=" + req.headers["x-altsvc"] + ",h3=" + req.headers["x-altsvc"] ); - } - // for use with test_trr.js - else if (u.pathname === "/dns-cname") { - // asking for cname.example.com - - function emitResponse(response, payload) { - let pcontent = createCNameContent(payload); - response.setHeader("Content-Type", "application/dns-message"); - response.setHeader("Content-Length", pcontent.length); - response.writeHead(200); - response.write(pcontent); - response.end(""); - } - - let payload = Buffer.from(""); - req.on("data", function receiveData(chunk) { - payload = Buffer.concat([payload, chunk]); - }); - req.on("end", function finishedData() { - emitResponse(res, payload); - }); - return; - } else if (u.pathname == "/reset-doh-request-count") { - gDoHRequestCount = 0; - res.setHeader("Content-Type", "text/plain"); - res.setHeader("Content-Length", "ok".length); - res.writeHead(200); - res.write("ok"); - res.end(""); - return; - } else if (u.pathname == "/doh") { - let responseIP = u.query.responseIP; - if (!responseIP) { - responseIP = "5.5.5.5"; - } - - let redirect = u.query.redirect; - if (redirect) { - responseIP = redirect; - if (u.query.dns) { - res.setHeader( - "Location", - "https://localhost:" + - serverPort + - "/doh?responseIP=" + - responseIP + - "&dns=" + - u.query.dns - ); - } else { - res.setHeader( - "Location", - "https://localhost:" + serverPort + "/doh?responseIP=" + responseIP - ); - } - res.writeHead(307); - res.end(""); - return; - } - - if (u.query.auth) { - if (!handleAuth()) { - return; - } - } - - if (u.query.noResponse) { - return; - } - - if (u.query.push) { - // push.example.org has AAAA entry 2018::2018 - let pcontent = dnsPacket.encode({ - id: 0, - type: "response", - flags: dnsPacket.RECURSION_DESIRED, - questions: [{ name: "push.example.org", type: "AAAA", class: "IN" }], - answers: [ - { - name: "push.example.org", - type: "AAAA", - ttl: 55, - class: "IN", - flush: false, - data: "2018::2018", - }, - ], - }); - push = res.push({ - hostname: "foo.example.com:" + serverPort, - port: serverPort, - path: "/dns-pushed-response?dns=AAAAAAABAAAAAAAABHB1c2gHZXhhbXBsZQNvcmcAABwAAQ", - method: "GET", - headers: { - accept: "application/dns-message", - }, - }); - push.writeHead(200, { - "content-type": "application/dns-message", - pushed: "yes", - "content-length": pcontent.length, - "X-Connection-Http2": "yes", - }); - push.end(pcontent); - } - - let payload = Buffer.from(""); - - function emitResponse(response, requestPayload, decodedPacket, delay) { - let packet = decodedPacket || dnsPacket.decode(requestPayload); - let answer = createDNSAnswer( - response, - packet, - responseIP, - requestPayload - ); - if (!answer) { - return; - } - writeDNSResponse( - response, - answer, - delay || getDelayFromPacket(packet, responseType(packet, responseIP)), - "application/dns-message" - ); - } - - if (u.query.dns) { - payload = Buffer.from(u.query.dns, "base64"); - emitResponse(res, payload); - return; - } - - req.on("data", function receiveData(chunk) { - payload = Buffer.concat([payload, chunk]); - }); - req.on("end", function finishedData() { - // parload is empty when we send redirect response. - if (payload.length) { - let packet = dnsPacket.decode(payload); - emitResponse(res, payload, packet); - } - }); - return; - } else if (u.pathname === "/httpssvc") { - let payload = Buffer.from(""); - req.on("data", function receiveData(chunk) { - payload = Buffer.concat([payload, chunk]); - }); - req.on("end", function finishedData() { - let packet = dnsPacket.decode(payload); - let answers = []; - answers.push({ - name: packet.questions[0].name, - type: packet.questions[0].type, - ttl: 55, - class: "IN", - flush: false, - data: { - priority: 1, - name: "h3pool", - values: [ - { key: "alpn", value: ["h2", "h3"] }, - { key: "no-default-alpn" }, - { key: "port", value: 8888 }, - { key: "ipv4hint", value: "1.2.3.4" }, - { key: "echconfig", value: "123..." }, - { key: "ipv6hint", value: "::1" }, - { key: 30, value: "somelargestring" }, - { key: "odoh", value: "456..." }, - ], - }, - }); - answers.push({ - name: packet.questions[0].name, - type: packet.questions[0].type, - ttl: 55, - class: "IN", - flush: false, - data: { - priority: 2, - name: ".", - values: [ - { key: "alpn", value: "h2" }, - { key: "ipv4hint", value: ["1.2.3.4", "5.6.7.8"] }, - { key: "echconfig", value: "abc..." }, - { key: "ipv6hint", value: ["::1", "fe80::794f:6d2c:3d5e:7836"] }, - { key: "odoh", value: "def..." }, - ], - }, - }); - answers.push({ - name: packet.questions[0].name, - type: packet.questions[0].type, - ttl: 55, - class: "IN", - flush: false, - data: { - priority: 3, - name: "hello", - values: [], - }, - }); - let buf = dnsPacket.encode({ - type: "response", - id: packet.id, - flags: dnsPacket.RECURSION_DESIRED, - questions: packet.questions, - answers, - }); - - res.setHeader("Content-Type", "application/dns-message"); - res.setHeader("Content-Length", buf.length); - res.writeHead(200); - res.write(buf); - res.end(""); - }); - return; - } else if (u.pathname === "/httpssvc_as_altsvc") { - let payload = Buffer.from(""); - req.on("data", function receiveData(chunk) { - payload = Buffer.concat([payload, chunk]); - }); - req.on("end", function finishedData() { - let packet = dnsPacket.decode(payload); - let answers = []; - if (packet.questions[0].type == "HTTPS") { - let priority = 1; - // Set an invalid priority to test the case when receiving a corrupted - // response. - if (packet.questions[0].name === "foo.notexisted.com") { - priority = 0; - } - answers.push({ - name: packet.questions[0].name, - type: packet.questions[0].type, - ttl: 55, - class: "IN", - flush: false, - data: { - priority, - name: packet.questions[0].name, - values: [ - { key: "alpn", value: "h2" }, - { key: "port", value: serverPort }, - { key: 30, value: "somelargestring" }, - ], - }, - }); - } else { - answers.push({ - name: packet.questions[0].name, - type: "A", - ttl: 55, - flush: false, - data: "127.0.0.1", - }); - } - - let buf = dnsPacket.encode({ - type: "response", - id: packet.id, - flags: dnsPacket.RECURSION_DESIRED, - questions: packet.questions, - answers, - }); - - res.setHeader("Content-Type", "application/dns-message"); - res.setHeader("Content-Length", buf.length); - res.writeHead(200); - res.write(buf); - res.end(""); - }); - return; - } else if (u.pathname === "/httpssvc_use_iphint") { - let payload = Buffer.from(""); - req.on("data", function receiveData(chunk) { - payload = Buffer.concat([payload, chunk]); - }); - req.on("end", function finishedData() { - let packet = dnsPacket.decode(payload); - let answers = []; - answers.push({ - name: packet.questions[0].name, - type: "HTTPS", - ttl: 55, - class: "IN", - flush: false, - data: { - priority: 1, - name: ".", - values: [ - { key: "alpn", value: "h2" }, - { key: "port", value: serverPort }, - { key: "ipv4hint", value: "127.0.0.1" }, - ], - }, - }); - - let buf = dnsPacket.encode({ - type: "response", - id: packet.id, - flags: dnsPacket.RECURSION_DESIRED, - questions: packet.questions, - answers, - }); - - res.setHeader("Content-Type", "application/dns-message"); - res.setHeader("Content-Length", buf.length); - res.writeHead(200); - res.write(buf); - res.end(""); - }); - return; - } else if (u.pathname === "/dns-cname-a") { - let rContent = createCNameARecord(); - res.setHeader("Content-Type", "application/dns-message"); - res.setHeader("Content-Length", rContent.length); - res.writeHead(200); - res.write(rContent); - res.end(""); - return; } else if (u.pathname === "/websocket") { res.setHeader("Upgrade", "websocket"); res.setHeader("Connection", "Upgrade"); @@ -1261,14 +627,6 @@ function handleRequest(req, res) { }); res.end(); return; - } else if (u.pathname === "/redirect_to_http") { - res.setHeader( - "Location", - `http://test.httpsrr.redirect.com:${u.query.port}/redirect_to_http?port=${u.query.port}` - ); - res.writeHead(307); - res.end(""); - return; } else if (u.pathname === "/103_response") { let link_val = req.headers["link-to-set"]; if (link_val) { @@ -1299,14 +657,6 @@ function handleRequest(req, res) { res.writeHead(200); res.end(""); return; - } else if (u.pathname === "/origin_header") { - let originHeader = req.headers.origin; - res.setHeader("Content-Length", originHeader.length); - res.setHeader("Content-Type", "text/plain"); - res.writeHead(200); - res.write(originHeader); - res.end(); - return; } res.setHeader("Content-Type", "text/html");