mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
acc81306ba
Currently, we are checking Enabled() without passing the request mode in IsDomainBlocked. This isn't the right place to do this, it's cleaner if IsDomainBlocked trusts the caller to have checked Enabled already. We should call Enabled, with the request mode, in TRR::MaybeBlockDomain() and return early if it's false. Differential Revision: https://phabricator.services.mozilla.com/D109240
1026 lines
31 KiB
C++
1026 lines
31 KiB
C++
/* -*- 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 "DNSUtils.h"
|
|
#include "nsCharSeparatedTokenizer.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsHttpHandler.h"
|
|
#include "nsHostResolver.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/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)
|
|
|
|
// when firing off a normal A or AAAA query
|
|
TRR::TRR(AHostResolver* aResolver, nsHostRecord* aRec, enum TrrType aType)
|
|
: mozilla::Runnable("TRR"),
|
|
mRec(aRec),
|
|
mHostResolver(aResolver),
|
|
mType(aType),
|
|
mOriginSuffix(aRec->originSuffix) {
|
|
mHost = aRec->host;
|
|
mPB = aRec->pb;
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess(),
|
|
"TRR must be in parent or socket process");
|
|
}
|
|
|
|
// when following CNAMEs
|
|
TRR::TRR(AHostResolver* aResolver, nsHostRecord* aRec, nsCString& aHost,
|
|
enum TrrType& aType, unsigned int aLoopCount, bool aPB)
|
|
: mozilla::Runnable("TRR"),
|
|
mHost(aHost),
|
|
mRec(aRec),
|
|
mHostResolver(aResolver),
|
|
mType(aType),
|
|
mPB(aPB),
|
|
mCnameLoop(aLoopCount),
|
|
mOriginSuffix(aRec ? aRec->originSuffix : ""_ns) {
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess(),
|
|
"TRR must be in parent or socket process");
|
|
}
|
|
|
|
// used on push
|
|
TRR::TRR(AHostResolver* aResolver, bool aPB)
|
|
: mozilla::Runnable("TRR"),
|
|
mHostResolver(aResolver),
|
|
mType(TRRTYPE_A),
|
|
mPB(aPB) {
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess(),
|
|
"TRR must be in parent or socket process");
|
|
}
|
|
|
|
// to verify a domain
|
|
TRR::TRR(AHostResolver* aResolver, nsACString& aHost, enum TrrType aType,
|
|
const nsACString& aOriginSuffix, bool aPB)
|
|
: mozilla::Runnable("TRR"),
|
|
mHost(aHost),
|
|
mRec(nullptr),
|
|
mHostResolver(aResolver),
|
|
mType(aType),
|
|
mPB(aPB),
|
|
mOriginSuffix(aOriginSuffix) {
|
|
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess(),
|
|
"TRR must be in parent or socket process");
|
|
}
|
|
|
|
void TRR::HandleTimeout() {
|
|
mTimeout = nullptr;
|
|
RecordReason(TRRSkippedReason::TRR_TIMEOUT);
|
|
Cancel(NS_ERROR_NET_TIMEOUT_EXTERNAL);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
TRR::Notify(nsITimer* aTimer) {
|
|
if (aTimer == mTimeout) {
|
|
HandleTimeout();
|
|
} 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(TRRSkippedReason::TRR_SEND_FAILED);
|
|
FailData(NS_ERROR_FAILURE);
|
|
// The dtor will now be run
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
DNSPacket* TRR::GetOrCreateDNSPacket() {
|
|
if (!mPacket) {
|
|
mPacket = MakeUnique<DNSPacket>();
|
|
}
|
|
|
|
return mPacket.get();
|
|
}
|
|
|
|
nsresult TRR::CreateQueryURI(nsIURI** aOutURI) {
|
|
nsAutoCString uri;
|
|
nsCOMPtr<nsIURI> dnsURI;
|
|
if (UseDefaultServer()) {
|
|
gTRRService->GetURI(uri);
|
|
} else {
|
|
uri = mRec->mTrrServer;
|
|
}
|
|
|
|
nsresult rv = NS_NewURI(getter_AddRefs(dnsURI), uri);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
dnsURI.forget(aOutURI);
|
|
return NS_OK;
|
|
}
|
|
|
|
bool TRR::MaybeBlockRequest() {
|
|
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 TRRService isn't enabled anymore for the req, don't do TRR.
|
|
if (!gTRRService->Enabled(mRec->mEffectiveTRRMode)) {
|
|
RecordReason(TRRSkippedReason::TRR_MODE_NOT_ENABLED);
|
|
return true;
|
|
}
|
|
|
|
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_BLACKLISTED3,
|
|
TRRService::ProviderKey(), true);
|
|
}
|
|
|
|
RecordReason(TRRSkippedReason::TRR_HOST_BLOCKED_TEMPORARY);
|
|
// not really an error but no TRR is issued
|
|
return true;
|
|
}
|
|
|
|
if (gTRRService->IsExcludedFromTRR(mHost)) {
|
|
RecordReason(TRRSkippedReason::TRR_EXCLUDED);
|
|
return true;
|
|
}
|
|
|
|
if (UseDefaultServer() && (mType == TRRTYPE_A)) {
|
|
Telemetry::Accumulate(Telemetry::DNS_TRR_BLACKLISTED3,
|
|
TRRService::ProviderKey(), false);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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 (MaybeBlockRequest()) {
|
|
return NS_ERROR_UNKNOWN_HOST;
|
|
}
|
|
|
|
LOG(("TRR::SendHTTPRequest resolve %s type %u\n", mHost.get(), mType));
|
|
|
|
nsAutoCString body;
|
|
bool disableECS = StaticPrefs::network_trr_disable_ECS();
|
|
nsresult rv =
|
|
GetOrCreateDNSPacket()->EncodeRequest(body, mHost, mType, disableECS);
|
|
if (NS_FAILED(rv)) {
|
|
HandleEncodeError(rv);
|
|
return rv;
|
|
}
|
|
|
|
bool useGet = StaticPrefs::network_trr_useGET();
|
|
nsCOMPtr<nsIURI> dnsURI;
|
|
rv = CreateQueryURI(getter_AddRefs(dnsURI));
|
|
if (NS_FAILED(rv)) {
|
|
LOG(("TRR:SendHTTPRequest: NewURI failed!\n"));
|
|
return rv;
|
|
}
|
|
|
|
if (useGet) {
|
|
/* For GET requests, the outgoing packet needs to be Base64url-encoded and
|
|
then appended to the end of the URI. */
|
|
nsAutoCString encoded;
|
|
rv = Base64URLEncode(body.Length(),
|
|
reinterpret_cast<const unsigned char*>(body.get()),
|
|
Base64URLEncodePaddingPolicy::Omit, encoded);
|
|
NS_ENSURE_SUCCESS(rv, 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(encoded);
|
|
|
|
rv = NS_MutateURI(dnsURI).SetQuery(query).Finalize(dnsURI);
|
|
LOG(("TRR::SendHTTPRequest GET dns=%s\n", body.get()));
|
|
}
|
|
|
|
nsCOMPtr<nsIChannel> channel;
|
|
rv = DNSUtils::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);
|
|
|
|
nsCString contentType(ContentType());
|
|
rv = httpChannel->SetRequestHeader("Accept"_ns, contentType, 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, contentType,
|
|
streamLength, "POST"_ns, false);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
rv = SetupTRRServiceChannelInternal(httpChannel, useGet, contentType);
|
|
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->mResolverType = ResolverType();
|
|
}
|
|
|
|
NS_NewTimerWithCallback(
|
|
getter_AddRefs(mTimeout), this,
|
|
mTimeoutMs ? mTimeoutMs : gTRRService->GetRequestTimeout(),
|
|
nsITimer::TYPE_ONE_SHOT);
|
|
|
|
mChannel = channel;
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult TRR::SetupTRRServiceChannelInternal(nsIHttpChannel* aChannel,
|
|
bool aUseGet,
|
|
const nsACString& aContentType) {
|
|
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(aContentType))) {
|
|
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(TRRSkippedReason::TRR_IS_OFFLINE);
|
|
}
|
|
|
|
switch (status) {
|
|
case NS_ERROR_UNKNOWN_HOST:
|
|
RecordReason(TRRSkippedReason::TRR_CHANNEL_DNS_FAIL);
|
|
break;
|
|
case NS_ERROR_OFFLINE:
|
|
RecordReason(TRRSkippedReason::TRR_IS_OFFLINE);
|
|
break;
|
|
case NS_ERROR_NET_RESET:
|
|
RecordReason(TRRSkippedReason::TRR_NET_RESET);
|
|
break;
|
|
case NS_ERROR_NET_TIMEOUT:
|
|
case NS_ERROR_NET_TIMEOUT_EXTERNAL:
|
|
RecordReason(TRRSkippedReason::TRR_NET_TIMEOUT);
|
|
break;
|
|
case NS_ERROR_PROXY_CONNECTION_REFUSED:
|
|
RecordReason(TRRSkippedReason::TRR_NET_REFUSED);
|
|
break;
|
|
case NS_ERROR_NET_INTERRUPT:
|
|
RecordReason(TRRSkippedReason::TRR_NET_INTERRUPT);
|
|
break;
|
|
case NS_ERROR_NET_INADEQUATE_SECURITY:
|
|
RecordReason(TRRSkippedReason::TRR_NET_INADEQ_SEQURITY);
|
|
break;
|
|
default:
|
|
RecordReason(TRRSkippedReason::TRR_UNKNOWN_CHANNEL_FAILURE);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void TRR::SaveAdditionalRecords(
|
|
const nsClassHashtable<nsCStringHashKey, DOHresp>& aRecords) {
|
|
if (!mRec) {
|
|
return;
|
|
}
|
|
nsresult rv;
|
|
for (const auto& recordEntry : aRecords) {
|
|
if (recordEntry.GetData() && recordEntry.GetData()->mAddresses.IsEmpty()) {
|
|
// no point in adding empty records.
|
|
continue;
|
|
}
|
|
RefPtr<nsHostRecord> hostRecord;
|
|
rv = mHostResolver->GetHostRecord(
|
|
recordEntry.GetKey(), 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(recordEntry.GetKey()).get()));
|
|
continue;
|
|
}
|
|
RefPtr<AddrInfo> ai(
|
|
new AddrInfo(recordEntry.GetKey(), ResolverType(), TRRTYPE_A,
|
|
std::move(recordEntry.GetData()->mAddresses),
|
|
recordEntry.GetData()->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(recordEntry.GetKey()).get()));
|
|
(void)mHostResolver->CompleteLookup(hostRecord, NS_OK, ai, mPB,
|
|
mOriginSuffix, TRRSkippedReason::TRR_OK,
|
|
this);
|
|
}
|
|
}
|
|
|
|
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 | nsIDNSService::RESOLVE_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, ResolverType(),
|
|
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,
|
|
TRRSkippedReason::TRR_OK, this);
|
|
}
|
|
|
|
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, ResolverType(), 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, this);
|
|
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(TRRSkippedReason::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, ResolverType(), mType, std::move(noAddresses));
|
|
|
|
(void)mHostResolver->CompleteLookup(mRec, error, ai, mPB, mOriginSuffix,
|
|
mTRRSkippedReason, this);
|
|
}
|
|
|
|
mHostResolver = nullptr;
|
|
mRec = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
void TRR::HandleDecodeError(nsresult aStatusCode) {
|
|
auto rcode = mPacket->GetRCode();
|
|
if (rcode.isOk() && rcode.unwrap() != 0) {
|
|
if (rcode.unwrap() == 0x03) {
|
|
RecordReason(TRRSkippedReason::TRR_NXDOMAIN);
|
|
} else {
|
|
RecordReason(TRRSkippedReason::TRR_RCODE_FAIL);
|
|
}
|
|
} else if (aStatusCode == NS_ERROR_UNKNOWN_HOST ||
|
|
aStatusCode == NS_ERROR_DEFINITIVE_UNKNOWN_HOST) {
|
|
RecordReason(TRRSkippedReason::TRR_NO_ANSWERS);
|
|
} else {
|
|
RecordReason(TRRSkippedReason::TRR_DECODE_FAILED);
|
|
}
|
|
}
|
|
|
|
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 = GetOrCreateDNSPacket()->Decode(
|
|
cname, mType, mCname, StaticPrefs::network_trr_allow_rfc1918(), mDNS,
|
|
mResult, additionalRecords, mTTL);
|
|
if (NS_FAILED(rv)) {
|
|
LOG(("TRR::FollowCname DohDecode %x\n", (int)rv));
|
|
HandleDecodeError(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 =
|
|
ResolverType() == DNSResolverType::ODoH
|
|
? new ODoH(mHostResolver, mRec, mCname, mType, mCnameLoop, mPB)
|
|
: 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 = GetOrCreateDNSPacket()->Decode(
|
|
mHost, mType, mCname, StaticPrefs::network_trr_allow_rfc1918(), mDNS,
|
|
mResult, additionalRecords, mTTL);
|
|
if (NS_FAILED(rv)) {
|
|
LOG(("TRR::On200Response DohDecode %x\n", (int)rv));
|
|
HandleDecodeError(rv);
|
|
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);
|
|
}
|
|
|
|
void TRR::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()));
|
|
}
|
|
|
|
void TRR::ReportStatus(nsresult aStatusCode) {
|
|
// If the TRR was cancelled by nsHostResolver, then we don't need to report
|
|
// it as failed; otherwise it can cause the confirmation to fail.
|
|
if (UseDefaultServer() && aStatusCode != NS_ERROR_ABORT) {
|
|
// Bad content is still considered "okay" if the HTTP response is okay
|
|
gTRRService->TRRIsOkay(aStatusCode);
|
|
}
|
|
}
|
|
|
|
static void RecordHttpVersion(nsIHttpChannel* aHttpChannel) {
|
|
nsAutoCString protocol;
|
|
nsresult rv = aHttpChannel->GetProtocolVersion(protocol);
|
|
if (NS_FAILED(rv)) {
|
|
LOG(("Failed to get protocol version, rv=%x", (int)rv));
|
|
return;
|
|
}
|
|
|
|
if (protocol.LowerCaseEqualsLiteral("h2")) {
|
|
Telemetry::AccumulateCategorical(
|
|
Telemetry::LABELS_DNS_TRR_HTTP_VERSION::h_2);
|
|
} else if (protocol.LowerCaseEqualsLiteral("h3")) {
|
|
Telemetry::AccumulateCategorical(
|
|
Telemetry::LABELS_DNS_TRR_HTTP_VERSION::h_3);
|
|
} else {
|
|
Telemetry::AccumulateCategorical(
|
|
Telemetry::LABELS_DNS_TRR_HTTP_VERSION::h_1);
|
|
}
|
|
|
|
LOG(("DoH endpoint responded using HTTP version: %s", protocol.get()));
|
|
}
|
|
|
|
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);
|
|
|
|
mChannelStatus = aStatusCode;
|
|
|
|
{
|
|
// Cancel the timer since we don't need it anymore.
|
|
nsCOMPtr<nsITimer> timer;
|
|
mTimeout.swap(timer);
|
|
if (timer) {
|
|
timer->Cancel();
|
|
}
|
|
}
|
|
|
|
ReportStatus(aStatusCode);
|
|
|
|
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.LowerCaseEqualsASCII(ContentType())) {
|
|
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(TRRSkippedReason::TRR_OK);
|
|
RecordProcessingTime(channel);
|
|
RecordHttpVersion(httpChannel);
|
|
return rv;
|
|
}
|
|
} else {
|
|
RecordReason(TRRSkippedReason::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 = GetOrCreateDNSPacket()->OnDataAvailable(aRequest, aInputStream,
|
|
aOffset, aCount);
|
|
if (NS_FAILED(rv)) {
|
|
LOG(("TRR::OnDataAvailable:%d fail\n", __LINE__));
|
|
mFailed = true;
|
|
return rv;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
void TRR::Cancel(nsresult aStatus) {
|
|
RefPtr<TRRServiceChannel> trrServiceChannel = do_QueryObject(mChannel);
|
|
if (trrServiceChannel && !XRE_IsSocketProcess()) {
|
|
if (gTRRService) {
|
|
nsCOMPtr<nsIThread> thread = gTRRService->TRRThread();
|
|
if (thread && !thread->IsOnCurrentThread()) {
|
|
thread->Dispatch(NS_NewRunnableFunction(
|
|
"TRR::Cancel",
|
|
[self = RefPtr(this), aStatus]() { self->Cancel(aStatus); }));
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
if (!NS_IsMainThread()) {
|
|
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
|
"TRR::Cancel",
|
|
[self = RefPtr(this), aStatus]() { self->Cancel(aStatus); }));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (mChannel) {
|
|
RecordReason(TRRSkippedReason::TRR_REQ_CANCELLED);
|
|
LOG(("TRR: %p canceling Channel %p %s %d status=%" PRIx32 "\n", this,
|
|
mChannel.get(), mHost.get(), mType, static_cast<uint32_t>(aStatus)));
|
|
mChannel->Cancel(aStatus);
|
|
}
|
|
}
|
|
|
|
bool TRR::UseDefaultServer() { return !mRec || mRec->mTrrServer.IsEmpty(); }
|
|
|
|
#undef LOG
|
|
|
|
// namespace
|
|
} // namespace net
|
|
} // namespace mozilla
|