Bug 1645108 - Parse additional section of TRR response r=dragana,necko-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D87088
This commit is contained in:
Valentin Gosu 2020-09-14 21:41:44 +00:00
Родитель 3fd81e0284
Коммит 6051d814c2
5 изменённых файлов: 398 добавлений и 11 удалений

Просмотреть файл

@ -1176,8 +1176,11 @@ nsresult TRR::DohDecode(nsCString& aHost) {
uint16_t arRecords = get16bit(mResponse, 10);
LOG(("TRR Decode: %d additional resource records (%u bytes body)\n",
arRecords, mBodySize));
nsClassHashtable<nsCStringHashKey, DOHresp> additionalRecords;
while (arRecords) {
rv = PassQName(index);
nsAutoCString qname;
rv = GetQname(qname, index);
if (NS_FAILED(rv)) {
return rv;
}
@ -1185,24 +1188,73 @@ nsresult TRR::DohDecode(nsCString& aHost) {
if (mBodySize < (index + 8)) {
return NS_ERROR_ILLEGAL_VALUE;
}
uint16_t type = get16bit(mResponse, index);
index += 2; // type
uint16_t cls = get16bit(mResponse, index);
index += 2; // class
uint32_t ttl = get32bit(mResponse, index);
index += 4; // ttl
// 16 bit RDLENGTH
if (mBodySize < (index + 2)) {
return NS_ERROR_ILLEGAL_VALUE;
}
uint16_t RDLENGTH = get16bit(mResponse, index);
uint16_t rdlength = get16bit(mResponse, index);
index += 2;
if (mBodySize < (index + RDLENGTH)) {
if (mBodySize < (index + rdlength)) {
return NS_ERROR_ILLEGAL_VALUE;
}
index += RDLENGTH;
auto parseRecord = [&]() {
if (kDNS_CLASS_IN != cls) {
return;
}
auto& entry = additionalRecords.GetOrInsert(qname);
if (!entry) {
entry.reset(new DOHresp());
}
switch (type) {
case TRRTYPE_A:
if (rdlength != 4) {
LOG(("TRR bad length for A (%u)\n", rdlength));
return;
}
rv = entry->Add(ttl, mResponse, index, rdlength, mAllowRFC1918);
if (NS_FAILED(rv)) {
LOG(
("TRR:DohDecode failed: local IP addresses or unknown IP "
"family\n"));
return;
}
break;
case TRRTYPE_AAAA:
if (rdlength != 16) {
LOG(("TRR bad length for AAAA (%u)\n", rdlength));
return;
}
rv = entry->Add(ttl, mResponse, index, rdlength, mAllowRFC1918);
if (NS_FAILED(rv)) {
LOG(("TRR got unique/local IPv6 address!\n"));
return;
}
break;
default:
break;
}
};
parseRecord();
index += rdlength;
LOG(("done with additional rr now %u of %u\n", index, mBodySize));
arRecords--;
}
SaveAdditionalRecords(additionalRecords);
if (index != mBodySize) {
LOG(("DohDecode failed to parse entire response body, %u out of %u bytes\n",
index, mBodySize));
@ -1229,6 +1281,56 @@ nsresult TRR::DohDecode(nsCString& aHost) {
return NS_OK;
}
void TRR::SaveAdditionalRecords(
const nsClassHashtable<nsCStringHashKey, DOHresp>& aRecords) {
if (!mRec) {
return;
}
nsresult rv;
for (auto iter = aRecords.ConstIter(); !iter.Done(); iter.Next()) {
if (iter.Data() && iter.Data()->mAddresses.isEmpty()) {
// no point in adding empty records.
continue;
}
RefPtr<nsHostRecord> hostRecord;
rv = mHostResolver->GetHostRecord(
iter.Key(), EmptyCString(), nsIDNSService::RESOLVE_TYPE_DEFAULT,
mRec->flags, AF_UNSPEC, mRec->pb, mRec->originSuffix,
getter_AddRefs(hostRecord));
if (NS_FAILED(rv)) {
LOG(("Failed to get host record for additional record %s",
nsCString(iter.Key()).get()));
continue;
}
uint32_t ttl = AddrInfo::NO_TTL_DATA;
DOHaddr* item = nullptr;
nsTArray<NetAddr> addresses;
while ((item = static_cast<DOHaddr*>(iter.Data()->mAddresses.popFirst()))) {
addresses.AppendElement(item->mNet);
if (item->mTtl < ttl) {
// While the DNS packet might return individual TTLs for each address,
// we can only return one value in the AddrInfo class so pick the
// lowest number.
ttl = item->mTtl;
}
}
RefPtr<AddrInfo> ai(
new AddrInfo(iter.Key(), TRRTYPE_A, std::move(addresses), ttl));
// Since we're not actually calling NameLookup for this record, we need
// to set these fields to avoid assertions in CompleteLookup.
// This is quite hacky, and should be fixed.
hostRecord->mResolving++;
hostRecord->mEffectiveTRRMode = mRec->mEffectiveTRRMode;
RefPtr<AddrHostRecord> addrRec = do_QueryObject(hostRecord);
addrRec->mTrrStart = TimeStamp::Now();
addrRec->mTrrA = this; // Hack!
LOG(("Completing lookup for additional: %s", nsCString(iter.Key()).get()));
(void)mHostResolver->CompleteLookup(hostRecord, NS_OK, ai, mPB,
mOriginSuffix, AddrHostRecord::TRR_OK);
}
}
nsresult TRR::ParseSvcParam(unsigned int svcbIndex, uint16_t key,
SvcFieldValue& field, uint16_t length) {
switch (key) {

Просмотреть файл

@ -9,6 +9,7 @@
#include "mozilla/net/DNSByTypeRecord.h"
#include "mozilla/Assertions.h"
#include "nsClassHashtable.h"
#include "nsIChannel.h"
#include "nsIHttpPushListener.h"
#include "nsIInterfaceRequestor.h"
@ -155,6 +156,8 @@ class TRR : public Runnable,
nsresult FollowCname(nsIChannel* aChannel);
bool UseDefaultServer();
void SaveAdditionalRecords(
const nsClassHashtable<nsCStringHashKey, DOHresp>& aRecords);
nsresult CreateChannelHelper(nsIURI* aUri, nsIChannel** aResult);

Просмотреть файл

@ -147,6 +147,7 @@ class TRRDNSListener {
this.expectedAnswer,
`Checking result for ${this.name}`
);
inRecord.rewind(); // In case the caller also checks the addresses
if (this.delay !== undefined) {
Assert.greaterOrEqual(
@ -268,17 +269,18 @@ function trrQueryHandler(req, resp, url) {
function processRequest(req, resp, payload) {
let dnsQuery = global.dnsPacket.decode(payload);
let answers =
let response =
global.dns_query_answers[
`${dnsQuery.questions[0].name}/${dnsQuery.questions[0].type}`
] || [];
] || {};
let buf = global.dnsPacket.encode({
type: "response",
id: dnsQuery.id,
flags: global.dnsPacket.RECURSION_DESIRED,
questions: dnsQuery.questions,
answers,
answers: response.answers || [],
additionals: response.additionals || [],
});
resp.setHeader("Content-Length", buf.length);
@ -333,10 +335,11 @@ class TRRServer {
/// flush: false,
/// data: "1.2.3.4",
/// }]
async registerDoHAnswers(name, type, answers) {
let text = `global.dns_query_answers["${name}/${type}"] = ${JSON.stringify(
answers
)}`;
async registerDoHAnswers(name, type, answers, additionals) {
let text = `global.dns_query_answers["${name}/${type}"] = ${JSON.stringify({
answers,
additionals,
})}`;
return this.execute(text);
}
}

Просмотреть файл

@ -0,0 +1,276 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
Ci.nsIDNSService
);
trr_test_setup();
registerCleanupFunction(async () => {
trr_clear_prefs();
});
function makeChan(url) {
let chan = NetUtil.newChannel({
uri: url,
loadUsingSystemPrincipal: true,
}).QueryInterface(Ci.nsIHttpChannel);
return chan;
}
let processId;
function channelOpenPromise(chan) {
return new Promise(resolve => {
function finish(req, buffer) {
resolve([req, buffer]);
}
chan.asyncOpen(new ChannelListener(finish));
});
}
add_task(async function test_parse_additional_section() {
let trrServer = new TRRServer();
registerCleanupFunction(async () => trrServer.stop());
await trrServer.start();
dump(`port = ${trrServer.port}\n`);
let chan = makeChan(`https://localhost:${trrServer.port}/test?bla=some`);
let [req, resp] = await channelOpenPromise(chan);
equal(resp, "<h1> 404 Path not found: /test?bla=some</h1>");
dns.clearCache(true);
Services.prefs.setIntPref("network.trr.mode", 3);
Services.prefs.setCharPref(
"network.trr.uri",
`https://foo.example.com:${trrServer.port}/dns-query`
);
await trrServer.registerDoHAnswers(
"something.foo",
"A",
[
{
name: "something.foo",
ttl: 55,
type: "A",
flush: false,
data: "1.2.3.4",
},
],
[
{
name: "else.foo",
ttl: 55,
type: "A",
flush: false,
data: "2.3.4.5",
},
]
);
await new TRRDNSListener("something.foo", "1.2.3.4");
await new TRRDNSListener("else.foo", "2.3.4.5");
await trrServer.registerDoHAnswers(
"a.foo",
"A",
[
{
name: "a.foo",
ttl: 55,
type: "A",
flush: false,
data: "1.2.3.4",
},
],
[
{
name: "b.foo",
ttl: 55,
type: "A",
flush: false,
data: "2.3.4.5",
},
]
);
await trrServer.registerDoHAnswers("b.foo", "A", [
{
name: "b.foo",
ttl: 55,
type: "A",
flush: false,
data: "3.4.5.6",
},
]);
let req1 = new TRRDNSListener("a.foo", "1.2.3.4");
// A request for b.foo will be in progress by the time we parse the additional
// record. To keep things simple we don't end up saving the record, instead
// we wait for the in-progress request to complete.
// This check is also racy - if the response for a.foo completes before we make
// this request, we'll put the other IP in the cache. But that is very unlikely.
let req2 = new TRRDNSListener("b.foo", "3.4.5.6");
await Promise.all([req1, req2]);
// IPv6 additional
await trrServer.registerDoHAnswers(
"xyz.foo",
"A",
[
{
name: "xyz.foo",
ttl: 55,
type: "A",
flush: false,
data: "1.2.3.4",
},
],
[
{
name: "abc.foo",
ttl: 55,
type: "AAAA",
flush: false,
data: "::1:2:3:4",
},
]
);
await new TRRDNSListener("xyz.foo", "1.2.3.4");
await new TRRDNSListener("abc.foo", "::1:2:3:4");
// IPv6 additional
await trrServer.registerDoHAnswers(
"ipv6.foo",
"AAAA",
[
{
name: "ipv6.foo",
ttl: 55,
type: "AAAA",
flush: false,
data: "::abcd",
},
],
[
{
name: "def.foo",
ttl: 55,
type: "AAAA",
flush: false,
data: "::a:b:c:d",
},
]
);
await new TRRDNSListener("ipv6.foo", "::abcd");
await new TRRDNSListener("def.foo", "::a:b:c:d");
// IPv6 additional
await trrServer.registerDoHAnswers(
"ipv6b.foo",
"AAAA",
[
{
name: "ipv6b.foo",
ttl: 55,
type: "AAAA",
flush: false,
data: "::abcd",
},
],
[
{
name: "qqqq.foo",
ttl: 55,
type: "A",
flush: false,
data: "9.8.7.6",
},
]
);
await new TRRDNSListener("ipv6b.foo", "::abcd");
await new TRRDNSListener("qqqq.foo", "9.8.7.6");
// Multiple IPs and multiple additional records
await trrServer.registerDoHAnswers(
"multiple.foo",
"A",
[
{
name: "multiple.foo",
ttl: 55,
type: "A",
flush: false,
data: "9.9.9.9",
},
],
[
{
// Should be ignored, because it should be in the answer section
name: "multiple.foo",
ttl: 55,
type: "A",
flush: false,
data: "1.1.1.1",
},
{
// Is ignored, because it should be in the answer section
name: "multiple.foo",
ttl: 55,
type: "AAAA",
flush: false,
data: "::abcd",
},
{
name: "yuiop.foo",
ttl: 55,
type: "AAAA",
flush: false,
data: "::abcd",
},
{
name: "yuiop.foo",
ttl: 55,
type: "A",
flush: false,
data: "1.2.3.4",
},
]
);
let [inRequest, inRecord, inStatus] = await new TRRDNSListener(
"multiple.foo",
"9.9.9.9"
);
let IPs = [];
inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
inRecord.rewind();
while (inRecord.hasMore()) {
IPs.push(inRecord.getNextAddrAsString());
}
equal(IPs.length, 1);
equal(IPs[0], "9.9.9.9");
IPs = [];
[inRequest, inRecord, inStatus] = await new TRRDNSListener(
"yuiop.foo",
undefined,
false
);
inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
inRecord.rewind();
while (inRecord.hasMore()) {
IPs.push(inRecord.getNextAddrAsString());
}
equal(IPs.length, 2);
equal(IPs[0], "::abcd");
equal(IPs[1], "1.2.3.4");
await trrServer.stop();
});

Просмотреть файл

@ -451,5 +451,8 @@ skip-if = asan || tsan || os == 'win' || os =='android'
skip-if = asan || tsan || os == 'win' || os =='android'
[test_use_httpssvc.js]
skip-if = os == "android"
[test_trr_additional_section.js]
skip-if = os == "android"
[test_httpssvc_iphint.js]
skip-if = os == "android"