tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

HTTPSSVC.cpp (19814B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "HTTPSSVC.h"
      6 #include "mozilla/net/DNS.h"
      7 #include "mozilla/glean/NetwerkProtocolHttpMetrics.h"
      8 #include "mozilla/StaticPrefs_network.h"
      9 #include "nsHttp.h"
     10 #include "nsHttpHandler.h"
     11 #include "nsNetAddr.h"
     12 #include "nsNetUtil.h"
     13 #include "nsIDNSService.h"
     14 
     15 namespace mozilla {
     16 namespace net {
     17 
     18 NS_IMPL_ISUPPORTS(SVCBRecord, nsISVCBRecord)
     19 
     20 class SvcParam : public nsISVCParam,
     21                 public nsISVCParamAlpn,
     22                 public nsISVCParamNoDefaultAlpn,
     23                 public nsISVCParamPort,
     24                 public nsISVCParamIPv4Hint,
     25                 public nsISVCParamEchConfig,
     26                 public nsISVCParamIPv6Hint,
     27                 public nsISVCParamODoHConfig {
     28  NS_DECL_THREADSAFE_ISUPPORTS
     29  NS_DECL_NSISVCPARAM
     30  NS_DECL_NSISVCPARAMALPN
     31  NS_DECL_NSISVCPARAMNODEFAULTALPN
     32  NS_DECL_NSISVCPARAMPORT
     33  NS_DECL_NSISVCPARAMIPV4HINT
     34  NS_DECL_NSISVCPARAMECHCONFIG
     35  NS_DECL_NSISVCPARAMIPV6HINT
     36  NS_DECL_NSISVCPARAMODOHCONFIG
     37 public:
     38  explicit SvcParam(const SvcParamType& value) : mValue(value) {};
     39 
     40 private:
     41  virtual ~SvcParam() = default;
     42  SvcParamType mValue;
     43 };
     44 
     45 NS_IMPL_ADDREF(SvcParam)
     46 NS_IMPL_RELEASE(SvcParam)
     47 NS_INTERFACE_MAP_BEGIN(SvcParam)
     48  NS_INTERFACE_MAP_ENTRY(nsISVCParam)
     49  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISVCParam)
     50  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamAlpn, mValue.is<SvcParamAlpn>())
     51  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamNoDefaultAlpn,
     52                                     mValue.is<SvcParamNoDefaultAlpn>())
     53  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamPort, mValue.is<SvcParamPort>())
     54  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamIPv4Hint,
     55                                     mValue.is<SvcParamIpv4Hint>())
     56  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamEchConfig,
     57                                     mValue.is<SvcParamEchConfig>())
     58  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamIPv6Hint,
     59                                     mValue.is<SvcParamIpv6Hint>())
     60  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamODoHConfig,
     61                                     mValue.is<SvcParamODoHConfig>())
     62 NS_INTERFACE_MAP_END
     63 
     64 NS_IMETHODIMP
     65 SvcParam::GetType(uint16_t* aType) {
     66  *aType = mValue.match(
     67      [](Nothing&) { return SvcParamKeyMandatory; },
     68      [](SvcParamAlpn&) { return SvcParamKeyAlpn; },
     69      [](SvcParamNoDefaultAlpn&) { return SvcParamKeyNoDefaultAlpn; },
     70      [](SvcParamPort&) { return SvcParamKeyPort; },
     71      [](SvcParamIpv4Hint&) { return SvcParamKeyIpv4Hint; },
     72      [](SvcParamEchConfig&) { return SvcParamKeyEchConfig; },
     73      [](SvcParamIpv6Hint&) { return SvcParamKeyIpv6Hint; },
     74      [](SvcParamODoHConfig&) { return SvcParamKeyODoHConfig; });
     75  return NS_OK;
     76 }
     77 
     78 NS_IMETHODIMP
     79 SvcParam::GetAlpn(nsTArray<nsCString>& aAlpn) {
     80  if (!mValue.is<SvcParamAlpn>()) {
     81    MOZ_ASSERT(false, "Unexpected type for variant");
     82    return NS_ERROR_NOT_AVAILABLE;
     83  }
     84  aAlpn.AppendElements(mValue.as<SvcParamAlpn>().mValue);
     85  return NS_OK;
     86 }
     87 
     88 NS_IMETHODIMP
     89 SvcParam::GetPort(uint16_t* aPort) {
     90  if (!mValue.is<SvcParamPort>()) {
     91    MOZ_ASSERT(false, "Unexpected type for variant");
     92    return NS_ERROR_NOT_AVAILABLE;
     93  }
     94  *aPort = mValue.as<SvcParamPort>().mValue;
     95  return NS_OK;
     96 }
     97 
     98 NS_IMETHODIMP
     99 SvcParam::GetEchconfig(nsACString& aEchConfig) {
    100  if (!mValue.is<SvcParamEchConfig>()) {
    101    MOZ_ASSERT(false, "Unexpected type for variant");
    102    return NS_ERROR_NOT_AVAILABLE;
    103  }
    104  aEchConfig = mValue.as<SvcParamEchConfig>().mValue;
    105  return NS_OK;
    106 }
    107 
    108 NS_IMETHODIMP
    109 SvcParam::GetIpv4Hint(nsTArray<RefPtr<nsINetAddr>>& aIpv4Hint) {
    110  if (!mValue.is<SvcParamIpv4Hint>()) {
    111    MOZ_ASSERT(false, "Unexpected type for variant");
    112    return NS_ERROR_NOT_AVAILABLE;
    113  }
    114  const auto& results = mValue.as<SvcParamIpv4Hint>().mValue;
    115  for (const auto& ip : results) {
    116    if (ip.raw.family != AF_INET) {
    117      return NS_ERROR_UNEXPECTED;
    118    }
    119    RefPtr<nsINetAddr> hint = new nsNetAddr(&ip);
    120    aIpv4Hint.AppendElement(hint);
    121  }
    122 
    123  return NS_OK;
    124 }
    125 
    126 NS_IMETHODIMP
    127 SvcParam::GetIpv6Hint(nsTArray<RefPtr<nsINetAddr>>& aIpv6Hint) {
    128  if (!mValue.is<SvcParamIpv6Hint>()) {
    129    MOZ_ASSERT(false, "Unexpected type for variant");
    130    return NS_ERROR_NOT_AVAILABLE;
    131  }
    132  const auto& results = mValue.as<SvcParamIpv6Hint>().mValue;
    133  for (const auto& ip : results) {
    134    if (ip.raw.family != AF_INET6) {
    135      return NS_ERROR_UNEXPECTED;
    136    }
    137    RefPtr<nsINetAddr> hint = new nsNetAddr(&ip);
    138    aIpv6Hint.AppendElement(hint);
    139  }
    140  return NS_OK;
    141 }
    142 
    143 NS_IMETHODIMP
    144 SvcParam::GetODoHConfig(nsACString& aODoHConfig) {
    145  if (!mValue.is<SvcParamODoHConfig>()) {
    146    MOZ_ASSERT(false, "Unexpected type for variant");
    147    return NS_ERROR_NOT_AVAILABLE;
    148  }
    149  aODoHConfig = mValue.as<SvcParamODoHConfig>().mValue;
    150  return NS_OK;
    151 }
    152 
    153 bool SVCB::operator<(const SVCB& aOther) const {
    154  if (nsHttpHandler::EchConfigEnabled()) {
    155    if (mHasEchConfig && !aOther.mHasEchConfig) {
    156      return true;
    157    }
    158    if (!mHasEchConfig && aOther.mHasEchConfig) {
    159      return false;
    160    }
    161  }
    162 
    163  return mSvcFieldPriority < aOther.mSvcFieldPriority;
    164 }
    165 
    166 Maybe<uint16_t> SVCB::GetPort() const {
    167  Maybe<uint16_t> port;
    168  for (const auto& value : mSvcFieldValue) {
    169    if (value.mValue.is<SvcParamPort>()) {
    170      port.emplace(value.mValue.as<SvcParamPort>().mValue);
    171      if (NS_FAILED(NS_CheckPortSafety(*port, "https"))) {
    172        *port = 0;
    173      }
    174      return port;
    175    }
    176  }
    177 
    178  return Nothing();
    179 }
    180 
    181 bool SVCB::NoDefaultAlpn() const {
    182  for (const auto& value : mSvcFieldValue) {
    183    if (value.mValue.is<SvcParamKeyNoDefaultAlpn>()) {
    184      return true;
    185    }
    186  }
    187 
    188  return false;
    189 }
    190 
    191 void SVCB::GetIPHints(CopyableTArray<mozilla::net::NetAddr>& aAddresses) const {
    192  if (mSvcFieldPriority == 0) {
    193    return;
    194  }
    195 
    196  for (const auto& value : mSvcFieldValue) {
    197    if (value.mValue.is<SvcParamIpv4Hint>()) {
    198      aAddresses.AppendElements(value.mValue.as<SvcParamIpv4Hint>().mValue);
    199    } else if (value.mValue.is<SvcParamIpv6Hint>()) {
    200      aAddresses.AppendElements(value.mValue.as<SvcParamIpv6Hint>().mValue);
    201    }
    202  }
    203 }
    204 
    205 class AlpnComparator {
    206 public:
    207  bool Equals(const std::tuple<nsCString, SupportedAlpnRank>& aA,
    208              const std::tuple<nsCString, SupportedAlpnRank>& aB) const {
    209    return std::get<1>(aA) == std::get<1>(aB);
    210  }
    211  bool LessThan(const std::tuple<nsCString, SupportedAlpnRank>& aA,
    212                const std::tuple<nsCString, SupportedAlpnRank>& aB) const {
    213    return std::get<1>(aA) > std::get<1>(aB);
    214  }
    215 };
    216 
    217 nsTArray<std::tuple<nsCString, SupportedAlpnRank>> SVCB::GetAllAlpn(
    218    bool& aHasNoDefaultAlpn) const {
    219  aHasNoDefaultAlpn = false;
    220  nsTArray<std::tuple<nsCString, SupportedAlpnRank>> alpnList;
    221  for (const auto& value : mSvcFieldValue) {
    222    if (value.mValue.is<SvcParamAlpn>()) {
    223      for (const auto& alpn : value.mValue.as<SvcParamAlpn>().mValue) {
    224        alpnList.AppendElement(std::make_tuple(alpn, IsAlpnSupported(alpn)));
    225      }
    226    } else if (value.mValue.is<SvcParamKeyNoDefaultAlpn>()) {
    227      // Found "no-default-alpn".
    228      aHasNoDefaultAlpn = true;
    229    }
    230  }
    231  alpnList.Sort(AlpnComparator());
    232  return alpnList;
    233 }
    234 
    235 SVCBRecord::SVCBRecord(const SVCB& data,
    236                       Maybe<std::tuple<nsCString, SupportedAlpnRank>> aAlpn)
    237    : mData(data), mAlpn(aAlpn) {
    238  mPort = mData.GetPort();
    239 }
    240 
    241 NS_IMETHODIMP SVCBRecord::GetPriority(uint16_t* aPriority) {
    242  *aPriority = mData.mSvcFieldPriority;
    243  return NS_OK;
    244 }
    245 
    246 NS_IMETHODIMP SVCBRecord::GetName(nsACString& aName) {
    247  aName = mData.mSvcDomainName;
    248  return NS_OK;
    249 }
    250 
    251 Maybe<uint16_t> SVCBRecord::GetPort() { return mPort; }
    252 
    253 Maybe<std::tuple<nsCString, SupportedAlpnRank>> SVCBRecord::GetAlpn() {
    254  return mAlpn;
    255 }
    256 
    257 NS_IMETHODIMP SVCBRecord::GetSelectedAlpn(nsACString& aAlpn) {
    258  if (!mAlpn) {
    259    return NS_ERROR_NOT_AVAILABLE;
    260  }
    261 
    262  aAlpn = std::get<0>(*mAlpn);
    263  return NS_OK;
    264 }
    265 
    266 NS_IMETHODIMP SVCBRecord::GetEchConfig(nsACString& aEchConfig) {
    267  aEchConfig = mData.mEchConfig;
    268  return NS_OK;
    269 }
    270 
    271 NS_IMETHODIMP SVCBRecord::GetODoHConfig(nsACString& aODoHConfig) {
    272  aODoHConfig = mData.mODoHConfig;
    273  return NS_OK;
    274 }
    275 
    276 NS_IMETHODIMP SVCBRecord::GetValues(nsTArray<RefPtr<nsISVCParam>>& aValues) {
    277  for (const auto& v : mData.mSvcFieldValue) {
    278    RefPtr<nsISVCParam> param = new SvcParam(v.mValue);
    279    aValues.AppendElement(param);
    280  }
    281  return NS_OK;
    282 }
    283 
    284 NS_IMETHODIMP SVCBRecord::GetHasIPHintAddress(bool* aHasIPHintAddress) {
    285  *aHasIPHintAddress = mData.mHasIPHints;
    286  return NS_OK;
    287 }
    288 
    289 static bool CheckRecordIsUsableWithCname(const SVCB& aRecord,
    290                                         const nsACString& aCname) {
    291  if (StaticPrefs::network_dns_https_rr_check_record_with_cname() &&
    292      !aCname.IsEmpty() && !aRecord.mSvcDomainName.Equals(aCname)) {
    293    return false;
    294  }
    295 
    296  return true;
    297 }
    298 
    299 static bool CheckRecordIsUsable(const SVCB& aRecord, nsIDNSService* aDNSService,
    300                                const nsACString& aHost,
    301                                uint32_t& aExcludedCount) {
    302  if (!aHost.IsEmpty()) {
    303    bool excluded = false;
    304    if (NS_SUCCEEDED(aDNSService->IsSVCDomainNameFailed(
    305            aHost, aRecord.mSvcDomainName, &excluded)) &&
    306        excluded) {
    307      // Skip if the domain name of this record was failed to connect before.
    308      ++aExcludedCount;
    309      return false;
    310    }
    311  }
    312 
    313  Maybe<uint16_t> port = aRecord.GetPort();
    314  if (port && *port == 0) {
    315    // Found an unsafe port, skip this record.
    316    return false;
    317  }
    318 
    319  return true;
    320 }
    321 
    322 static bool CheckAlpnIsUsable(SupportedAlpnRank aAlpnType, bool aNoHttp2,
    323                              bool aNoHttp3, bool aCheckHttp3ExcludedList,
    324                              const nsACString& aTargetName,
    325                              uint32_t& aExcludedCount) {
    326  // Skip if this alpn is not supported.
    327  if (aAlpnType == SupportedAlpnRank::NOT_SUPPORTED) {
    328    return false;
    329  }
    330 
    331  // Skip if we don't want to use http2.
    332  if (aNoHttp2 && aAlpnType == SupportedAlpnRank::HTTP_2) {
    333    return false;
    334  }
    335 
    336  if (IsHttp3(aAlpnType)) {
    337    if (aCheckHttp3ExcludedList && gHttpHandler->IsHttp3Excluded(aTargetName)) {
    338      aExcludedCount++;
    339      return false;
    340    }
    341 
    342    if (aNoHttp3) {
    343      return false;
    344    }
    345  }
    346 
    347  return true;
    348 }
    349 
    350 static nsTArray<SVCBWrapper> FlattenRecords(const nsACString& aHost,
    351                                            const nsTArray<SVCB>& aRecords,
    352                                            uint32_t& aH3RecordCount) {
    353  nsTArray<SVCBWrapper> result;
    354  aH3RecordCount = 0;
    355  for (const auto& record : aRecords) {
    356    bool hasNoDefaultAlpn = false;
    357    nsTArray<std::tuple<nsCString, SupportedAlpnRank>> alpnList =
    358        record.GetAllAlpn(hasNoDefaultAlpn);
    359    if (alpnList.IsEmpty()) {
    360      result.AppendElement(SVCBWrapper(record));
    361    } else {
    362      bool h1AlpnAdded = false;
    363      if (!hasNoDefaultAlpn) {
    364        // Consider two scenarios when "no-default-alpn" is not found:
    365        // 1. If echConfig is present in the record:
    366        //    - Firefox should always attempt to connect using echConfig without
    367        //    fallback.
    368        //    - Therefore, we add an additional record with an empty ALPN to
    369        //    allow Firefox to retry using HTTP/1.1 or h2 with echConfig.
    370        //
    371        // 2. If echConfig is not present in the record::
    372        //    - We allow fallback to connections that do not use HTTPS RR.
    373        //    - In this case, adding another record with the same target name as
    374        //    the host name is unnecessary.
    375        if (!aHost.Equals(record.mSvcDomainName) || record.mHasEchConfig) {
    376          alpnList.AppendElement(
    377              std::make_tuple(""_ns, SupportedAlpnRank::HTTP_1_1));
    378          h1AlpnAdded = true;
    379        }
    380      }
    381      for (const auto& alpn : alpnList) {
    382        const auto alpnRank = std::get<1>(alpn);
    383        if (IsHttp3(alpnRank)) {
    384          aH3RecordCount++;
    385        }
    386 
    387        // Skip explicit h2 if h1 is already present.
    388        if (alpnRank == SupportedAlpnRank::HTTP_2 && h1AlpnAdded) {
    389          continue;
    390        }
    391 
    392        // For h2, normalize the tuple to use an empty ALPN. If both h1 and h2
    393        // are available, the ALPN negotiation will automatically select h2 when
    394        // the server supports it, otherwise it will fall back to h1.
    395        // However, if `no-default-alpn` is present, fallback to h1 is not
    396        // possible, so we must explicitly set h2.
    397        auto chosen =
    398            (alpnRank == SupportedAlpnRank::HTTP_2 && !hasNoDefaultAlpn)
    399                ? std::make_tuple(""_ns, alpnRank)
    400                : alpn;
    401        SVCBWrapper wrapper(record);
    402        wrapper.mAlpn = Some(chosen);
    403        result.AppendElement(std::move(wrapper));
    404      }
    405    }
    406  }
    407  return result;
    408 }
    409 
    410 static void TelemetryForServiceModeRecord(const nsACString& aKey) {
    411 #ifndef ANDROID
    412  glean::networking::https_record_state.Get(aKey).Add(1);
    413 #endif
    414 }
    415 
    416 already_AddRefed<nsISVCBRecord>
    417 DNSHTTPSSVCRecordBase::GetServiceModeRecordInternal(
    418    bool aNoHttp2, bool aNoHttp3, const nsTArray<SVCB>& aRecords,
    419    bool& aRecordsAllExcluded, bool aCheckHttp3ExcludedList,
    420    const nsACString& aCname) {
    421  RefPtr<SVCBRecord> selectedRecord;
    422  RefPtr<SVCBRecord> h3RecordWithEchConfig;
    423  uint32_t recordHasNoDefaultAlpnCount = 0;
    424  uint32_t recordExcludedCount = 0;
    425  uint32_t recordHasUnmatchedCname = 0;
    426  aRecordsAllExcluded = false;
    427  nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
    428  uint32_t h3ExcludedCount = 0;
    429  uint32_t h3RecordCount = 0;
    430  nsTArray<SVCBWrapper> records =
    431      FlattenRecords(mHost, aRecords, h3RecordCount);
    432  for (const auto& record : records) {
    433    if (record.mRecord.mSvcFieldPriority == 0) {
    434      // In ServiceMode, the SvcPriority should never be 0.
    435      TelemetryForServiceModeRecord("invalid"_ns);
    436      return nullptr;
    437    }
    438 
    439    if (record.mRecord.NoDefaultAlpn()) {
    440      ++recordHasNoDefaultAlpnCount;
    441    }
    442 
    443    if (!CheckRecordIsUsable(record.mRecord, dns, mHost, recordExcludedCount)) {
    444      // Skip if this record is not usable.
    445      continue;
    446    }
    447 
    448    if (!CheckRecordIsUsableWithCname(record.mRecord, aCname)) {
    449      recordHasUnmatchedCname++;
    450      continue;
    451    }
    452 
    453    if (record.mAlpn) {
    454      if (!CheckAlpnIsUsable(std::get<1>(*(record.mAlpn)), aNoHttp2, aNoHttp3,
    455                             aCheckHttp3ExcludedList,
    456                             record.mRecord.mSvcDomainName, h3ExcludedCount)) {
    457        continue;
    458      }
    459 
    460      if (IsHttp3(std::get<1>(*(record.mAlpn)))) {
    461        // If the selected alpn is h3 and ech for h3 is disabled, we want
    462        // to find out if there is another non-h3 record that has
    463        // echConfig. If yes, we'll use the non-h3 record with echConfig
    464        // to connect. If not, we'll use h3 to connect without echConfig.
    465        if (record.mRecord.mHasEchConfig &&
    466            (nsHttpHandler::EchConfigEnabled() &&
    467             !nsHttpHandler::EchConfigEnabled(true))) {
    468          if (!h3RecordWithEchConfig) {
    469            // Save this h3 record for later use.
    470            h3RecordWithEchConfig =
    471                new SVCBRecord(record.mRecord, record.mAlpn);
    472            // Make sure the next record is not h3.
    473            aNoHttp3 = true;
    474            continue;
    475          }
    476        }
    477      }
    478    }
    479 
    480    if (!selectedRecord) {
    481      selectedRecord = new SVCBRecord(record.mRecord, record.mAlpn);
    482    }
    483  }
    484 
    485  if (!selectedRecord && !h3RecordWithEchConfig) {
    486    // If all records indicate "no-default-alpn", we should not use this RRSet.
    487    if (recordHasNoDefaultAlpnCount == records.Length()) {
    488      TelemetryForServiceModeRecord("no_default_alpn"_ns);
    489      return nullptr;
    490    }
    491 
    492    if (recordExcludedCount == records.Length()) {
    493      aRecordsAllExcluded = true;
    494      TelemetryForServiceModeRecord("all_excluded"_ns);
    495      return nullptr;
    496    }
    497 
    498    if (recordHasUnmatchedCname == records.Length()) {
    499      TelemetryForServiceModeRecord("unmatched_cname"_ns);
    500      return nullptr;
    501    }
    502 
    503    // If all records are in http3 excluded list, try again without checking the
    504    // excluded list. This is better than returning nothing.
    505    if (h3ExcludedCount && h3ExcludedCount == h3RecordCount &&
    506        aCheckHttp3ExcludedList) {
    507      return GetServiceModeRecordInternal(aNoHttp2, aNoHttp3, aRecords,
    508                                          aRecordsAllExcluded, false, aCname);
    509    }
    510  }
    511 
    512  if (h3RecordWithEchConfig) {
    513    TelemetryForServiceModeRecord("succeeded"_ns);
    514    if (selectedRecord && selectedRecord->mData.mHasEchConfig) {
    515      return selectedRecord.forget();
    516    }
    517 
    518    return h3RecordWithEchConfig.forget();
    519  }
    520 
    521  if (selectedRecord) {
    522    TelemetryForServiceModeRecord("succeeded"_ns);
    523  } else {
    524    TelemetryForServiceModeRecord("others"_ns);
    525  }
    526  return selectedRecord.forget();
    527 }
    528 
    529 void DNSHTTPSSVCRecordBase::GetAllRecordsInternal(
    530    bool aNoHttp2, bool aNoHttp3, const nsACString& aCname,
    531    const nsTArray<SVCB>& aRecords, bool aOnlyRecordsWithECH,
    532    bool* aAllRecordsHaveEchConfig, bool* aAllRecordsInH3ExcludedList,
    533    nsTArray<RefPtr<nsISVCBRecord>>& aResult, bool aCheckHttp3ExcludedList) {
    534  if (aRecords.IsEmpty()) {
    535    return;
    536  }
    537 
    538  *aAllRecordsHaveEchConfig = aRecords[0].mHasEchConfig;
    539  *aAllRecordsInH3ExcludedList = false;
    540  // The first record should have echConfig.
    541  if (aOnlyRecordsWithECH && !(*aAllRecordsHaveEchConfig)) {
    542    return;
    543  }
    544 
    545  uint32_t h3ExcludedCount = 0;
    546  uint32_t h3RecordCount = 0;
    547  nsTArray<SVCBWrapper> records =
    548      FlattenRecords(mHost, aRecords, h3RecordCount);
    549  for (const auto& record : records) {
    550    if (record.mRecord.mSvcFieldPriority == 0) {
    551      // This should not happen, since GetAllRecordsInternal()
    552      // should be called only if GetServiceModeRecordInternal() returns a
    553      // non-null record.
    554      MOZ_ASSERT(false);
    555      return;
    556    }
    557 
    558    // Records with echConfig are in front of records without echConfig, so we
    559    // don't have to continue.
    560    *aAllRecordsHaveEchConfig &= record.mRecord.mHasEchConfig;
    561    if (aOnlyRecordsWithECH && !(*aAllRecordsHaveEchConfig)) {
    562      aResult.Clear();
    563      return;
    564    }
    565 
    566    if (!CheckRecordIsUsableWithCname(record.mRecord, aCname)) {
    567      continue;
    568    }
    569 
    570    Maybe<uint16_t> port = record.mRecord.GetPort();
    571    if (port && *port == 0) {
    572      // Found an unsafe port, skip this record.
    573      continue;
    574    }
    575 
    576    if (record.mAlpn) {
    577      if (!CheckAlpnIsUsable(std::get<1>(*(record.mAlpn)), aNoHttp2, aNoHttp3,
    578                             aCheckHttp3ExcludedList,
    579                             record.mRecord.mSvcDomainName, h3ExcludedCount)) {
    580        continue;
    581      }
    582    }
    583 
    584    RefPtr<nsISVCBRecord> svcbRecord =
    585        new SVCBRecord(record.mRecord, record.mAlpn);
    586    aResult.AppendElement(svcbRecord);
    587  }
    588 
    589  // If all records are in http3 excluded list, try again without checking the
    590  // excluded list. This is better than returning nothing.
    591  if (h3ExcludedCount && h3ExcludedCount == h3RecordCount &&
    592      aCheckHttp3ExcludedList) {
    593    GetAllRecordsInternal(aNoHttp2, aNoHttp3, aCname, aRecords,
    594                          aOnlyRecordsWithECH, aAllRecordsHaveEchConfig,
    595                          aAllRecordsInH3ExcludedList, aResult, false);
    596    *aAllRecordsInH3ExcludedList = true;
    597  }
    598 }
    599 
    600 bool DNSHTTPSSVCRecordBase::HasIPAddressesInternal(
    601    const nsTArray<SVCB>& aRecords) {
    602  for (const SVCB& record : aRecords) {
    603    if (record.mSvcFieldPriority != 0) {
    604      for (const auto& value : record.mSvcFieldValue) {
    605        if (value.mValue.is<SvcParamIpv4Hint>()) {
    606          return true;
    607        }
    608        if (value.mValue.is<SvcParamIpv6Hint>()) {
    609          return true;
    610        }
    611      }
    612    }
    613  }
    614 
    615  return false;
    616 }
    617 
    618 }  // namespace net
    619 }  // namespace mozilla