/* 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 "TRRQuery.h" #include "mozilla/StaticPrefs_network.h" #include "mozilla/Telemetry.h" #include "nsQueryObject.h" #include "TRR.h" #include "TRRService.h" #include "ODoH.h" // Put DNSLogging.h at the end to avoid LOG being overwritten by other headers. #include "DNSLogging.h" namespace mozilla { namespace net { static already_AddRefed merge_rrset(AddrInfo* rrto, AddrInfo* rrfrom) { MOZ_ASSERT(rrto && rrfrom); // Each of the arguments are all-IPv4 or all-IPv6 hence judging // by the first element. This is true only for TRR resolutions. bool isIPv6 = rrfrom->Addresses().Length() > 0 && rrfrom->Addresses()[0].raw.family == PR_AF_INET6; nsTArray addresses; if (isIPv6) { addresses = rrfrom->Addresses().Clone(); addresses.AppendElements(rrto->Addresses()); } else { addresses = rrto->Addresses().Clone(); addresses.AppendElements(rrfrom->Addresses()); } auto builder = rrto->Build(); builder.SetAddresses(std::move(addresses)); return builder.Finish(); } void TRRQuery::Cancel(nsresult aStatus) { MutexAutoLock trrlock(mTrrLock); if (mTrrA) { mTrrA->Cancel(aStatus); } if (mTrrAAAA) { mTrrAAAA->Cancel(aStatus); } if (mTrrByType) { mTrrByType->Cancel(aStatus); } } void TRRQuery::MarkSendingTRR(TRR* trr, enum TrrType rectype, MutexAutoLock&) { if (rectype == TRRTYPE_A) { MOZ_ASSERT(!mTrrA); mTrrA = trr; mTrrAUsed = STARTED; } else if (rectype == TRRTYPE_AAAA) { MOZ_ASSERT(!mTrrAAAA); mTrrAAAA = trr; mTrrAAAAUsed = STARTED; } else { LOG(("TrrLookup called with bad type set: %d\n", rectype)); MOZ_ASSERT(0); } } void TRRQuery::PrepareQuery(bool aUseODoH, enum TrrType aRecType, nsTArray>& aRequestsToSend) { LOG(("TRR Resolve %s type %d\n", mRecord->host.get(), (int)aRecType)); RefPtr trr; if (aUseODoH) { trr = new ODoH(this, mRecord, aRecType); } else { trr = new TRR(this, mRecord, aRecType); } { MutexAutoLock trrlock(mTrrLock); MarkSendingTRR(trr, aRecType, trrlock); aRequestsToSend.AppendElement(trr); } } bool TRRQuery::SendQueries(nsTArray>& aRequestsToSend) { bool madeQuery = false; mTRRRequestCounter = aRequestsToSend.Length(); for (const auto& request : aRequestsToSend) { if (NS_SUCCEEDED(TRRService::Get()->DispatchTRRRequest(request))) { madeQuery = true; } else { mTRRRequestCounter--; MutexAutoLock trrlock(mTrrLock); if (request == mTrrA) { mTrrA = nullptr; mTrrAUsed = INIT; } if (request == mTrrAAAA) { mTrrAAAA = nullptr; mTrrAAAAUsed = INIT; } } } aRequestsToSend.Clear(); return madeQuery; } nsresult TRRQuery::DispatchLookup(TRR* pushedTRR, bool aUseODoH) { if (aUseODoH && pushedTRR) { MOZ_ASSERT(false, "ODoH should not support push"); return NS_ERROR_UNKNOWN_HOST; } if (!mRecord->IsAddrRecord()) { return DispatchByTypeLookup(pushedTRR, aUseODoH); } RefPtr addrRec = do_QueryObject(mRecord); MOZ_ASSERT(addrRec); if (!addrRec) { return NS_ERROR_UNEXPECTED; } mTrrStart = TimeStamp::Now(); mTrrAUsed = INIT; mTrrAAAAUsed = INIT; // Always issue both A and AAAA. // When both are complete we filter out the unneeded results. enum TrrType rectype = (mRecord->af == AF_INET6) ? TRRTYPE_AAAA : TRRTYPE_A; if (pushedTRR) { MutexAutoLock trrlock(mTrrLock); rectype = pushedTRR->Type(); MarkSendingTRR(pushedTRR, rectype, trrlock); return NS_OK; } // Need to dispatch TRR requests after |mTrrA| and |mTrrAAAA| are set // properly so as to avoid the race when CompleteLookup() is called at the // same time. nsTArray> requestsToSend; if ((mRecord->af == AF_UNSPEC || mRecord->af == AF_INET6)) { PrepareQuery(aUseODoH, TRRTYPE_AAAA, requestsToSend); } if (mRecord->af == AF_UNSPEC || mRecord->af == AF_INET) { PrepareQuery(aUseODoH, TRRTYPE_A, requestsToSend); } if (SendQueries(requestsToSend)) { mUsingODoH = aUseODoH; return NS_OK; } return NS_ERROR_UNKNOWN_HOST; } nsresult TRRQuery::DispatchByTypeLookup(TRR* pushedTRR, bool aUseODoH) { RefPtr typeRec = do_QueryObject(mRecord); MOZ_ASSERT(typeRec); if (!typeRec) { return NS_ERROR_UNEXPECTED; } typeRec->mStart = TimeStamp::Now(); enum TrrType rectype; // XXX this could use a more extensible approach. if (mRecord->type == nsIDNSService::RESOLVE_TYPE_TXT) { rectype = TRRTYPE_TXT; } else if (mRecord->type == nsIDNSService::RESOLVE_TYPE_HTTPSSVC) { rectype = TRRTYPE_HTTPSSVC; } else if (pushedTRR) { rectype = pushedTRR->Type(); } else { MOZ_ASSERT(false, "Not an expected request type"); return NS_ERROR_UNKNOWN_HOST; } LOG(("TRR Resolve %s type %d\n", typeRec->host.get(), (int)rectype)); RefPtr trr; if (aUseODoH) { trr = new ODoH(this, mRecord, rectype); } else { trr = pushedTRR ? pushedTRR : new TRR(this, mRecord, rectype); } if (pushedTRR || NS_SUCCEEDED(TRRService::Get()->DispatchTRRRequest(trr))) { MutexAutoLock trrlock(mTrrLock); MOZ_ASSERT(!mTrrByType); mTrrByType = trr; return NS_OK; } return NS_ERROR_UNKNOWN_HOST; } AHostResolver::LookupStatus TRRQuery::CompleteLookup( nsHostRecord* rec, nsresult status, AddrInfo* aNewRRSet, bool pb, const nsACString& aOriginsuffix, nsHostRecord::TRRSkippedReason aReason, TRR* aTRRRequest) { if (rec != mRecord) { LOG(("TRRQuery::CompleteLookup - Pushed record. Go to resolver")); return mHostResolver->CompleteLookup(rec, status, aNewRRSet, pb, aOriginsuffix, aReason, aTRRRequest); } LOG(("TRRQuery::CompleteLookup > host: %s", rec->host.get())); RefPtr newRRSet(aNewRRSet); DNSResolverType resolverType = newRRSet->ResolverType(); { MutexAutoLock trrlock(mTrrLock); if (newRRSet->TRRType() == TRRTYPE_A) { MOZ_ASSERT(mTrrA); mTRRAFailReason = aReason; mTrrA = nullptr; mTrrAUsed = NS_SUCCEEDED(status) ? OK : FAILED; MOZ_ASSERT(!mAddrInfoA); mAddrInfoA = newRRSet; mAResult = status; LOG(("A query status: 0x%x", static_cast(status))); } else if (newRRSet->TRRType() == TRRTYPE_AAAA) { MOZ_ASSERT(mTrrAAAA); mTRRAAAAFailReason = aReason; mTrrAAAA = nullptr; mTrrAAAAUsed = NS_SUCCEEDED(status) ? OK : FAILED; MOZ_ASSERT(!mAddrInfoAAAA); mAddrInfoAAAA = newRRSet; mAAAAResult = status; LOG(("AAAA query status: 0x%x", static_cast(status))); } else { MOZ_ASSERT(0); } } if (NS_SUCCEEDED(status)) { mTRRSuccess++; if (mTRRSuccess == 1) { // Store the duration on first succesful TRR response. We // don't know that there will be a second response nor can we // tell which of two has useful data. mTrrDuration = TimeStamp::Now() - mTrrStart; } } bool pendingRequest = false; if (mTRRRequestCounter) { mTRRRequestCounter--; pendingRequest = (mTRRRequestCounter != 0); } else { MOZ_DIAGNOSTIC_ASSERT(false, "Request counter is messed up"); } if (pendingRequest) { // There are other outstanding requests LOG(("CompleteLookup: waiting for all responses!\n")); return LOOKUP_OK; } if (mRecord->af == AF_UNSPEC) { // merge successful records if (mTrrAUsed == OK) { LOG(("Have A response")); newRRSet = mAddrInfoA; status = mAResult; if (mTrrAAAAUsed == OK) { LOG(("Merging A and AAAA responses")); newRRSet = merge_rrset(newRRSet, mAddrInfoAAAA); } } else { newRRSet = mAddrInfoAAAA; status = mAAAAResult; } if (NS_FAILED(status) && (mAAAAResult == NS_ERROR_DEFINITIVE_UNKNOWN_HOST || mAResult == NS_ERROR_DEFINITIVE_UNKNOWN_HOST)) { status = NS_ERROR_DEFINITIVE_UNKNOWN_HOST; } } else { // If this is a failed AAAA request, but the server only has a A record, // then we should not fallback to Do53. Instead we also send a A request // and return NS_ERROR_DEFINITIVE_UNKNOWN_HOST if that succeeds. if (NS_FAILED(status) && status != NS_ERROR_DEFINITIVE_UNKNOWN_HOST && (mTrrAUsed == INIT || mTrrAAAAUsed == INIT)) { if (newRRSet->TRRType() == TRRTYPE_A) { LOG(("A lookup failed. Checking if AAAA record exists")); nsTArray> requestsToSend; PrepareQuery(mUsingODoH, TRRTYPE_AAAA, requestsToSend); if (SendQueries(requestsToSend)) { LOG(("Sent AAAA request")); return LOOKUP_OK; } } else if (newRRSet->TRRType() == TRRTYPE_AAAA) { LOG(("AAAA lookup failed. Checking if A record exists")); nsTArray> requestsToSend; PrepareQuery(mUsingODoH, TRRTYPE_A, requestsToSend); if (SendQueries(requestsToSend)) { LOG(("Sent A request")); return LOOKUP_OK; } } else { MOZ_ASSERT(false, "Unexpected family"); } } bool otherSucceeded = mRecord->af == AF_INET6 ? mTrrAUsed == OK : mTrrAAAAUsed == OK; LOG(("TRRQuery::CompleteLookup other request succeeded")); if (mRecord->af == AF_INET) { // return only A record newRRSet = mAddrInfoA; status = mAResult; if (NS_FAILED(status) && (otherSucceeded || mAAAAResult == NS_ERROR_DEFINITIVE_UNKNOWN_HOST)) { LOG(("status set to NS_ERROR_DEFINITIVE_UNKNOWN_HOST")); status = NS_ERROR_DEFINITIVE_UNKNOWN_HOST; } } else if (mRecord->af == AF_INET6) { // return only AAAA record newRRSet = mAddrInfoAAAA; status = mAAAAResult; if (NS_FAILED(status) && (otherSucceeded || mAResult == NS_ERROR_DEFINITIVE_UNKNOWN_HOST)) { LOG(("status set to NS_ERROR_DEFINITIVE_UNKNOWN_HOST")); status = NS_ERROR_DEFINITIVE_UNKNOWN_HOST; } } else { MOZ_ASSERT(false, "Unexpected AF"); return LOOKUP_OK; } // If this record failed, but there is a record for the other AF // we prevent fallback to the native resolver. } if (mTRRSuccess && mHostResolver->GetNCS() && (mHostResolver->GetNCS()->GetNAT64() == nsINetworkConnectivityService::OK) && newRRSet) { newRRSet = mHostResolver->GetNCS()->MapNAT64IPs(newRRSet); } if (resolverType == DNSResolverType::TRR) { if (mTrrAUsed == OK) { AccumulateCategoricalKeyed( TRRService::ProviderKey(), Telemetry::LABELS_DNS_LOOKUP_DISPOSITION3::trrAOK); } else if (mTrrAUsed == FAILED) { AccumulateCategoricalKeyed( TRRService::ProviderKey(), Telemetry::LABELS_DNS_LOOKUP_DISPOSITION3::trrAFail); } if (mTrrAAAAUsed == OK) { AccumulateCategoricalKeyed( TRRService::ProviderKey(), Telemetry::LABELS_DNS_LOOKUP_DISPOSITION3::trrAAAAOK); } else if (mTrrAAAAUsed == FAILED) { AccumulateCategoricalKeyed( TRRService::ProviderKey(), Telemetry::LABELS_DNS_LOOKUP_DISPOSITION3::trrAAAAFail); } } mAddrInfoAAAA = nullptr; mAddrInfoA = nullptr; MOZ_DIAGNOSTIC_ASSERT(!mCalledCompleteLookup, "must not call CompleteLookup more than once"); mCalledCompleteLookup = true; return mHostResolver->CompleteLookup(rec, status, newRRSet, pb, aOriginsuffix, aReason, aTRRRequest); } AHostResolver::LookupStatus TRRQuery::CompleteLookupByType( nsHostRecord* rec, nsresult status, mozilla::net::TypeRecordResultType& aResult, uint32_t aTtl, bool pb) { if (rec != mRecord) { LOG(("TRRQuery::CompleteLookup - Pushed record. Go to resolver")); return mHostResolver->CompleteLookupByType(rec, status, aResult, aTtl, pb); } { MutexAutoLock trrlock(mTrrLock); mTrrByType = nullptr; } MOZ_DIAGNOSTIC_ASSERT(!mCalledCompleteLookup, "must not call CompleteLookup more than once"); mCalledCompleteLookup = true; return mHostResolver->CompleteLookupByType(rec, status, aResult, aTtl, pb); } } // namespace net } // namespace mozilla