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/. */
6 #include "mozilla/net/DNS.h"
7 #include "mozilla/glean/NetwerkProtocolHttpMetrics.h"
8 #include "mozilla/StaticPrefs_network.h"
10 #include "nsHttpHandler.h"
11 #include "nsNetAddr.h"
12 #include "nsNetUtil.h"
13 #include "nsIDNSService.h"
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
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
38 explicit SvcParam(const SvcParamType
& value
) : mValue(value
) {};
41 virtual ~SvcParam() = default;
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
>())
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
; });
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
);
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
;
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
;
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
);
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
);
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
;
153 bool SVCB::operator<(const SVCB
& aOther
) const {
154 if (gHttpHandler
->EchConfigEnabled()) {
155 if (mHasEchConfig
&& !aOther
.mHasEchConfig
) {
158 if (!mHasEchConfig
&& aOther
.mHasEchConfig
) {
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"))) {
181 bool SVCB::NoDefaultAlpn() const {
182 for (const auto& value
: mSvcFieldValue
) {
183 if (value
.mValue
.is
<SvcParamKeyNoDefaultAlpn
>()) {
191 void SVCB::GetIPHints(CopyableTArray
<mozilla::net::NetAddr
>& aAddresses
) const {
192 if (mSvcFieldPriority
== 0) {
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
{
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());
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
;
246 NS_IMETHODIMP
SVCBRecord::GetName(nsACString
& aName
) {
247 aName
= mData
.mSvcDomainName
;
251 Maybe
<uint16_t> SVCBRecord::GetPort() { return mPort
; }
253 Maybe
<std::tuple
<nsCString
, SupportedAlpnRank
>> SVCBRecord::GetAlpn() {
257 NS_IMETHODIMP
SVCBRecord::GetSelectedAlpn(nsACString
& aAlpn
) {
259 return NS_ERROR_NOT_AVAILABLE
;
262 aAlpn
= std::get
<0>(*mAlpn
);
266 NS_IMETHODIMP
SVCBRecord::GetEchConfig(nsACString
& aEchConfig
) {
267 aEchConfig
= mData
.mEchConfig
;
271 NS_IMETHODIMP
SVCBRecord::GetODoHConfig(nsACString
& aODoHConfig
) {
272 aODoHConfig
= mData
.mODoHConfig
;
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
);
284 NS_IMETHODIMP
SVCBRecord::GetHasIPHintAddress(bool* aHasIPHintAddress
) {
285 *aHasIPHintAddress
= mData
.mHasIPHints
;
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
)) {
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
)) &&
307 // Skip if the domain name of this record was failed to connect before.
313 Maybe
<uint16_t> port
= aRecord
.GetPort();
314 if (port
&& *port
== 0) {
315 // Found an unsafe port, skip this record.
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
) {
331 // Skip if we don't want to use http2.
332 if (aNoHttp2
&& aAlpnType
== SupportedAlpnRank::HTTP_2
) {
336 if (IsHttp3(aAlpnType
)) {
337 if (aCheckHttp3ExcludedList
&& gHttpHandler
->IsHttp3Excluded(aTargetName
)) {
350 static nsTArray
<SVCBWrapper
> FlattenRecords(const nsACString
& aHost
,
351 const nsTArray
<SVCB
>& aRecords
,
352 uint32_t& aH3RecordCount
) {
353 nsTArray
<SVCBWrapper
> result
;
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
));
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
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
))) {
385 result
.AppendElement(wrapper
);
392 static void TelemetryForServiceModeRecord(const nsACString
& aKey
) {
394 glean::networking::https_record_state
.Get(aKey
).Add(1);
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
);
421 if (record
.mRecord
.NoDefaultAlpn()) {
422 ++recordHasNoDefaultAlpnCount
;
425 if (!CheckRecordIsUsable(record
.mRecord
, dns
, mHost
, recordExcludedCount
)) {
426 // Skip if this record is not usable.
430 if (!CheckRecordIsUsableWithCname(record
.mRecord
, aCname
)) {
431 recordHasUnmatchedCname
++;
436 if (!CheckAlpnIsUsable(std::get
<1>(*(record
.mAlpn
)), aNoHttp2
, aNoHttp3
,
437 aCheckHttp3ExcludedList
,
438 record
.mRecord
.mSvcDomainName
, h3ExcludedCount
)) {
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.
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
);
474 if (recordExcludedCount
== records
.Length()) {
475 aRecordsAllExcluded
= true;
476 TelemetryForServiceModeRecord("all_excluded"_ns
);
480 if (recordHasUnmatchedCname
== records
.Length()) {
481 TelemetryForServiceModeRecord("unmatched_cname"_ns
);
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
);
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()) {
520 *aAllRecordsHaveEchConfig
= aRecords
[0].mHasEchConfig
;
521 *aAllRecordsInH3ExcludedList
= false;
522 // The first record should have echConfig.
523 if (aOnlyRecordsWithECH
&& !(*aAllRecordsHaveEchConfig
)) {
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
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
)) {
548 if (!CheckRecordIsUsableWithCname(record
.mRecord
, aCname
)) {
552 Maybe
<uint16_t> port
= record
.mRecord
.GetPort();
553 if (port
&& *port
== 0) {
554 // Found an unsafe port, skip this record.
559 if (!CheckAlpnIsUsable(std::get
<1>(*(record
.mAlpn
)), aNoHttp2
, aNoHttp3
,
560 aCheckHttp3ExcludedList
,
561 record
.mRecord
.mSvcDomainName
, h3ExcludedCount
)) {
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
>()) {
590 if (value
.mValue
.is
<SvcParamIpv6Hint
>()) {
601 } // namespace mozilla