Bug 1689987 - P3: Add some ODoH specific skip reasons draft
authorKershaw Chang <kershaw@mozilla.com>
Thu, 11 Feb 2021 17:10:04 +0100
changeset 3544976 b5cca9e393f233e5e89a58f630e51c5afd720291
parent 3544975 1a4836ba20f59e53fa42cbe9f1b7cf2fdf4c04f1
child 3544977 e0e7482b7b032fff880a669ac76bff15d2dd2959
push id656091
push userkjang@mozilla.com
push dateWed, 17 Feb 2021 08:59:00 +0000
treeherdertry@c347c8f867c5 [default view] [failures only]
bugs1689987
milestone87.0a1
Bug 1689987 - P3: Add some ODoH specific skip reasons Differential Revision: https://phabricator.services.mozilla.com/D104831
netwerk/dns/DNSPacket.cpp
netwerk/dns/DNSPacket.h
netwerk/dns/ODoH.cpp
netwerk/dns/ODoH.h
netwerk/dns/ODoHService.cpp
netwerk/dns/ODoHService.h
netwerk/dns/TRR.cpp
netwerk/dns/TRR.h
netwerk/dns/nsHostResolver.h
--- a/netwerk/dns/DNSPacket.cpp
+++ b/netwerk/dns/DNSPacket.cpp
@@ -358,16 +358,17 @@ nsresult DNSPacket::EncodeRequest(nsCStr
     if (kNotFound != index) {
       dotFound = true;
       labelLength = index - offset;
     } else {
       labelLength = aHost.Length() - offset;
     }
     if (labelLength > 63) {
       // too long label!
+      SetDNSPacketStatus(DNSPacketStatus::EncodeError);
       return NS_ERROR_ILLEGAL_VALUE;
     }
     if (labelLength > 0) {
       aBody += static_cast<unsigned char>(labelLength);
       nsDependentCSubstring label = Substring(aHost, offset, labelLength);
       aBody.Append(label);
     }
     if (!dotFound) {
@@ -411,16 +412,17 @@ nsresult DNSPacket::EncodeRequest(nsCStr
                     // not the AF_* constants!
     aBody += 1;     // FAMILY (Ipv4), 2 octets
 
     aBody += '\0';  // SOURCE PREFIX-LENGTH      |     SCOPE PREFIX-LENGTH |
     aBody += '\0';
 
     // ADDRESS, minimum number of octets == nothing because zero bits
   }
+  SetDNSPacketStatus(DNSPacketStatus::Success);
   return NS_OK;
 }
 
 Result<uint8_t, nsresult> DNSPacket::GetRCode() const {
   if (mBodySize < 12) {
     LOG(("DNSPacket::GetRCode - packet too small"));
     return Err(NS_ERROR_ILLEGAL_VALUE);
   }
@@ -979,18 +981,22 @@ nsresult DNSPacket::DecodeInternal(
 //
 // DohDecode() collects the TTL and the IP addresses in the response
 //
 nsresult DNSPacket::Decode(
     nsCString& aHost, enum TrrType aType, nsCString& aCname, bool aAllowRFC1918,
     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) {
   SECItem* item(::SECITEM_AllocItem(nullptr, nullptr,
                                     8 + aConfig.mContents.mPublicKey.Length()));
   if (!item) {
     return nullptr;
   }
@@ -1152,29 +1158,37 @@ bool ODoHDNSPacket::ParseODoHConfigs(con
 
 ODoHDNSPacket::~ODoHDNSPacket() { PK11_HPKE_DestroyContext(mContext, true); }
 
 nsresult ODoHDNSPacket::EncodeRequest(nsCString& aBody, const nsACString& aHost,
                                       uint16_t aType, bool aDisableECS) {
   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;
   }
 
   // We only use the first ODoHConfig.
   const ObliviousDoHConfig& config = (*gODoHService->ODoHConfigs())[0];
 
   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;
   }
 
   aBody.Truncate();
   aBody += message.mType;
   uint16_t keyIdLength = message.mKeyId.Length();
   aBody += static_cast<uint8_t>(keyIdLength >> 8);
   aBody += static_cast<uint8_t>(keyIdLength);
@@ -1182,16 +1196,17 @@ nsresult ODoHDNSPacket::EncodeRequest(ns
                keyIdLength);
   uint16_t messageLen = message.mEncryptedMessage.Length();
   aBody += static_cast<uint8_t>(messageLen >> 8);
   aBody += static_cast<uint8_t>(messageLen);
   aBody.Append(
       reinterpret_cast<const char*>(message.mEncryptedMessage.Elements()),
       messageLen);
 
+  SetDNSPacketStatus(DNSPacketStatus::Success);
   return NS_OK;
 }
 
 /*
  * def encrypt_query_body(pkR, key_id, Q_plain):
  *     enc, context = SetupBaseS(pkR, "odoh query")
  *     aad = 0x01 || len(key_id) || key_id
  *     ct = context.Seal(aad, Q_plain)
@@ -1288,46 +1303,52 @@ nsresult ODoHDNSPacket::Decode(
     nsCString& aHost, enum TrrType aType, nsCString& aCname, bool aAllowRFC1918,
     DOHresp& aResp, TypeRecordResultType& aTypeResult,
     nsClassHashtable<nsCStringHashKey, DOHresp>& aAdditionalRecords,
     uint32_t& aTTL) {
   // This function could be called multiple times when we are checking CNAME
   // records, but we only need to decrypt the response once.
   if (!mDecryptedResponseRange) {
     if (!DecryptDNSResponse()) {
+      SetDNSPacketStatus(DNSPacketStatus::DecryptError);
       return NS_ERROR_FAILURE;
     }
 
     uint32_t index = 0;
     uint16_t responseLength = get16bit(mResponse, index);
     index += 2;
 
     if (mBodySize < (index + responseLength)) {
+      SetDNSPacketStatus(DNSPacketStatus::DecryptError);
       return NS_ERROR_ILLEGAL_VALUE;
     }
 
     DecryptedResponseRange range;
     range.mStart = index;
     range.mLength = responseLength;
 
     index += responseLength;
     uint16_t paddingLen = get16bit(mResponse, index);
 
     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,
                                       unsigned int aLength,
                                       ObliviousDoHMessage& aOut) {
   if (aLength < 5) {
     return false;
   }
--- a/netwerk/dns/DNSPacket.h
+++ b/netwerk/dns/DNSPacket.h
@@ -28,16 +28,27 @@ enum TrrType {
   TRRTYPE_NS = 2,
   TRRTYPE_CNAME = 5,
   TRRTYPE_AAAA = 28,
   TRRTYPE_OPT = 41,
   TRRTYPE_TXT = 16,
   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;
   virtual ~DNSPacket() = default;
 
   Result<uint8_t, nsresult> GetRCode() const;
 
   // Called in order to feed data into the buffer.
@@ -52,16 +63,18 @@ class DNSPacket {
   // etc. XXX: This should probably be refactored to reduce the number of
   // output parameters and have a common format for different record types.
   virtual nsresult Decode(
       nsCString& aHost, enum TrrType aType, nsCString& aCname,
       bool aAllowRFC1918, DOHresp& aResp, TypeRecordResultType& aTypeResult,
       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.
   static const unsigned int MAX_SIZE = 3200;
 
   nsresult PassQName(unsigned int& index, const unsigned char* aBuffer);
   nsresult GetQname(nsACString& aQname, unsigned int& aIndex,
                     const unsigned char* aBuffer);
@@ -69,19 +82,27 @@ class DNSPacket {
                          SvcFieldValue& field, uint16_t length,
                          const unsigned char* aBuffer);
   nsresult DecodeInternal(
       nsCString& aHost, enum TrrType aType, nsCString& aCname,
       bool aAllowRFC1918, DOHresp& aResp, TypeRecordResultType& aTypeResult,
       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 {
  public:
   ODoHDNSPacket() = default;
   virtual ~ODoHDNSPacket();
 
   static bool ParseODoHConfigs(const nsCString& aRawODoHConfig,
--- a/netwerk/dns/ODoH.cpp
+++ b/netwerk/dns/ODoH.cpp
@@ -29,16 +29,17 @@ ODoH::Run() {
     return NS_OK;
   }
 
   if (!gODoHService->ODoHConfigs()) {
     LOG(("ODoH::Run ODoHConfigs is not available\n"));
     if (NS_SUCCEEDED(gODoHService->UpdateODoHConfig())) {
       gODoHService->AppendPendingODoHRequest(this);
     } else {
+      RecordReason(nsHostRecord::ODOH_UPDATE_KEY_FAILED);
       FailData(NS_ERROR_FAILURE);
       return NS_OK;
     }
     return NS_OK;
   }
 
   return TRR::Run();
 }
@@ -60,10 +61,50 @@ nsresult ODoH::CreateQueryURI(nsIURI** a
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   dnsURI.forget(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
--- a/netwerk/dns/ODoH.h
+++ b/netwerk/dns/ODoH.h
@@ -12,16 +12,20 @@
 namespace mozilla {
 namespace net {
 
 class ODoH final : public TRR {
  public:
   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 {
     return NS_ERROR_NO_INTERFACE;
   }
 
  protected:
   virtual ~ODoH() = default;
@@ -38,14 +42,17 @@ class ODoH final : public TRR {
     return false;
   };
   virtual void RecordProcessingTime(nsIChannel* aChannel) override {
     // TODO: record processing time for ODoH.
   }
   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
 }  // namespace mozilla
 
 #endif
--- a/netwerk/dns/ODoHService.cpp
+++ b/netwerk/dns/ODoHService.cpp
@@ -233,16 +233,17 @@ ODoHService::OnLookupComplete(nsICancela
     }
   }
 
   nsTArray<ObliviousDoHConfig> configs;
   if (!ODoHDNSPacket::ParseODoHConfigs(rawODoHConfig, configs)) {
     return NS_OK;
   }
 
+  mODoHConfigs.reset();
   mODoHConfigs.emplace(std::move(configs));
 
   if (!mPendingRequests.IsEmpty()) {
     nsTArray<RefPtr<ODoH>> requests = std::move(mPendingRequests);
     nsCOMPtr<nsIEventTarget> target = gTRRService->MainThreadOrTRRThread();
     for (auto& query : requests) {
       target->Dispatch(query.forget());
     }
@@ -262,10 +263,18 @@ void ODoHService::AppendPendingODoHReque
   LOG(("ODoHService::AppendPendingODoHQuery\n"));
   MOZ_ASSERT_IF(XRE_IsParentProcess() && gTRRService,
                 NS_IsMainThread() || gTRRService->IsOnTRRThread());
   MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
 
   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
--- a/netwerk/dns/ODoHService.h
+++ b/netwerk/dns/ODoHService.h
@@ -28,16 +28,17 @@ class ODoHService : public nsIDNSListene
   NS_DECL_NSIOBSERVER
 
   ODoHService();
   bool Init();
   bool Enabled() const;
 
   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();
 
  private:
   virtual ~ODoHService();
   nsresult ReadPrefs(const char* aName);
   void OnODoHPrefsChange(bool aInit);
--- a/netwerk/dns/TRR.cpp
+++ b/netwerk/dns/TRR.cpp
@@ -45,22 +45,26 @@ namespace net {
 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)
 
+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");
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -211,17 +215,20 @@ nsresult TRR::SendHTTPRequest() {
   }
 
   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);
-  NS_ENSURE_SUCCESS(rv, rv);
+  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;
   }
@@ -743,16 +750,28 @@ nsresult TRR::FailData(nsresult error) {
                                         mTRRSkippedReason);
   }
 
   mHostResolver = nullptr;
   mRec = nullptr;
   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;
   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));
@@ -761,27 +780,18 @@ nsresult TRR::FollowCname(nsIChannel* aC
 
     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::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);
     }
   }
 
   // restore mCname as DohDecode() change it
   mCname = cname;
   if (NS_SUCCEEDED(rv) && !mDNS.mAddresses.IsEmpty()) {
     ReturnData(aChannel);
     return NS_OK;
@@ -790,40 +800,34 @@ nsresult TRR::FollowCname(nsIChannel* aC
   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);
+      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));
-    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);
 
   if (mResult.is<TypeRecordHTTPSSVC>()) {
     auto& results = mResult.as<TypeRecordHTTPSSVC>();
     for (const auto& rec : results) {
       StoreIPHintAsDNSRecord(rec);
--- a/netwerk/dns/TRR.h
+++ b/netwerk/dns/TRR.h
@@ -105,16 +105,19 @@ class TRR : public Runnable,
   virtual ~TRR() = default;
   virtual DNSPacket* GetOrCreateDNSPacket();
   virtual nsresult CreateQueryURI(nsIURI** aOutURI);
   virtual const char* ContentType() const { return "application/dns-message"; }
   virtual DNSResolverType ResolverType() const { return DNSResolverType::TRR; }
   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);
 
   // FailData() must be called to signal that the asynch TRR resolve is
   // completed. For failed name resolves ("no such host"), the 'error' it
   // passses on in its argument must be NS_ERROR_UNKNOWN_HOST. Other errors
   // (if host was blacklisted, there as a bad content-type received, etc)
   // other error codes must be used. This distinction is important for the
--- a/netwerk/dns/nsHostResolver.h
+++ b/netwerk/dns/nsHostResolver.h
@@ -114,18 +114,23 @@ class nsHostRecord : public mozilla::Lin
     TRR_NET_INTERRUPT = 22,        // NS_ERROR_NET_INTERRUPT
     TRR_NET_INADEQ_SEQURITY = 23,  // NS_ERROR_NET_INADEQUATE_SECURITY
     TRR_NO_ANSWERS = 24,           // TRR returned no answers
     TRR_DECODE_FAILED = 25,        // DohDecode failed
     TRR_EXCLUDED = 26,             // ExcludedFromTRR
     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.
   void RecordReason(TRRSkippedReason reason) {
     if (mTRRTRRSkippedReason == TRR_UNSET) {
       mTRRTRRSkippedReason = reason;
     }
   }