Bug 1689987 - P3: Add some ODoH specific skip reasons r=necko-reviewers,valentin

Differential Revision: https://phabricator.services.mozilla.com/D104831
This commit is contained in:
Kershaw Chang 2021-02-18 12:50:03 +00:00
parent 782c896152
commit 9795f890cc
9 changed files with 146 additions and 34 deletions

View File

@ -363,6 +363,7 @@ nsresult DNSPacket::EncodeRequest(nsCString& aBody, const nsACString& aHost,
}
if (labelLength > 63) {
// too long label!
SetDNSPacketStatus(DNSPacketStatus::EncodeError);
return NS_ERROR_ILLEGAL_VALUE;
}
if (labelLength > 0) {
@ -416,6 +417,7 @@ nsresult DNSPacket::EncodeRequest(nsCString& aBody, const nsACString& aHost,
// ADDRESS, minimum number of octets == nothing because zero bits
}
SetDNSPacketStatus(DNSPacketStatus::Success);
return NS_OK;
}
@ -982,8 +984,12 @@ nsresult DNSPacket::Decode(
DOHresp& aResp, TypeRecordResultType& aTypeResult,
nsClassHashtable<nsCStringHashKey, DOHresp>& aAdditionalRecords,
uint32_t& aTTL) {
return DecodeInternal(aHost, aType, aCname, aAllowRFC1918, aResp, aTypeResult,
aAdditionalRecords, aTTL, mResponse, mBodySize);
nsresult rv =
DecodeInternal(aHost, aType, aCname, aAllowRFC1918, aResp, aTypeResult,
aAdditionalRecords, aTTL, mResponse, mBodySize);
SetDNSPacketStatus(NS_SUCCEEDED(rv) ? DNSPacketStatus::Success
: DNSPacketStatus::DecodeError);
return rv;
}
static SECItem* CreateRawConfig(const ObliviousDoHConfig& aConfig) {
@ -1155,10 +1161,17 @@ nsresult ODoHDNSPacket::EncodeRequest(nsCString& aBody, const nsACString& aHost,
nsAutoCString queryBody;
nsresult rv = DNSPacket::EncodeRequest(queryBody, aHost, aType, aDisableECS);
if (NS_FAILED(rv)) {
SetDNSPacketStatus(DNSPacketStatus::EncodeError);
return rv;
}
if (!gODoHService->ODoHConfigs() || gODoHService->ODoHConfigs()->IsEmpty()) {
if (!gODoHService->ODoHConfigs()) {
SetDNSPacketStatus(DNSPacketStatus::KeyNotAvailable);
return NS_ERROR_FAILURE;
}
if (gODoHService->ODoHConfigs()->IsEmpty()) {
SetDNSPacketStatus(DNSPacketStatus::KeyNotUsable);
return NS_ERROR_FAILURE;
}
@ -1168,6 +1181,7 @@ nsresult ODoHDNSPacket::EncodeRequest(nsCString& aBody, const nsACString& aHost,
ObliviousDoHMessage message;
// The spec didn't recommand padding length for encryption, let's use 0 here.
if (!EncryptDNSQuery(queryBody, 0, config, message)) {
SetDNSPacketStatus(DNSPacketStatus::EncryptError);
return NS_ERROR_FAILURE;
}
@ -1185,6 +1199,7 @@ nsresult ODoHDNSPacket::EncodeRequest(nsCString& aBody, const nsACString& aHost,
reinterpret_cast<const char*>(message.mEncryptedMessage.Elements()),
messageLen);
SetDNSPacketStatus(DNSPacketStatus::Success);
return NS_OK;
}
@ -1291,6 +1306,7 @@ nsresult ODoHDNSPacket::Decode(
// records, but we only need to decrypt the response once.
if (!mDecryptedResponseRange) {
if (!DecryptDNSResponse()) {
SetDNSPacketStatus(DNSPacketStatus::DecryptError);
return NS_ERROR_FAILURE;
}
@ -1299,6 +1315,7 @@ nsresult ODoHDNSPacket::Decode(
index += 2;
if (mBodySize < (index + responseLength)) {
SetDNSPacketStatus(DNSPacketStatus::DecryptError);
return NS_ERROR_ILLEGAL_VALUE;
}
@ -1311,16 +1328,20 @@ nsresult ODoHDNSPacket::Decode(
if (static_cast<unsigned int>(4 + responseLength + paddingLen) !=
mBodySize) {
SetDNSPacketStatus(DNSPacketStatus::DecryptError);
return NS_ERROR_ILLEGAL_VALUE;
}
mDecryptedResponseRange.emplace(range);
}
return DecodeInternal(aHost, aType, aCname, aAllowRFC1918, aResp, aTypeResult,
aAdditionalRecords, aTTL,
&mResponse[mDecryptedResponseRange->mStart],
mDecryptedResponseRange->mLength);
nsresult rv = DecodeInternal(aHost, aType, aCname, aAllowRFC1918, aResp,
aTypeResult, aAdditionalRecords, aTTL,
&mResponse[mDecryptedResponseRange->mStart],
mDecryptedResponseRange->mLength);
SetDNSPacketStatus(NS_SUCCEEDED(rv) ? DNSPacketStatus::Success
: DNSPacketStatus::DecodeError);
return rv;
}
static bool CreateObliviousDoHMessage(const unsigned char* aData,

View File

@ -33,6 +33,17 @@ enum TrrType {
TRRTYPE_HTTPSSVC = nsIDNSService::RESOLVE_TYPE_HTTPSSVC, // 65
};
enum class DNSPacketStatus : uint8_t {
Unknown = 0,
Success,
KeyNotAvailable,
KeyNotUsable,
EncodeError,
EncryptError,
DecodeError,
DecryptError,
};
class DNSPacket {
public:
DNSPacket() = default;
@ -57,6 +68,8 @@ class DNSPacket {
nsClassHashtable<nsCStringHashKey, DOHresp>& aAdditionalRecords,
uint32_t& aTTL);
DNSPacketStatus PacketStatus() const { return mStatus; }
protected:
// Never accept larger DOH responses than this as that would indicate
// something is wrong. Typical ones are much smaller.
@ -74,9 +87,17 @@ class DNSPacket {
nsClassHashtable<nsCStringHashKey, DOHresp>& aAdditionalRecords,
uint32_t& aTTL, const unsigned char* aBuffer, uint32_t aLen);
void SetDNSPacketStatus(DNSPacketStatus aStatus) {
if (mStatus == DNSPacketStatus::Unknown ||
mStatus == DNSPacketStatus::Success) {
mStatus = aStatus;
}
}
// The response buffer.
unsigned char mResponse[MAX_SIZE]{};
unsigned int mBodySize = 0;
DNSPacketStatus mStatus = DNSPacketStatus::Unknown;
};
class ODoHDNSPacket final : public DNSPacket {

View File

@ -34,6 +34,7 @@ ODoH::Run() {
if (NS_SUCCEEDED(gODoHService->UpdateODoHConfig())) {
gODoHService->AppendPendingODoHRequest(this);
} else {
RecordReason(nsHostRecord::ODOH_UPDATE_KEY_FAILED);
FailData(NS_ERROR_FAILURE);
return NS_OK;
}
@ -65,5 +66,45 @@ nsresult ODoH::CreateQueryURI(nsIURI** aOutURI) {
return NS_OK;
}
void ODoH::HandleTimeout() {
// If this request is still in the pending queue, it means we can't get the
// ODoHConfigs within the timeout.
if (gODoHService->RemovePendingODoHRequest(this)) {
RecordReason(nsHostRecord::ODOH_KEY_NOT_AVAILABLE);
}
TRR::HandleTimeout();
}
void ODoH::HandleEncodeError(nsresult aStatusCode) {
MOZ_ASSERT(NS_FAILED(aStatusCode));
DNSPacketStatus status = mPacket->PacketStatus();
MOZ_ASSERT(status != DNSPacketStatus::Success);
if (status == DNSPacketStatus::KeyNotAvailable) {
RecordReason(nsHostRecord::ODOH_KEY_NOT_AVAILABLE);
} else if (status == DNSPacketStatus::KeyNotUsable) {
RecordReason(nsHostRecord::ODOH_KEY_NOT_USABLE);
} else if (status == DNSPacketStatus::EncryptError) {
RecordReason(nsHostRecord::ODOH_ENCRYPTION_FAILED);
} else {
MOZ_ASSERT_UNREACHABLE("Unexpected status code.");
}
}
void ODoH::HandleDecodeError(nsresult aStatusCode) {
MOZ_ASSERT(NS_FAILED(aStatusCode));
DNSPacketStatus status = mPacket->PacketStatus();
MOZ_ASSERT(status != DNSPacketStatus::Success);
if (status == DNSPacketStatus::DecryptError) {
RecordReason(nsHostRecord::ODOH_DECRYPTION_FAILED);
}
TRR::HandleDecodeError(aStatusCode);
}
} // namespace net
} // namespace mozilla

View File

@ -17,6 +17,10 @@ class ODoH final : public TRR {
explicit ODoH(AHostResolver* aResolver, nsHostRecord* aRec,
enum TrrType aType)
: TRR(aResolver, aRec, aType) {}
// when following CNAMEs
explicit ODoH(AHostResolver* aResolver, nsHostRecord* aRec, nsCString& aHost,
enum TrrType& aType, unsigned int aLoopCount, bool aPB)
: TRR(aResolver, aRec, aHost, aType, aLoopCount, aPB) {}
NS_IMETHOD Run() override;
// ODoH should not support push.
NS_IMETHOD GetInterface(const nsIID&, void**) override {
@ -43,6 +47,9 @@ class ODoH final : public TRR {
virtual void ReportStatus(nsresult aStatusCode) override {
// TODO: record status in ODoHService.
}
virtual void HandleTimeout() override;
virtual void HandleEncodeError(nsresult aStatusCode) override;
virtual void HandleDecodeError(nsresult aStatusCode) override;
};
} // namespace net

View File

@ -238,6 +238,7 @@ ODoHService::OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRec,
return NS_OK;
}
mODoHConfigs.reset();
mODoHConfigs.emplace(std::move(configs));
if (!mPendingRequests.IsEmpty()) {
@ -267,5 +268,13 @@ void ODoHService::AppendPendingODoHRequest(ODoH* aRequest) {
mPendingRequests.AppendElement(aRequest);
}
bool ODoHService::RemovePendingODoHRequest(ODoH* aRequest) {
MOZ_ASSERT_IF(XRE_IsParentProcess() && gTRRService,
NS_IsMainThread() || gTRRService->IsOnTRRThread());
MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
return mPendingRequests.RemoveElement(aRequest);
}
} // namespace net
} // namespace mozilla

View File

@ -33,6 +33,7 @@ class ODoHService : public nsIDNSListener,
const Maybe<nsTArray<ObliviousDoHConfig>>& ODoHConfigs();
void AppendPendingODoHRequest(ODoH* aRequest);
bool RemovePendingODoHRequest(ODoH* aRequest);
void GetRequestURI(nsACString& aResult);
// Send a DNS query to reterive the ODoHConfig.
nsresult UpdateODoHConfig();

View File

@ -50,12 +50,16 @@ extern mozilla::LazyLogModule gHostResolverLog;
NS_IMPL_ISUPPORTS(TRR, nsIHttpPushListener, nsIInterfaceRequestor,
nsIStreamListener, nsIRunnable)
void TRR::HandleTimeout() {
mTimeout = nullptr;
RecordReason(nsHostRecord::TRR_TIMEOUT);
Cancel(NS_ERROR_NET_TIMEOUT_EXTERNAL);
}
NS_IMETHODIMP
TRR::Notify(nsITimer* aTimer) {
if (aTimer == mTimeout) {
mTimeout = nullptr;
RecordReason(nsHostRecord::TRR_TIMEOUT);
Cancel(NS_ERROR_NET_TIMEOUT_EXTERNAL);
HandleTimeout();
} else {
MOZ_CRASH("Unknown timer");
}
@ -216,7 +220,10 @@ nsresult TRR::SendHTTPRequest() {
bool disableECS = StaticPrefs::network_trr_disable_ECS();
nsresult rv =
GetOrCreateDNSPacket()->EncodeRequest(body, mHost, mType, disableECS);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_FAILED(rv)) {
HandleEncodeError(rv);
return rv;
}
bool useGet = StaticPrefs::network_trr_useGET();
nsCOMPtr<nsIURI> dnsURI;
@ -750,6 +757,18 @@ nsresult TRR::FailData(nsresult error) {
return NS_OK;
}
void TRR::HandleDecodeError(nsresult aStatusCode) {
auto rcode = mPacket->GetRCode();
if (rcode.isOk() && rcode.unwrap() != 0) {
RecordReason(nsHostRecord::TRR_RCODE_FAIL);
} else if (aStatusCode == NS_ERROR_UNKNOWN_HOST ||
aStatusCode == NS_ERROR_DEFINITIVE_UNKNOWN_HOST) {
RecordReason(nsHostRecord::TRR_NO_ANSWERS);
} else {
RecordReason(nsHostRecord::TRR_DECODE_FAILED);
}
}
nsresult TRR::FollowCname(nsIChannel* aChannel) {
nsresult rv = NS_OK;
nsAutoCString cname;
@ -768,17 +787,8 @@ nsresult TRR::FollowCname(nsIChannel* aChannel) {
cname, mType, mCname, StaticPrefs::network_trr_allow_rfc1918(), mDNS,
mResult, additionalRecords, mTTL);
if (NS_FAILED(rv)) {
LOG(("TRR::On200Response DohDecode %x\n", (int)rv));
auto rcode = mPacket->GetRCode();
if (rcode.isOk() && rcode.unwrap() != 0) {
RecordReason(nsHostRecord::TRR_RCODE_FAIL);
} else if (rv == NS_ERROR_UNKNOWN_HOST ||
rv == NS_ERROR_DEFINITIVE_UNKNOWN_HOST) {
RecordReason(nsHostRecord::TRR_NO_ANSWERS);
} else {
RecordReason(nsHostRecord::TRR_DECODE_FAILED);
}
LOG(("TRR::FollowCname DohDecode %x\n", (int)rv));
HandleDecodeError(rv);
}
}
@ -797,7 +807,9 @@ nsresult TRR::FollowCname(nsIChannel* aChannel) {
LOG(("TRR::On200Response CNAME %s => %s (%u)\n", mHost.get(), mCname.get(),
mCnameLoop));
RefPtr<TRR> trr =
new TRR(mHostResolver, mRec, mCname, mType, mCnameLoop, mPB);
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;
}
@ -812,15 +824,7 @@ nsresult TRR::On200Response(nsIChannel* aChannel) {
mResult, additionalRecords, mTTL);
if (NS_FAILED(rv)) {
LOG(("TRR::On200Response DohDecode %x\n", (int)rv));
auto rcode = mPacket->GetRCode();
if (rcode.isOk() && rcode.unwrap() != 0) {
RecordReason(nsHostRecord::TRR_RCODE_FAIL);
} else if (rv == NS_ERROR_UNKNOWN_HOST ||
rv == NS_ERROR_DEFINITIVE_UNKNOWN_HOST) {
RecordReason(nsHostRecord::TRR_NO_ANSWERS);
} else {
RecordReason(nsHostRecord::TRR_DECODE_FAILED);
}
HandleDecodeError(rv);
return rv;
}
SaveAdditionalRecords(additionalRecords);

View File

@ -114,6 +114,9 @@ class TRR : public Runnable,
virtual bool MaybeBlockRequest();
virtual void RecordProcessingTime(nsIChannel* aChannel);
virtual void ReportStatus(nsresult aStatusCode);
virtual void HandleTimeout();
virtual void HandleEncodeError(nsresult aStatusCode) {}
virtual void HandleDecodeError(nsresult aStatusCode);
nsresult SendHTTPRequest();
nsresult ReturnData(nsIChannel* aChannel);

View File

@ -119,8 +119,13 @@ class nsHostRecord : public mozilla::LinkedListElement<RefPtr<nsHostRecord>>,
TRR_SERVER_RESPONSE_ERR = 27, // Server responded with non-200 code
TRR_RCODE_FAIL = 28, // DNS response contains a non-NOERROR rcode
TRR_NO_CONNECTIVITY = 29, // Not confirmed because of no connectivity
TRR_NXDOMAIN = 30, // DNS response contains NXDOMAIN rcode (0x03)
TRR_REQ_CANCELLED = 31, // The request has been cancelled
TRR_NXDOMAIN = 30, // DNS response contains NXDOMAIN rcode (0x03)
TRR_REQ_CANCELLED = 31, // The request has been cancelled
ODOH_KEY_NOT_USABLE = 32, // We don't have a valid ODoHConfig to use.
ODOH_UPDATE_KEY_FAILED = 33, // Failed to update the ODoHConfigs.
ODOH_KEY_NOT_AVAILABLE = 34, // ODoH requests timeout because of no key.
ODOH_ENCRYPTION_FAILED = 35, // Failed to encrypt DNS packets.
ODOH_DECRYPTION_FAILED = 36, // Failed to decrypt DNS packets.
};
// Records the first reason that caused TRR to be skipped or to fail.