зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
3fd81e0284
Коммит
6051d814c2
|
@ -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"
|
||||
|
|
Загрузка…
Ссылка в новой задаче