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"
8 #include "nsHttpHandler.h"
10 #include "nsNetUtil.h"
11 #include "nsIDNSService.h"
16 NS_IMPL_ISUPPORTS(SVCBRecord
, nsISVCBRecord
)
18 class SvcParam
: public nsISVCParam
,
19 public nsISVCParamAlpn
,
20 public nsISVCParamNoDefaultAlpn
,
21 public nsISVCParamPort
,
22 public nsISVCParamIPv4Hint
,
23 public nsISVCParamEchConfig
,
24 public nsISVCParamIPv6Hint
,
25 public nsISVCParamODoHConfig
{
26 NS_DECL_THREADSAFE_ISUPPORTS
28 NS_DECL_NSISVCPARAMALPN
29 NS_DECL_NSISVCPARAMNODEFAULTALPN
30 NS_DECL_NSISVCPARAMPORT
31 NS_DECL_NSISVCPARAMIPV4HINT
32 NS_DECL_NSISVCPARAMECHCONFIG
33 NS_DECL_NSISVCPARAMIPV6HINT
34 NS_DECL_NSISVCPARAMODOHCONFIG
36 explicit SvcParam(const SvcParamType
& value
) : mValue(value
){};
39 virtual ~SvcParam() = default;
43 NS_IMPL_ADDREF(SvcParam
)
44 NS_IMPL_RELEASE(SvcParam
)
45 NS_INTERFACE_MAP_BEGIN(SvcParam
)
46 NS_INTERFACE_MAP_ENTRY(nsISVCParam
)
47 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsISVCParam
)
48 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamAlpn
, mValue
.is
<SvcParamAlpn
>())
49 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamNoDefaultAlpn
,
50 mValue
.is
<SvcParamNoDefaultAlpn
>())
51 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamPort
, mValue
.is
<SvcParamPort
>())
52 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamIPv4Hint
,
53 mValue
.is
<SvcParamIpv4Hint
>())
54 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamEchConfig
,
55 mValue
.is
<SvcParamEchConfig
>())
56 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamIPv6Hint
,
57 mValue
.is
<SvcParamIpv6Hint
>())
58 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISVCParamODoHConfig
,
59 mValue
.is
<SvcParamODoHConfig
>())
63 SvcParam::GetType(uint16_t* aType
) {
64 *aType
= mValue
.match(
65 [](Nothing
&) { return SvcParamKeyMandatory
; },
66 [](SvcParamAlpn
&) { return SvcParamKeyAlpn
; },
67 [](SvcParamNoDefaultAlpn
&) { return SvcParamKeyNoDefaultAlpn
; },
68 [](SvcParamPort
&) { return SvcParamKeyPort
; },
69 [](SvcParamIpv4Hint
&) { return SvcParamKeyIpv4Hint
; },
70 [](SvcParamEchConfig
&) { return SvcParamKeyEchConfig
; },
71 [](SvcParamIpv6Hint
&) { return SvcParamKeyIpv6Hint
; },
72 [](SvcParamODoHConfig
&) { return SvcParamKeyODoHConfig
; });
77 SvcParam::GetAlpn(nsTArray
<nsCString
>& aAlpn
) {
78 if (!mValue
.is
<SvcParamAlpn
>()) {
79 MOZ_ASSERT(false, "Unexpected type for variant");
80 return NS_ERROR_NOT_AVAILABLE
;
82 aAlpn
.AppendElements(mValue
.as
<SvcParamAlpn
>().mValue
);
87 SvcParam::GetPort(uint16_t* aPort
) {
88 if (!mValue
.is
<SvcParamPort
>()) {
89 MOZ_ASSERT(false, "Unexpected type for variant");
90 return NS_ERROR_NOT_AVAILABLE
;
92 *aPort
= mValue
.as
<SvcParamPort
>().mValue
;
97 SvcParam::GetEchconfig(nsACString
& aEchConfig
) {
98 if (!mValue
.is
<SvcParamEchConfig
>()) {
99 MOZ_ASSERT(false, "Unexpected type for variant");
100 return NS_ERROR_NOT_AVAILABLE
;
102 aEchConfig
= mValue
.as
<SvcParamEchConfig
>().mValue
;
107 SvcParam::GetIpv4Hint(nsTArray
<RefPtr
<nsINetAddr
>>& aIpv4Hint
) {
108 if (!mValue
.is
<SvcParamIpv4Hint
>()) {
109 MOZ_ASSERT(false, "Unexpected type for variant");
110 return NS_ERROR_NOT_AVAILABLE
;
112 const auto& results
= mValue
.as
<SvcParamIpv4Hint
>().mValue
;
113 for (const auto& ip
: results
) {
114 if (ip
.raw
.family
!= AF_INET
) {
115 return NS_ERROR_UNEXPECTED
;
117 RefPtr
<nsINetAddr
> hint
= new nsNetAddr(&ip
);
118 aIpv4Hint
.AppendElement(hint
);
125 SvcParam::GetIpv6Hint(nsTArray
<RefPtr
<nsINetAddr
>>& aIpv6Hint
) {
126 if (!mValue
.is
<SvcParamIpv6Hint
>()) {
127 MOZ_ASSERT(false, "Unexpected type for variant");
128 return NS_ERROR_NOT_AVAILABLE
;
130 const auto& results
= mValue
.as
<SvcParamIpv6Hint
>().mValue
;
131 for (const auto& ip
: results
) {
132 if (ip
.raw
.family
!= AF_INET6
) {
133 return NS_ERROR_UNEXPECTED
;
135 RefPtr
<nsINetAddr
> hint
= new nsNetAddr(&ip
);
136 aIpv6Hint
.AppendElement(hint
);
142 SvcParam::GetODoHConfig(nsACString
& aODoHConfig
) {
143 if (!mValue
.is
<SvcParamODoHConfig
>()) {
144 MOZ_ASSERT(false, "Unexpected type for variant");
145 return NS_ERROR_NOT_AVAILABLE
;
147 aODoHConfig
= mValue
.as
<SvcParamODoHConfig
>().mValue
;
151 bool SVCB::operator<(const SVCB
& aOther
) const {
152 if (gHttpHandler
->EchConfigEnabled()) {
153 if (mHasEchConfig
&& !aOther
.mHasEchConfig
) {
156 if (!mHasEchConfig
&& aOther
.mHasEchConfig
) {
161 return mSvcFieldPriority
< aOther
.mSvcFieldPriority
;
164 Maybe
<uint16_t> SVCB::GetPort() const {
165 Maybe
<uint16_t> port
;
166 for (const auto& value
: mSvcFieldValue
) {
167 if (value
.mValue
.is
<SvcParamPort
>()) {
168 port
.emplace(value
.mValue
.as
<SvcParamPort
>().mValue
);
169 if (NS_FAILED(NS_CheckPortSafety(*port
, "https"))) {
179 bool SVCB::NoDefaultAlpn() const {
180 for (const auto& value
: mSvcFieldValue
) {
181 if (value
.mValue
.is
<SvcParamKeyNoDefaultAlpn
>()) {
189 void SVCB::GetIPHints(CopyableTArray
<mozilla::net::NetAddr
>& aAddresses
) const {
190 if (mSvcFieldPriority
== 0) {
194 for (const auto& value
: mSvcFieldValue
) {
195 if (value
.mValue
.is
<SvcParamIpv4Hint
>()) {
196 aAddresses
.AppendElements(value
.mValue
.as
<SvcParamIpv4Hint
>().mValue
);
197 } else if (value
.mValue
.is
<SvcParamIpv6Hint
>()) {
198 aAddresses
.AppendElements(value
.mValue
.as
<SvcParamIpv6Hint
>().mValue
);
203 class AlpnComparator
{
205 bool Equals(const std::tuple
<nsCString
, SupportedAlpnRank
>& aA
,
206 const std::tuple
<nsCString
, SupportedAlpnRank
>& aB
) const {
207 return std::get
<1>(aA
) == std::get
<1>(aB
);
209 bool LessThan(const std::tuple
<nsCString
, SupportedAlpnRank
>& aA
,
210 const std::tuple
<nsCString
, SupportedAlpnRank
>& aB
) const {
211 return std::get
<1>(aA
) > std::get
<1>(aB
);
215 nsTArray
<std::tuple
<nsCString
, SupportedAlpnRank
>> SVCB::GetAllAlpn() const {
216 nsTArray
<std::tuple
<nsCString
, SupportedAlpnRank
>> alpnList
;
217 for (const auto& value
: mSvcFieldValue
) {
218 if (value
.mValue
.is
<SvcParamAlpn
>()) {
219 for (const auto& alpn
: value
.mValue
.as
<SvcParamAlpn
>().mValue
) {
220 alpnList
.AppendElement(std::make_tuple(alpn
, IsAlpnSupported(alpn
)));
224 alpnList
.Sort(AlpnComparator());
228 SVCBRecord::SVCBRecord(const SVCB
& data
,
229 Maybe
<std::tuple
<nsCString
, SupportedAlpnRank
>> aAlpn
)
230 : mData(data
), mAlpn(aAlpn
) {
231 mPort
= mData
.GetPort();
234 NS_IMETHODIMP
SVCBRecord::GetPriority(uint16_t* aPriority
) {
235 *aPriority
= mData
.mSvcFieldPriority
;
239 NS_IMETHODIMP
SVCBRecord::GetName(nsACString
& aName
) {
240 aName
= mData
.mSvcDomainName
;
244 Maybe
<uint16_t> SVCBRecord::GetPort() { return mPort
; }
246 Maybe
<std::tuple
<nsCString
, SupportedAlpnRank
>> SVCBRecord::GetAlpn() {
250 NS_IMETHODIMP
SVCBRecord::GetSelectedAlpn(nsACString
& aAlpn
) {
252 return NS_ERROR_NOT_AVAILABLE
;
255 aAlpn
= std::get
<0>(*mAlpn
);
259 NS_IMETHODIMP
SVCBRecord::GetEchConfig(nsACString
& aEchConfig
) {
260 aEchConfig
= mData
.mEchConfig
;
264 NS_IMETHODIMP
SVCBRecord::GetODoHConfig(nsACString
& aODoHConfig
) {
265 aODoHConfig
= mData
.mODoHConfig
;
269 NS_IMETHODIMP
SVCBRecord::GetValues(nsTArray
<RefPtr
<nsISVCParam
>>& aValues
) {
270 for (const auto& v
: mData
.mSvcFieldValue
) {
271 RefPtr
<nsISVCParam
> param
= new SvcParam(v
.mValue
);
272 aValues
.AppendElement(param
);
277 NS_IMETHODIMP
SVCBRecord::GetHasIPHintAddress(bool* aHasIPHintAddress
) {
278 *aHasIPHintAddress
= mData
.mHasIPHints
;
282 static bool CheckRecordIsUsable(const SVCB
& aRecord
, nsIDNSService
* aDNSService
,
283 const nsACString
& aHost
,
284 uint32_t& aExcludedCount
) {
285 if (!aHost
.IsEmpty()) {
286 bool excluded
= false;
287 if (NS_SUCCEEDED(aDNSService
->IsSVCDomainNameFailed(
288 aHost
, aRecord
.mSvcDomainName
, &excluded
)) &&
290 // Skip if the domain name of this record was failed to connect before.
296 Maybe
<uint16_t> port
= aRecord
.GetPort();
297 if (port
&& *port
== 0) {
298 // Found an unsafe port, skip this record.
305 static bool CheckAlpnIsUsable(SupportedAlpnRank aAlpnType
, bool aNoHttp2
,
306 bool aNoHttp3
, bool aCheckHttp3ExcludedList
,
307 const nsACString
& aTargetName
,
308 uint32_t& aExcludedCount
) {
309 // Skip if this alpn is not supported.
310 if (aAlpnType
== SupportedAlpnRank::NOT_SUPPORTED
) {
314 // Skip if we don't want to use http2.
315 if (aNoHttp2
&& aAlpnType
== SupportedAlpnRank::HTTP_2
) {
319 if (IsHttp3(aAlpnType
)) {
320 if (aCheckHttp3ExcludedList
&& gHttpHandler
->IsHttp3Excluded(aTargetName
)) {
333 static nsTArray
<SVCBWrapper
> FlattenRecords(const nsTArray
<SVCB
>& aRecords
) {
334 nsTArray
<SVCBWrapper
> result
;
335 for (const auto& record
: aRecords
) {
336 nsTArray
<std::tuple
<nsCString
, SupportedAlpnRank
>> alpnList
=
338 if (alpnList
.IsEmpty()) {
339 result
.AppendElement(SVCBWrapper(record
));
341 for (const auto& alpn
: alpnList
) {
342 SVCBWrapper
wrapper(record
);
343 wrapper
.mAlpn
= Some(alpn
);
344 result
.AppendElement(wrapper
);
351 already_AddRefed
<nsISVCBRecord
>
352 DNSHTTPSSVCRecordBase::GetServiceModeRecordInternal(
353 bool aNoHttp2
, bool aNoHttp3
, const nsTArray
<SVCB
>& aRecords
,
354 bool& aRecordsAllExcluded
, bool aCheckHttp3ExcludedList
) {
355 RefPtr
<SVCBRecord
> selectedRecord
;
356 RefPtr
<SVCBRecord
> h3RecordWithEchConfig
;
357 uint32_t recordHasNoDefaultAlpnCount
= 0;
358 uint32_t recordExcludedCount
= 0;
359 aRecordsAllExcluded
= false;
360 nsCOMPtr
<nsIDNSService
> dns
= do_GetService(NS_DNSSERVICE_CONTRACTID
);
361 uint32_t h3ExcludedCount
= 0;
363 nsTArray
<SVCBWrapper
> records
= FlattenRecords(aRecords
);
364 for (const auto& record
: records
) {
365 if (record
.mRecord
.mSvcFieldPriority
== 0) {
366 // In ServiceMode, the SvcPriority should never be 0.
370 if (record
.mRecord
.NoDefaultAlpn()) {
371 ++recordHasNoDefaultAlpnCount
;
374 if (!CheckRecordIsUsable(record
.mRecord
, dns
, mHost
, recordExcludedCount
)) {
375 // Skip if this record is not usable.
380 if (!CheckAlpnIsUsable(std::get
<1>(*(record
.mAlpn
)), aNoHttp2
, aNoHttp3
,
381 aCheckHttp3ExcludedList
,
382 record
.mRecord
.mSvcDomainName
, h3ExcludedCount
)) {
386 if (IsHttp3(std::get
<1>(*(record
.mAlpn
)))) {
387 // If the selected alpn is h3 and ech for h3 is disabled, we want
388 // to find out if there is another non-h3 record that has
389 // echConfig. If yes, we'll use the non-h3 record with echConfig
390 // to connect. If not, we'll use h3 to connect without echConfig.
391 if (record
.mRecord
.mHasEchConfig
&&
392 (gHttpHandler
->EchConfigEnabled() &&
393 !gHttpHandler
->EchConfigEnabled(true))) {
394 if (!h3RecordWithEchConfig
) {
395 // Save this h3 record for later use.
396 h3RecordWithEchConfig
=
397 new SVCBRecord(record
.mRecord
, record
.mAlpn
);
398 // Make sure the next record is not h3.
406 if (!selectedRecord
) {
407 selectedRecord
= new SVCBRecord(record
.mRecord
, record
.mAlpn
);
411 if (!selectedRecord
&& !h3RecordWithEchConfig
) {
412 // If all records indicate "no-default-alpn", we should not use this RRSet.
413 if (recordHasNoDefaultAlpnCount
== records
.Length()) {
417 if (recordExcludedCount
== records
.Length()) {
418 aRecordsAllExcluded
= true;
422 // If all records are in http3 excluded list, try again without checking the
423 // excluded list. This is better than returning nothing.
424 if (h3ExcludedCount
== records
.Length() && aCheckHttp3ExcludedList
) {
425 return GetServiceModeRecordInternal(aNoHttp2
, aNoHttp3
, aRecords
,
426 aRecordsAllExcluded
, false);
430 if (h3RecordWithEchConfig
) {
431 if (selectedRecord
&& selectedRecord
->mData
.mHasEchConfig
) {
432 return selectedRecord
.forget();
435 return h3RecordWithEchConfig
.forget();
438 return selectedRecord
.forget();
441 void DNSHTTPSSVCRecordBase::GetAllRecordsWithEchConfigInternal(
442 bool aNoHttp2
, bool aNoHttp3
, const nsTArray
<SVCB
>& aRecords
,
443 bool* aAllRecordsHaveEchConfig
, bool* aAllRecordsInH3ExcludedList
,
444 nsTArray
<RefPtr
<nsISVCBRecord
>>& aResult
, bool aCheckHttp3ExcludedList
) {
445 if (aRecords
.IsEmpty()) {
449 *aAllRecordsHaveEchConfig
= aRecords
[0].mHasEchConfig
;
450 *aAllRecordsInH3ExcludedList
= false;
451 // The first record should have echConfig.
452 if (!(*aAllRecordsHaveEchConfig
)) {
456 uint32_t h3ExcludedCount
= 0;
457 nsTArray
<SVCBWrapper
> records
= FlattenRecords(aRecords
);
458 for (const auto& record
: records
) {
459 if (record
.mRecord
.mSvcFieldPriority
== 0) {
460 // This should not happen, since GetAllRecordsWithEchConfigInternal()
461 // should be called only if GetServiceModeRecordInternal() returns a
467 // Records with echConfig are in front of records without echConfig, so we
468 // don't have to continue.
469 *aAllRecordsHaveEchConfig
&= record
.mRecord
.mHasEchConfig
;
470 if (!(*aAllRecordsHaveEchConfig
)) {
475 Maybe
<uint16_t> port
= record
.mRecord
.GetPort();
476 if (port
&& *port
== 0) {
477 // Found an unsafe port, skip this record.
482 if (!CheckAlpnIsUsable(std::get
<1>(*(record
.mAlpn
)), aNoHttp2
, aNoHttp3
,
483 aCheckHttp3ExcludedList
,
484 record
.mRecord
.mSvcDomainName
, h3ExcludedCount
)) {
489 RefPtr
<nsISVCBRecord
> svcbRecord
=
490 new SVCBRecord(record
.mRecord
, record
.mAlpn
);
491 aResult
.AppendElement(svcbRecord
);
494 // If all records are in http3 excluded list, try again without checking the
495 // excluded list. This is better than returning nothing.
496 if (h3ExcludedCount
== records
.Length() && aCheckHttp3ExcludedList
) {
497 GetAllRecordsWithEchConfigInternal(
498 aNoHttp2
, aNoHttp3
, aRecords
, aAllRecordsHaveEchConfig
,
499 aAllRecordsInH3ExcludedList
, aResult
, false);
500 *aAllRecordsInH3ExcludedList
= true;
504 bool DNSHTTPSSVCRecordBase::HasIPAddressesInternal(
505 const nsTArray
<SVCB
>& aRecords
) {
506 for (const SVCB
& record
: aRecords
) {
507 if (record
.mSvcFieldPriority
!= 0) {
508 for (const auto& value
: record
.mSvcFieldValue
) {
509 if (value
.mValue
.is
<SvcParamIpv4Hint
>()) {
512 if (value
.mValue
.is
<SvcParamIpv6Hint
>()) {
523 } // namespace mozilla