Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / netwerk / dns / HTTPSSVC.cpp
blob4b245c44ceafed748214d6c1ec42db12790c004f
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/. */
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"
15 namespace mozilla {
16 namespace net {
18 NS_IMPL_ISUPPORTS(SVCBRecord, nsISVCBRecord)
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) {};
40 private:
41 virtual ~SvcParam() = default;
42 SvcParamType mValue;
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
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;
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;
84 aAlpn.AppendElements(mValue.as<SvcParamAlpn>().mValue);
85 return NS_OK;
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;
94 *aPort = mValue.as<SvcParamPort>().mValue;
95 return NS_OK;
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;
104 aEchConfig = mValue.as<SvcParamEchConfig>().mValue;
105 return NS_OK;
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;
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;
119 RefPtr<nsINetAddr> hint = new nsNetAddr(&ip);
120 aIpv4Hint.AppendElement(hint);
123 return NS_OK;
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;
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;
137 RefPtr<nsINetAddr> hint = new nsNetAddr(&ip);
138 aIpv6Hint.AppendElement(hint);
140 return NS_OK;
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;
149 aODoHConfig = mValue.as<SvcParamODoHConfig>().mValue;
150 return NS_OK;
153 bool SVCB::operator<(const SVCB& aOther) const {
154 if (gHttpHandler->EchConfigEnabled()) {
155 if (mHasEchConfig && !aOther.mHasEchConfig) {
156 return true;
158 if (!mHasEchConfig && aOther.mHasEchConfig) {
159 return false;
163 return mSvcFieldPriority < aOther.mSvcFieldPriority;
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;
174 return port;
178 return Nothing();
181 bool SVCB::NoDefaultAlpn() const {
182 for (const auto& value : mSvcFieldValue) {
183 if (value.mValue.is<SvcParamKeyNoDefaultAlpn>()) {
184 return true;
188 return false;
191 void SVCB::GetIPHints(CopyableTArray<mozilla::net::NetAddr>& aAddresses) const {
192 if (mSvcFieldPriority == 0) {
193 return;
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);
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);
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);
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)));
226 } else if (value.mValue.is<SvcParamKeyNoDefaultAlpn>()) {
227 // Found "no-default-alpn".
228 aHasNoDefaultAlpn = true;
231 alpnList.Sort(AlpnComparator());
232 return alpnList;
235 SVCBRecord::SVCBRecord(const SVCB& data,
236 Maybe<std::tuple<nsCString, SupportedAlpnRank>> aAlpn)
237 : mData(data), mAlpn(aAlpn) {
238 mPort = mData.GetPort();
241 NS_IMETHODIMP SVCBRecord::GetPriority(uint16_t* aPriority) {
242 *aPriority = mData.mSvcFieldPriority;
243 return NS_OK;
246 NS_IMETHODIMP SVCBRecord::GetName(nsACString& aName) {
247 aName = mData.mSvcDomainName;
248 return NS_OK;
251 Maybe<uint16_t> SVCBRecord::GetPort() { return mPort; }
253 Maybe<std::tuple<nsCString, SupportedAlpnRank>> SVCBRecord::GetAlpn() {
254 return mAlpn;
257 NS_IMETHODIMP SVCBRecord::GetSelectedAlpn(nsACString& aAlpn) {
258 if (!mAlpn) {
259 return NS_ERROR_NOT_AVAILABLE;
262 aAlpn = std::get<0>(*mAlpn);
263 return NS_OK;
266 NS_IMETHODIMP SVCBRecord::GetEchConfig(nsACString& aEchConfig) {
267 aEchConfig = mData.mEchConfig;
268 return NS_OK;
271 NS_IMETHODIMP SVCBRecord::GetODoHConfig(nsACString& aODoHConfig) {
272 aODoHConfig = mData.mODoHConfig;
273 return NS_OK;
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);
281 return NS_OK;
284 NS_IMETHODIMP SVCBRecord::GetHasIPHintAddress(bool* aHasIPHintAddress) {
285 *aHasIPHintAddress = mData.mHasIPHints;
286 return NS_OK;
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;
296 return true;
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;
313 Maybe<uint16_t> port = aRecord.GetPort();
314 if (port && *port == 0) {
315 // Found an unsafe port, skip this record.
316 return false;
319 return true;
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;
331 // Skip if we don't want to use http2.
332 if (aNoHttp2 && aAlpnType == SupportedAlpnRank::HTTP_2) {
333 return false;
336 if (IsHttp3(aAlpnType)) {
337 if (aCheckHttp3ExcludedList && gHttpHandler->IsHttp3Excluded(aTargetName)) {
338 aExcludedCount++;
339 return false;
342 if (aNoHttp3) {
343 return false;
347 return true;
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 if (!hasNoDefaultAlpn) {
363 // Consider two scenarios when "no-default-alpn" is not found:
364 // 1. If echConfig is present in the record:
365 // - Firefox should always attempt to connect using echConfig without
366 // fallback.
367 // - Therefore, we add an additional record with an empty ALPN to
368 // allow Firefox to retry using HTTP/1.1 or h2 with echConfig.
370 // 2. If echConfig is not present in the record::
371 // - We allow fallback to connections that do not use HTTPS RR.
372 // - In this case, adding another record with the same target name as
373 // the host name is unnecessary.
374 if (!aHost.Equals(record.mSvcDomainName) || record.mHasEchConfig) {
375 alpnList.AppendElement(
376 std::make_tuple(""_ns, SupportedAlpnRank::HTTP_1_1));
379 for (const auto& alpn : alpnList) {
380 SVCBWrapper wrapper(record);
381 wrapper.mAlpn = Some(alpn);
382 if (IsHttp3(std::get<1>(alpn))) {
383 aH3RecordCount++;
385 result.AppendElement(wrapper);
389 return result;
392 static void TelemetryForServiceModeRecord(const nsACString& aKey) {
393 #ifndef ANDROID
394 glean::networking::https_record_state.Get(aKey).Add(1);
395 #endif
398 already_AddRefed<nsISVCBRecord>
399 DNSHTTPSSVCRecordBase::GetServiceModeRecordInternal(
400 bool aNoHttp2, bool aNoHttp3, const nsTArray<SVCB>& aRecords,
401 bool& aRecordsAllExcluded, bool aCheckHttp3ExcludedList,
402 const nsACString& aCname) {
403 RefPtr<SVCBRecord> selectedRecord;
404 RefPtr<SVCBRecord> h3RecordWithEchConfig;
405 uint32_t recordHasNoDefaultAlpnCount = 0;
406 uint32_t recordExcludedCount = 0;
407 uint32_t recordHasUnmatchedCname = 0;
408 aRecordsAllExcluded = false;
409 nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
410 uint32_t h3ExcludedCount = 0;
411 uint32_t h3RecordCount = 0;
412 nsTArray<SVCBWrapper> records =
413 FlattenRecords(mHost, aRecords, h3RecordCount);
414 for (const auto& record : records) {
415 if (record.mRecord.mSvcFieldPriority == 0) {
416 // In ServiceMode, the SvcPriority should never be 0.
417 TelemetryForServiceModeRecord("invalid"_ns);
418 return nullptr;
421 if (record.mRecord.NoDefaultAlpn()) {
422 ++recordHasNoDefaultAlpnCount;
425 if (!CheckRecordIsUsable(record.mRecord, dns, mHost, recordExcludedCount)) {
426 // Skip if this record is not usable.
427 continue;
430 if (!CheckRecordIsUsableWithCname(record.mRecord, aCname)) {
431 recordHasUnmatchedCname++;
432 continue;
435 if (record.mAlpn) {
436 if (!CheckAlpnIsUsable(std::get<1>(*(record.mAlpn)), aNoHttp2, aNoHttp3,
437 aCheckHttp3ExcludedList,
438 record.mRecord.mSvcDomainName, h3ExcludedCount)) {
439 continue;
442 if (IsHttp3(std::get<1>(*(record.mAlpn)))) {
443 // If the selected alpn is h3 and ech for h3 is disabled, we want
444 // to find out if there is another non-h3 record that has
445 // echConfig. If yes, we'll use the non-h3 record with echConfig
446 // to connect. If not, we'll use h3 to connect without echConfig.
447 if (record.mRecord.mHasEchConfig &&
448 (gHttpHandler->EchConfigEnabled() &&
449 !gHttpHandler->EchConfigEnabled(true))) {
450 if (!h3RecordWithEchConfig) {
451 // Save this h3 record for later use.
452 h3RecordWithEchConfig =
453 new SVCBRecord(record.mRecord, record.mAlpn);
454 // Make sure the next record is not h3.
455 aNoHttp3 = true;
456 continue;
462 if (!selectedRecord) {
463 selectedRecord = new SVCBRecord(record.mRecord, record.mAlpn);
467 if (!selectedRecord && !h3RecordWithEchConfig) {
468 // If all records indicate "no-default-alpn", we should not use this RRSet.
469 if (recordHasNoDefaultAlpnCount == records.Length()) {
470 TelemetryForServiceModeRecord("no_default_alpn"_ns);
471 return nullptr;
474 if (recordExcludedCount == records.Length()) {
475 aRecordsAllExcluded = true;
476 TelemetryForServiceModeRecord("all_excluded"_ns);
477 return nullptr;
480 if (recordHasUnmatchedCname == records.Length()) {
481 TelemetryForServiceModeRecord("unmatched_cname"_ns);
482 return nullptr;
485 // If all records are in http3 excluded list, try again without checking the
486 // excluded list. This is better than returning nothing.
487 if (h3ExcludedCount && h3ExcludedCount == h3RecordCount &&
488 aCheckHttp3ExcludedList) {
489 return GetServiceModeRecordInternal(aNoHttp2, aNoHttp3, aRecords,
490 aRecordsAllExcluded, false, aCname);
494 if (h3RecordWithEchConfig) {
495 TelemetryForServiceModeRecord("succeeded"_ns);
496 if (selectedRecord && selectedRecord->mData.mHasEchConfig) {
497 return selectedRecord.forget();
500 return h3RecordWithEchConfig.forget();
503 if (selectedRecord) {
504 TelemetryForServiceModeRecord("succeeded"_ns);
505 } else {
506 TelemetryForServiceModeRecord("others"_ns);
508 return selectedRecord.forget();
511 void DNSHTTPSSVCRecordBase::GetAllRecordsInternal(
512 bool aNoHttp2, bool aNoHttp3, const nsACString& aCname,
513 const nsTArray<SVCB>& aRecords, bool aOnlyRecordsWithECH,
514 bool* aAllRecordsHaveEchConfig, bool* aAllRecordsInH3ExcludedList,
515 nsTArray<RefPtr<nsISVCBRecord>>& aResult, bool aCheckHttp3ExcludedList) {
516 if (aRecords.IsEmpty()) {
517 return;
520 *aAllRecordsHaveEchConfig = aRecords[0].mHasEchConfig;
521 *aAllRecordsInH3ExcludedList = false;
522 // The first record should have echConfig.
523 if (aOnlyRecordsWithECH && !(*aAllRecordsHaveEchConfig)) {
524 return;
527 uint32_t h3ExcludedCount = 0;
528 uint32_t h3RecordCount = 0;
529 nsTArray<SVCBWrapper> records =
530 FlattenRecords(mHost, aRecords, h3RecordCount);
531 for (const auto& record : records) {
532 if (record.mRecord.mSvcFieldPriority == 0) {
533 // This should not happen, since GetAllRecordsInternal()
534 // should be called only if GetServiceModeRecordInternal() returns a
535 // non-null record.
536 MOZ_ASSERT(false);
537 return;
540 // Records with echConfig are in front of records without echConfig, so we
541 // don't have to continue.
542 *aAllRecordsHaveEchConfig &= record.mRecord.mHasEchConfig;
543 if (aOnlyRecordsWithECH && !(*aAllRecordsHaveEchConfig)) {
544 aResult.Clear();
545 return;
548 if (!CheckRecordIsUsableWithCname(record.mRecord, aCname)) {
549 continue;
552 Maybe<uint16_t> port = record.mRecord.GetPort();
553 if (port && *port == 0) {
554 // Found an unsafe port, skip this record.
555 continue;
558 if (record.mAlpn) {
559 if (!CheckAlpnIsUsable(std::get<1>(*(record.mAlpn)), aNoHttp2, aNoHttp3,
560 aCheckHttp3ExcludedList,
561 record.mRecord.mSvcDomainName, h3ExcludedCount)) {
562 continue;
566 RefPtr<nsISVCBRecord> svcbRecord =
567 new SVCBRecord(record.mRecord, record.mAlpn);
568 aResult.AppendElement(svcbRecord);
571 // If all records are in http3 excluded list, try again without checking the
572 // excluded list. This is better than returning nothing.
573 if (h3ExcludedCount && h3ExcludedCount == h3RecordCount &&
574 aCheckHttp3ExcludedList) {
575 GetAllRecordsInternal(aNoHttp2, aNoHttp3, aCname, aRecords,
576 aOnlyRecordsWithECH, aAllRecordsHaveEchConfig,
577 aAllRecordsInH3ExcludedList, aResult, false);
578 *aAllRecordsInH3ExcludedList = true;
582 bool DNSHTTPSSVCRecordBase::HasIPAddressesInternal(
583 const nsTArray<SVCB>& aRecords) {
584 for (const SVCB& record : aRecords) {
585 if (record.mSvcFieldPriority != 0) {
586 for (const auto& value : record.mSvcFieldValue) {
587 if (value.mValue.is<SvcParamIpv4Hint>()) {
588 return true;
590 if (value.mValue.is<SvcParamIpv6Hint>()) {
591 return true;
597 return false;
600 } // namespace net
601 } // namespace mozilla