summaryrefslogtreecommitdiffstats
path: root/netwerk/dns/TRR.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netwerk/dns/TRR.cpp965
1 files changed, 965 insertions, 0 deletions
diff --git a/netwerk/dns/TRR.cpp b/netwerk/dns/TRR.cpp
new file mode 100644
index 0000000000..dde39fd25e
--- /dev/null
+++ b/netwerk/dns/TRR.cpp
@@ -0,0 +1,965 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cin: */
+/* 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/. */
+
+#include "DNS.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsContentUtils.h"
+#include "nsHttpHandler.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIIOService.h"
+#include "nsIInputStream.h"
+#include "nsISupportsBase.h"
+#include "nsISupportsUtils.h"
+#include "nsITimedChannel.h"
+#include "nsIUploadChannel2.h"
+#include "nsIURIMutator.h"
+#include "nsNetUtil.h"
+#include "nsStringStream.h"
+#include "nsThreadUtils.h"
+#include "nsURLHelper.h"
+#include "TRR.h"
+#include "TRRService.h"
+#include "TRRServiceChannel.h"
+#include "TRRLoadInfo.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace net {
+
+#undef LOG
+#undef LOG_ENABLED
+extern mozilla::LazyLogModule gHostResolverLog;
+#define LOG(args) MOZ_LOG(gHostResolverLog, mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(mozilla::net::gHostResolverLog, mozilla::LogLevel::Debug)
+
+NS_IMPL_ISUPPORTS(TRR, nsIHttpPushListener, nsIInterfaceRequestor,
+ nsIStreamListener, nsIRunnable)
+
+NS_IMETHODIMP
+TRR::Notify(nsITimer* aTimer) {
+ if (aTimer == mTimeout) {
+ mTimeout = nullptr;
+ Cancel();
+ } else {
+ MOZ_CRASH("Unknown timer");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRR::Run() {
+ MOZ_ASSERT_IF(XRE_IsParentProcess() && gTRRService,
+ NS_IsMainThread() || gTRRService->IsOnTRRThread());
+ MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
+
+ if ((gTRRService == nullptr) || NS_FAILED(SendHTTPRequest())) {
+ RecordReason(nsHostRecord::TRR_SEND_FAILED);
+ FailData(NS_ERROR_FAILURE);
+ // The dtor will now be run
+ }
+ return NS_OK;
+}
+
+static void InitHttpHandler() {
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ios->GetProtocolHandler("http", getter_AddRefs(handler));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+}
+
+nsresult TRR::CreateChannelHelper(nsIURI* aUri, nsIChannel** aResult) {
+ *aResult = nullptr;
+
+ if (NS_IsMainThread() && !XRE_IsSocketProcess()) {
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewChannel(
+ aResult, aUri, nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // nsICookieJarSettings
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL, ios);
+ }
+
+ // Unfortunately, we can only initialize gHttpHandler on main thread.
+ if (!gHttpHandler) {
+ nsCOMPtr<nsIEventTarget> main = GetMainThreadEventTarget();
+ if (main) {
+ // Forward to the main thread synchronously.
+ SyncRunnable::DispatchToThread(
+ main, new SyncRunnable(NS_NewRunnableFunction(
+ "InitHttpHandler", []() { InitHttpHandler(); })));
+ }
+ }
+
+ if (!gHttpHandler) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<TRRLoadInfo> loadInfo =
+ new TRRLoadInfo(aUri, nsIContentPolicy::TYPE_OTHER);
+ return gHttpHandler->CreateTRRServiceChannel(aUri,
+ nullptr, // givenProxyInfo
+ 0, // proxyResolveFlags
+ nullptr, // proxyURI
+ loadInfo, // aLoadInfo
+ aResult);
+}
+
+nsresult TRR::SendHTTPRequest() {
+ // This is essentially the "run" method - created from nsHostResolver
+
+ if ((mType != TRRTYPE_A) && (mType != TRRTYPE_AAAA) &&
+ (mType != TRRTYPE_NS) && (mType != TRRTYPE_TXT) &&
+ (mType != TRRTYPE_HTTPSSVC)) {
+ // limit the calling interface because nsHostResolver has explicit slots for
+ // these types
+ return NS_ERROR_FAILURE;
+ }
+
+ if (((mType == TRRTYPE_A) || (mType == TRRTYPE_AAAA)) &&
+ mRec->mEffectiveTRRMode != nsIRequest::TRR_ONLY_MODE) {
+ // let NS resolves skip the blocklist check
+ // we also don't check the blocklist for TRR only requests
+ MOZ_ASSERT(mRec);
+
+ if (UseDefaultServer() &&
+ gTRRService->IsTemporarilyBlocked(mHost, mOriginSuffix, mPB, true)) {
+ if (mType == TRRTYPE_A) {
+ // count only blocklist for A records to avoid double counts
+ Telemetry::Accumulate(Telemetry::DNS_TRR_BLACKLISTED2,
+ TRRService::AutoDetectedKey(), true);
+ }
+
+ RecordReason(nsHostRecord::TRR_HOST_BLOCKED_TEMPORARY);
+ // not really an error but no TRR is issued
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (gTRRService->IsExcludedFromTRR(mHost)) {
+ RecordReason(nsHostRecord::TRR_EXCLUDED);
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (UseDefaultServer() && (mType == TRRTYPE_A)) {
+ Telemetry::Accumulate(Telemetry::DNS_TRR_BLACKLISTED2,
+ TRRService::AutoDetectedKey(), false);
+ }
+ }
+
+ bool useGet = StaticPrefs::network_trr_useGET();
+ nsAutoCString body;
+ nsCOMPtr<nsIURI> dnsURI;
+ bool disableECS = StaticPrefs::network_trr_disable_ECS();
+ nsresult rv;
+
+ LOG(("TRR::SendHTTPRequest resolve %s type %u\n", mHost.get(), mType));
+
+ if (useGet) {
+ nsAutoCString tmp;
+ rv = DNSPacket::EncodeRequest(tmp, mHost, mType, disableECS);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* For GET requests, the outgoing packet needs to be Base64url-encoded and
+ then appended to the end of the URI. */
+ rv = Base64URLEncode(tmp.Length(),
+ reinterpret_cast<const unsigned char*>(tmp.get()),
+ Base64URLEncodePaddingPolicy::Omit, body);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString uri;
+ if (UseDefaultServer()) {
+ gTRRService->GetURI(uri);
+ } else {
+ uri = mRec->mTrrServer;
+ }
+
+ rv = NS_NewURI(getter_AddRefs(dnsURI), uri);
+ if (NS_FAILED(rv)) {
+ LOG(("TRR:SendHTTPRequest: NewURI failed!\n"));
+ return rv;
+ }
+
+ nsAutoCString query;
+ rv = dnsURI->GetQuery(query);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (query.IsEmpty()) {
+ query.Assign("?dns="_ns);
+ } else {
+ query.Append("&dns="_ns);
+ }
+ query.Append(body);
+
+ rv = NS_MutateURI(dnsURI).SetQuery(query).Finalize(dnsURI);
+ LOG(("TRR::SendHTTPRequest GET dns=%s\n", body.get()));
+ } else {
+ rv = DNSPacket::EncodeRequest(body, mHost, mType, disableECS);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString uri;
+ if (UseDefaultServer()) {
+ gTRRService->GetURI(uri);
+ } else {
+ uri = mRec->mTrrServer;
+ }
+ rv = NS_NewURI(getter_AddRefs(dnsURI), uri);
+ }
+ if (NS_FAILED(rv)) {
+ LOG(("TRR:SendHTTPRequest: NewURI failed!\n"));
+ return rv;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = CreateChannelHelper(dnsURI, getter_AddRefs(channel));
+ if (NS_FAILED(rv) || !channel) {
+ LOG(("TRR:SendHTTPRequest: NewChannel failed!\n"));
+ return rv;
+ }
+
+ channel->SetLoadFlags(
+ nsIRequest::LOAD_ANONYMOUS | nsIRequest::INHIBIT_CACHING |
+ nsIRequest::LOAD_BYPASS_CACHE | nsIChannel::LOAD_BYPASS_URL_CLASSIFIER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = channel->SetNotificationCallbacks(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
+ if (!httpChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // This connection should not use TRR
+ rv = httpChannel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = httpChannel->SetRequestHeader("Accept"_ns, "application/dns-message"_ns,
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString cred;
+ if (UseDefaultServer()) {
+ gTRRService->GetCredentials(cred);
+ }
+ if (!cred.IsEmpty()) {
+ rv = httpChannel->SetRequestHeader("Authorization"_ns, cred, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(channel);
+ if (!internalChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // setting a small stream window means the h2 stack won't pipeline a window
+ // update with each HEADERS or reply to a DATA with a WINDOW UPDATE
+ rv = internalChannel->SetInitialRwin(127 * 1024);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = internalChannel->SetIsTRRServiceChannel(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (useGet) {
+ rv = httpChannel->SetRequestMethod("GET"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
+ if (!uploadChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ uint32_t streamLength = body.Length();
+ nsCOMPtr<nsIInputStream> uploadStream;
+ rv =
+ NS_NewCStringInputStream(getter_AddRefs(uploadStream), std::move(body));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = uploadChannel->ExplicitSetUploadStream(uploadStream,
+ "application/dns-message"_ns,
+ streamLength, "POST"_ns, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = SetupTRRServiceChannelInternal(httpChannel, useGet);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = httpChannel->AsyncOpen(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If the asyncOpen succeeded we can say that we actually attempted to
+ // use the TRR connection.
+ RefPtr<AddrHostRecord> addrRec = do_QueryObject(mRec);
+ if (addrRec) {
+ addrRec->mTRRUsed = true;
+ }
+
+ NS_NewTimerWithCallback(getter_AddRefs(mTimeout), this,
+ gTRRService->GetRequestTimeout(),
+ nsITimer::TYPE_ONE_SHOT);
+
+ mChannel = channel;
+ return NS_OK;
+}
+
+// static
+nsresult TRR::SetupTRRServiceChannelInternal(nsIHttpChannel* aChannel,
+ bool aUseGet) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = aChannel;
+ MOZ_ASSERT(httpChannel);
+
+ nsresult rv = NS_OK;
+ if (!aUseGet) {
+ rv =
+ httpChannel->SetRequestHeader("Cache-Control"_ns, "no-store"_ns, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Sanitize the request by removing the Accept-Language header so we minimize
+ // the amount of fingerprintable information we send to the server.
+ if (!StaticPrefs::network_trr_send_accept_language_headers()) {
+ rv = httpChannel->SetRequestHeader("Accept-Language"_ns, ""_ns, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Sanitize the request by removing the User-Agent
+ if (!StaticPrefs::network_trr_send_user_agent_headers()) {
+ rv = httpChannel->SetRequestHeader("User-Agent"_ns, ""_ns, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (StaticPrefs::network_trr_send_empty_accept_encoding_headers()) {
+ rv = httpChannel->SetEmptyRequestHeader("Accept-Encoding"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // set the *default* response content type
+ if (NS_FAILED(httpChannel->SetContentType("application/dns-message"_ns))) {
+ LOG(("TRR::SetupTRRServiceChannelInternal: couldn't set content-type!\n"));
+ }
+
+ nsCOMPtr<nsITimedChannel> timedChan(do_QueryInterface(httpChannel));
+ if (timedChan) {
+ timedChan->SetTimingEnabled(true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRR::GetInterface(const nsIID& iid, void** result) {
+ if (!iid.Equals(NS_GET_IID(nsIHttpPushListener))) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsCOMPtr<nsIHttpPushListener> copy(this);
+ *result = copy.forget().take();
+ return NS_OK;
+}
+
+nsresult TRR::DohDecodeQuery(const nsCString& query, nsCString& host,
+ enum TrrType& type) {
+ FallibleTArray<uint8_t> binary;
+ bool found_dns = false;
+ LOG(("TRR::DohDecodeQuery %s!\n", query.get()));
+
+ // extract "dns=" from the query string
+ nsAutoCString data;
+ for (const nsACString& token :
+ nsCCharSeparatedTokenizer(query, '&').ToRange()) {
+ nsDependentCSubstring dns = Substring(token, 0, 4);
+ nsAutoCString check(dns);
+ if (check.Equals("dns=")) {
+ nsDependentCSubstring q = Substring(token, 4, -1);
+ data = q;
+ found_dns = true;
+ break;
+ }
+ }
+ if (!found_dns) {
+ LOG(("TRR::DohDecodeQuery no dns= in pushed URI query string\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsresult rv =
+ Base64URLDecode(data, Base64URLDecodePaddingPolicy::Ignore, binary);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t avail = binary.Length();
+ if (avail < 12) {
+ return NS_ERROR_FAILURE;
+ }
+ // check the query bit and the opcode
+ if ((binary[2] & 0xf8) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+ uint32_t qdcount = (binary[4] << 8) + binary[5];
+ if (!qdcount) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t index = 12;
+ uint32_t length = 0;
+ host.Truncate();
+ do {
+ if (avail < (index + 1)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ length = binary[index];
+ if (length) {
+ if (host.Length()) {
+ host.Append(".");
+ }
+ if (avail < (index + 1 + length)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ host.Append((const char*)(&binary[0]) + index + 1, length);
+ }
+ index += 1 + length; // skip length byte + label
+ } while (length);
+
+ LOG(("TRR::DohDecodeQuery host %s\n", host.get()));
+
+ if (avail < (index + 2)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ uint16_t i16 = 0;
+ i16 += binary[index] << 8;
+ i16 += binary[index + 1];
+ type = (enum TrrType)i16;
+
+ LOG(("TRR::DohDecodeQuery type %d\n", (int)type));
+
+ return NS_OK;
+}
+
+nsresult TRR::ReceivePush(nsIHttpChannel* pushed, nsHostRecord* pushedRec) {
+ if (!mHostResolver) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ LOG(("TRR::ReceivePush: PUSH incoming!\n"));
+
+ nsCOMPtr<nsIURI> uri;
+ pushed->GetURI(getter_AddRefs(uri));
+ nsAutoCString query;
+ if (uri) {
+ uri->GetQuery(query);
+ }
+
+ PRNetAddr tempAddr;
+ if (NS_FAILED(DohDecodeQuery(query, mHost, mType)) ||
+ (PR_StringToNetAddr(mHost.get(), &tempAddr) == PR_SUCCESS)) { // literal
+ LOG(("TRR::ReceivePush failed to decode %s\n", mHost.get()));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if ((mType != TRRTYPE_A) && (mType != TRRTYPE_AAAA) &&
+ (mType != TRRTYPE_TXT) && (mType != TRRTYPE_HTTPSSVC)) {
+ LOG(("TRR::ReceivePush unknown type %d\n", mType));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (gTRRService->IsExcludedFromTRR(mHost)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t type = nsIDNSService::RESOLVE_TYPE_DEFAULT;
+ if (mType == TRRTYPE_TXT) {
+ type = nsIDNSService::RESOLVE_TYPE_TXT;
+ } else if (mType == TRRTYPE_HTTPSSVC) {
+ type = nsIDNSService::RESOLVE_TYPE_HTTPSSVC;
+ }
+
+ RefPtr<nsHostRecord> hostRecord;
+ nsresult rv;
+ rv = mHostResolver->GetHostRecord(
+ mHost, ""_ns, type, pushedRec->flags, pushedRec->af, pushedRec->pb,
+ pushedRec->originSuffix, getter_AddRefs(hostRecord));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Since we don't ever call nsHostResolver::NameLookup for this record,
+ // we need to copy the trr mode from the previous record
+ if (hostRecord->mEffectiveTRRMode == nsIRequest::TRR_DEFAULT_MODE) {
+ hostRecord->mEffectiveTRRMode = pushedRec->mEffectiveTRRMode;
+ }
+
+ rv = mHostResolver->TrrLookup_unlocked(hostRecord, this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = pushed->AsyncOpen(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // OK!
+ mChannel = pushed;
+ mRec.swap(hostRecord);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRR::OnPush(nsIHttpChannel* associated, nsIHttpChannel* pushed) {
+ LOG(("TRR::OnPush entry\n"));
+ MOZ_ASSERT(associated == mChannel);
+ if (!mRec) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!UseDefaultServer()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<TRR> trr = new TRR(mHostResolver, mPB);
+ return trr->ReceivePush(pushed, mRec);
+}
+
+NS_IMETHODIMP
+TRR::OnStartRequest(nsIRequest* aRequest) {
+ LOG(("TRR::OnStartRequest %p %s %d\n", this, mHost.get(), mType));
+
+ nsresult status = NS_OK;
+ aRequest->GetStatus(&status);
+
+ if (NS_FAILED(status)) {
+ if (NS_IsOffline()) {
+ RecordReason(nsHostRecord::TRR_IS_OFFLINE);
+ }
+
+ switch (status) {
+ case NS_ERROR_UNKNOWN_HOST:
+ RecordReason(nsHostRecord::TRR_CHANNEL_DNS_FAIL);
+ break;
+ case NS_ERROR_OFFLINE:
+ RecordReason(nsHostRecord::TRR_IS_OFFLINE);
+ break;
+ case NS_ERROR_NET_RESET:
+ RecordReason(nsHostRecord::TRR_NET_RESET);
+ break;
+ case NS_ERROR_NET_TIMEOUT:
+ RecordReason(nsHostRecord::TRR_NET_TIMEOUT);
+ break;
+ case NS_ERROR_PROXY_CONNECTION_REFUSED:
+ RecordReason(nsHostRecord::TRR_NET_REFUSED);
+ break;
+ case NS_ERROR_NET_INTERRUPT:
+ RecordReason(nsHostRecord::TRR_NET_INTERRUPT);
+ break;
+ case NS_ERROR_NET_INADEQUATE_SECURITY:
+ RecordReason(nsHostRecord::TRR_NET_INADEQ_SEQURITY);
+ break;
+ default:
+ RecordReason(nsHostRecord::TRR_UNKNOWN_CHANNEL_FAILURE);
+ }
+ }
+
+ 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;
+ }
+ RefPtr<AddrInfo> ai(new AddrInfo(iter.Key(), TRRTYPE_A,
+ std::move(iter.Data()->mAddresses),
+ iter.Data()->mTtl));
+ mHostResolver->MaybeRenewHostRecord(hostRecord);
+
+ // 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();
+ LOG(("Completing lookup for additional: %s", nsCString(iter.Key()).get()));
+ (void)mHostResolver->CompleteLookup(hostRecord, NS_OK, ai, mPB,
+ mOriginSuffix, AddrHostRecord::TRR_OK);
+ }
+}
+
+void TRR::StoreIPHintAsDNSRecord(const struct SVCB& aSVCBRecord) {
+ LOG(("TRR::StoreIPHintAsDNSRecord [%p] [%s]", this,
+ aSVCBRecord.mSvcDomainName.get()));
+ CopyableTArray<NetAddr> addresses;
+ aSVCBRecord.GetIPHints(addresses);
+ if (addresses.IsEmpty()) {
+ return;
+ }
+
+ RefPtr<nsHostRecord> hostRecord;
+ nsresult rv = mHostResolver->GetHostRecord(
+ aSVCBRecord.mSvcDomainName, EmptyCString(),
+ nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ mRec->flags | nsHostResolver::RES_IP_HINT, AF_UNSPEC, mRec->pb,
+ mRec->originSuffix, getter_AddRefs(hostRecord));
+ if (NS_FAILED(rv)) {
+ LOG(("Failed to get host record"));
+ return;
+ }
+
+ mHostResolver->MaybeRenewHostRecord(hostRecord);
+
+ uint32_t ttl = AddrInfo::NO_TTL_DATA;
+ RefPtr<AddrInfo> ai(new AddrInfo(aSVCBRecord.mSvcDomainName, 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();
+
+ (void)mHostResolver->CompleteLookup(hostRecord, NS_OK, ai, mPB, mOriginSuffix,
+ AddrHostRecord::TRR_OK);
+}
+
+nsresult TRR::ReturnData(nsIChannel* aChannel) {
+ if (mType != TRRTYPE_TXT && mType != TRRTYPE_HTTPSSVC) {
+ // create and populate an AddrInfo instance to pass on
+ RefPtr<AddrInfo> ai(
+ new AddrInfo(mHost, mType, nsTArray<NetAddr>(), mDNS.mTtl));
+ auto builder = ai->Build();
+ builder.SetAddresses(std::move(mDNS.mAddresses));
+ builder.SetCanonicalHostname(mCname);
+
+ // Set timings.
+ nsCOMPtr<nsITimedChannel> timedChan = do_QueryInterface(aChannel);
+ if (timedChan) {
+ TimeStamp asyncOpen, start, end;
+ if (NS_SUCCEEDED(timedChan->GetAsyncOpen(&asyncOpen)) &&
+ !asyncOpen.IsNull()) {
+ builder.SetTrrFetchDuration(
+ (TimeStamp::Now() - asyncOpen).ToMilliseconds());
+ }
+ if (NS_SUCCEEDED(timedChan->GetRequestStart(&start)) &&
+ NS_SUCCEEDED(timedChan->GetResponseEnd(&end)) && !start.IsNull() &&
+ !end.IsNull()) {
+ builder.SetTrrFetchDurationNetworkOnly((end - start).ToMilliseconds());
+ }
+ }
+ ai = builder.Finish();
+
+ if (!mHostResolver) {
+ return NS_ERROR_FAILURE;
+ }
+ (void)mHostResolver->CompleteLookup(mRec, NS_OK, ai, mPB, mOriginSuffix,
+ mTRRSkippedReason);
+ mHostResolver = nullptr;
+ mRec = nullptr;
+ } else {
+ (void)mHostResolver->CompleteLookupByType(mRec, NS_OK, mResult, mTTL, mPB);
+ }
+ return NS_OK;
+}
+
+nsresult TRR::FailData(nsresult error) {
+ if (!mHostResolver) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we didn't record a reason until now, record a default one.
+ RecordReason(nsHostRecord::TRR_FAILED);
+
+ if (mType == TRRTYPE_TXT || mType == TRRTYPE_HTTPSSVC) {
+ TypeRecordResultType empty(Nothing{});
+ (void)mHostResolver->CompleteLookupByType(mRec, error, empty, 0, mPB);
+ } else {
+ // create and populate an TRR AddrInfo instance to pass on to signal that
+ // this comes from TRR
+ nsTArray<NetAddr> noAddresses;
+ RefPtr<AddrInfo> ai = new AddrInfo(mHost, mType, std::move(noAddresses));
+
+ (void)mHostResolver->CompleteLookup(mRec, error, ai, mPB, mOriginSuffix,
+ mTRRSkippedReason);
+ }
+
+ mHostResolver = nullptr;
+ mRec = nullptr;
+ return NS_OK;
+}
+
+nsresult TRR::FollowCname(nsIChannel* aChannel) {
+ nsresult rv = NS_OK;
+ nsAutoCString cname;
+ while (NS_SUCCEEDED(rv) && mDNS.mAddresses.IsEmpty() && !mCname.IsEmpty() &&
+ mCnameLoop > 0) {
+ mCnameLoop--;
+ LOG(("TRR::On200Response CNAME %s => %s (%u)\n", mHost.get(), mCname.get(),
+ mCnameLoop));
+ cname = mCname;
+ mCname.Truncate();
+
+ LOG(("TRR: check for CNAME record for %s within previous response\n",
+ cname.get()));
+ nsClassHashtable<nsCStringHashKey, DOHresp> additionalRecords;
+ rv = mPacket.Decode(
+ cname, mType, mCname, StaticPrefs::network_trr_allow_rfc1918(),
+ mTRRSkippedReason, mDNS, mResult, additionalRecords, mTTL);
+ if (NS_FAILED(rv)) {
+ LOG(("TRR::On200Response DohDecode %x\n", (int)rv));
+ }
+ }
+
+ // restore mCname as DohDecode() change it
+ mCname = cname;
+ if (NS_SUCCEEDED(rv) && !mDNS.mAddresses.IsEmpty()) {
+ ReturnData(aChannel);
+ return NS_OK;
+ }
+
+ if (!mCnameLoop) {
+ LOG(("TRR::On200Response CNAME loop, eject!\n"));
+ return NS_ERROR_REDIRECT_LOOP;
+ }
+
+ LOG(("TRR::On200Response CNAME %s => %s (%u)\n", mHost.get(), mCname.get(),
+ mCnameLoop));
+ RefPtr<TRR> trr =
+ new TRR(mHostResolver, mRec, mCname, mType, mCnameLoop, mPB);
+ if (!gTRRService) {
+ return NS_ERROR_FAILURE;
+ }
+ return gTRRService->DispatchTRRRequest(trr);
+}
+
+nsresult TRR::On200Response(nsIChannel* aChannel) {
+ // decode body and create an AddrInfo struct for the response
+ nsClassHashtable<nsCStringHashKey, DOHresp> additionalRecords;
+ nsresult rv = mPacket.Decode(
+ mHost, mType, mCname, StaticPrefs::network_trr_allow_rfc1918(),
+ mTRRSkippedReason, mDNS, mResult, additionalRecords, mTTL);
+
+ if (NS_FAILED(rv)) {
+ LOG(("TRR::On200Response DohDecode %x\n", (int)rv));
+ RecordReason(nsHostRecord::TRR_DECODE_FAILED);
+ return rv;
+ }
+ SaveAdditionalRecords(additionalRecords);
+
+ if (mResult.is<TypeRecordHTTPSSVC>()) {
+ auto& results = mResult.as<TypeRecordHTTPSSVC>();
+ for (const auto& rec : results) {
+ StoreIPHintAsDNSRecord(rec);
+ }
+ }
+
+ if (!mDNS.mAddresses.IsEmpty() || mType == TRRTYPE_TXT || mCname.IsEmpty()) {
+ // pass back the response data
+ ReturnData(aChannel);
+ return NS_OK;
+ }
+
+ LOG(("TRR::On200Response trying CNAME %s", mCname.get()));
+ return FollowCname(aChannel);
+}
+
+static void RecordProcessingTime(nsIChannel* aChannel) {
+ // This method records the time it took from the last received byte of the
+ // DoH response until we've notified the consumer with a host record.
+ nsCOMPtr<nsITimedChannel> timedChan = do_QueryInterface(aChannel);
+ if (!timedChan) {
+ return;
+ }
+ TimeStamp end;
+ if (NS_FAILED(timedChan->GetResponseEnd(&end))) {
+ return;
+ }
+
+ if (end.IsNull()) {
+ return;
+ }
+
+ Telemetry::AccumulateTimeDelta(Telemetry::DNS_TRR_PROCESSING_TIME, end);
+
+ LOG(("Processing DoH response took %f ms",
+ (TimeStamp::Now() - end).ToMilliseconds()));
+}
+
+NS_IMETHODIMP
+TRR::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ // The dtor will be run after the function returns
+ LOG(("TRR:OnStopRequest %p %s %d failed=%d code=%X\n", this, mHost.get(),
+ mType, mFailed, (unsigned int)aStatusCode));
+ nsCOMPtr<nsIChannel> channel;
+ channel.swap(mChannel);
+
+ {
+ // Cancel the timer since we don't need it anymore.
+ nsCOMPtr<nsITimer> timer;
+ mTimeout.swap(timer);
+ if (timer) {
+ timer->Cancel();
+ }
+ }
+
+ if (UseDefaultServer()) {
+ // Bad content is still considered "okay" if the HTTP response is okay
+ gTRRService->TRRIsOkay(NS_SUCCEEDED(aStatusCode) ? TRRService::OKAY_NORMAL
+ : TRRService::OKAY_BAD);
+ }
+
+ nsresult rv = NS_OK;
+ // if status was "fine", parse the response and pass on the answer
+ if (!mFailed && NS_SUCCEEDED(aStatusCode)) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+ if (!httpChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsAutoCString contentType;
+ httpChannel->GetContentType(contentType);
+ if (contentType.Length() &&
+ !contentType.LowerCaseEqualsLiteral("application/dns-message")) {
+ LOG(("TRR:OnStopRequest %p %s %d wrong content type %s\n", this,
+ mHost.get(), mType, contentType.get()));
+ FailData(NS_ERROR_UNEXPECTED);
+ return NS_OK;
+ }
+
+ uint32_t httpStatus;
+ rv = httpChannel->GetResponseStatus(&httpStatus);
+ if (NS_SUCCEEDED(rv) && httpStatus == 200) {
+ rv = On200Response(channel);
+ if (NS_SUCCEEDED(rv) && UseDefaultServer()) {
+ RecordReason(nsHostRecord::TRR_OK);
+ RecordProcessingTime(channel);
+ return rv;
+ }
+ } else {
+ RecordReason(nsHostRecord::TRR_SERVER_RESPONSE_ERR);
+ LOG(("TRR:OnStopRequest:%d %p rv %x httpStatus %d\n", __LINE__, this,
+ (int)rv, httpStatus));
+ }
+ }
+
+ LOG(("TRR:OnStopRequest %p status %x mFailed %d\n", this, (int)aStatusCode,
+ mFailed));
+ FailData(NS_SUCCEEDED(rv) ? NS_ERROR_UNKNOWN_HOST : rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRR::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream,
+ uint64_t aOffset, const uint32_t aCount) {
+ LOG(("TRR:OnDataAvailable %p %s %d failed=%d aCount=%u\n", this, mHost.get(),
+ mType, mFailed, (unsigned int)aCount));
+ // receive DNS response into the local buffer
+ if (mFailed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv =
+ mPacket.OnDataAvailable(aRequest, aInputStream, aOffset, aCount);
+ if (NS_FAILED(rv)) {
+ LOG(("TRR::OnDataAvailable:%d fail\n", __LINE__));
+ mFailed = true;
+ return rv;
+ }
+ return NS_OK;
+}
+
+class ProxyCancel : public Runnable {
+ public:
+ explicit ProxyCancel(TRR* aTRR) : Runnable("proxyTrrCancel"), mTRR(aTRR) {}
+
+ NS_IMETHOD Run() override {
+ mTRR->Cancel();
+ mTRR = nullptr;
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<TRR> mTRR;
+};
+
+void TRR::Cancel() {
+ RefPtr<TRRServiceChannel> trrServiceChannel = do_QueryObject(mChannel);
+ if (trrServiceChannel && !XRE_IsSocketProcess()) {
+ if (gTRRService) {
+ nsCOMPtr<nsIThread> thread = gTRRService->TRRThread();
+ if (thread && !thread->IsOnCurrentThread()) {
+ nsCOMPtr<nsIRunnable> r = new ProxyCancel(this);
+ thread->Dispatch(r.forget());
+ return;
+ }
+ }
+ } else {
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(new ProxyCancel(this));
+ return;
+ }
+ }
+
+ if (mChannel) {
+ RecordReason(nsHostRecord::TRR_TIMEOUT);
+ LOG(("TRR: %p canceling Channel %p %s %d\n", this, mChannel.get(),
+ mHost.get(), mType));
+ mChannel->Cancel(NS_ERROR_ABORT);
+ if (UseDefaultServer()) {
+ gTRRService->TRRIsOkay(TRRService::OKAY_TIMEOUT);
+ }
+ }
+}
+
+bool TRR::UseDefaultServer() { return !mRec || mRec->mTrrServer.IsEmpty(); }
+
+#undef LOG
+
+// namespace
+} // namespace net
+} // namespace mozilla