Bug 1876541 - Remove an assert that doesn't hold for svg roots.
[gecko.git] / netwerk / dns / HTTPSSVC.cpp
blobd36bdf91a26da96e87670573a185ed78dc4c002e
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 "nsHttp.h"
8 #include "nsHttpHandler.h"
9 #include "nsNetAddr.h"
10 #include "nsNetUtil.h"
11 #include "nsIDNSService.h"
13 namespace mozilla {
14 namespace net {
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
27 NS_DECL_NSISVCPARAM
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
35 public:
36 explicit SvcParam(const SvcParamType& value) : mValue(value){};
38 private:
39 virtual ~SvcParam() = default;
40 SvcParamType mValue;
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>())
60 NS_INTERFACE_MAP_END
62 NS_IMETHODIMP
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; });
73 return NS_OK;
76 NS_IMETHODIMP
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);
83 return NS_OK;
86 NS_IMETHODIMP
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;
93 return NS_OK;
96 NS_IMETHODIMP
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;
103 return NS_OK;
106 NS_IMETHODIMP
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);
121 return NS_OK;
124 NS_IMETHODIMP
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);
138 return NS_OK;
141 NS_IMETHODIMP
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;
148 return NS_OK;
151 bool SVCB::operator<(const SVCB& aOther) const {
152 if (gHttpHandler->EchConfigEnabled()) {
153 if (mHasEchConfig && !aOther.mHasEchConfig) {
154 return true;
156 if (!mHasEchConfig && aOther.mHasEchConfig) {
157 return false;
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"))) {
170 *port = 0;
172 return port;
176 return Nothing();
179 bool SVCB::NoDefaultAlpn() const {
180 for (const auto& value : mSvcFieldValue) {
181 if (value.mValue.is<SvcParamKeyNoDefaultAlpn>()) {
182 return true;
186 return false;
189 void SVCB::GetIPHints(CopyableTArray<mozilla::net::NetAddr>& aAddresses) const {
190 if (mSvcFieldPriority == 0) {
191 return;
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 {
204 public:
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());
225 return alpnList;
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;
236 return NS_OK;
239 NS_IMETHODIMP SVCBRecord::GetName(nsACString& aName) {
240 aName = mData.mSvcDomainName;
241 return NS_OK;
244 Maybe<uint16_t> SVCBRecord::GetPort() { return mPort; }
246 Maybe<std::tuple<nsCString, SupportedAlpnRank>> SVCBRecord::GetAlpn() {
247 return mAlpn;
250 NS_IMETHODIMP SVCBRecord::GetSelectedAlpn(nsACString& aAlpn) {
251 if (!mAlpn) {
252 return NS_ERROR_NOT_AVAILABLE;
255 aAlpn = std::get<0>(*mAlpn);
256 return NS_OK;
259 NS_IMETHODIMP SVCBRecord::GetEchConfig(nsACString& aEchConfig) {
260 aEchConfig = mData.mEchConfig;
261 return NS_OK;
264 NS_IMETHODIMP SVCBRecord::GetODoHConfig(nsACString& aODoHConfig) {
265 aODoHConfig = mData.mODoHConfig;
266 return NS_OK;
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);
274 return NS_OK;
277 NS_IMETHODIMP SVCBRecord::GetHasIPHintAddress(bool* aHasIPHintAddress) {
278 *aHasIPHintAddress = mData.mHasIPHints;
279 return NS_OK;
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)) &&
289 excluded) {
290 // Skip if the domain name of this record was failed to connect before.
291 ++aExcludedCount;
292 return false;
296 Maybe<uint16_t> port = aRecord.GetPort();
297 if (port && *port == 0) {
298 // Found an unsafe port, skip this record.
299 return false;
302 return true;
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) {
311 return false;
314 // Skip if we don't want to use http2.
315 if (aNoHttp2 && aAlpnType == SupportedAlpnRank::HTTP_2) {
316 return false;
319 if (IsHttp3(aAlpnType)) {
320 if (aCheckHttp3ExcludedList && gHttpHandler->IsHttp3Excluded(aTargetName)) {
321 aExcludedCount++;
322 return false;
325 if (aNoHttp3) {
326 return false;
330 return true;
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 =
337 record.GetAllAlpn();
338 if (alpnList.IsEmpty()) {
339 result.AppendElement(SVCBWrapper(record));
340 } else {
341 for (const auto& alpn : alpnList) {
342 SVCBWrapper wrapper(record);
343 wrapper.mAlpn = Some(alpn);
344 result.AppendElement(wrapper);
348 return result;
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.
367 return nullptr;
370 if (record.mRecord.NoDefaultAlpn()) {
371 ++recordHasNoDefaultAlpnCount;
374 if (!CheckRecordIsUsable(record.mRecord, dns, mHost, recordExcludedCount)) {
375 // Skip if this record is not usable.
376 continue;
379 if (record.mAlpn) {
380 if (!CheckAlpnIsUsable(std::get<1>(*(record.mAlpn)), aNoHttp2, aNoHttp3,
381 aCheckHttp3ExcludedList,
382 record.mRecord.mSvcDomainName, h3ExcludedCount)) {
383 continue;
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.
399 aNoHttp3 = true;
400 continue;
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()) {
414 return nullptr;
417 if (recordExcludedCount == records.Length()) {
418 aRecordsAllExcluded = true;
419 return nullptr;
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()) {
446 return;
449 *aAllRecordsHaveEchConfig = aRecords[0].mHasEchConfig;
450 *aAllRecordsInH3ExcludedList = false;
451 // The first record should have echConfig.
452 if (!(*aAllRecordsHaveEchConfig)) {
453 return;
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
462 // non-null record.
463 MOZ_ASSERT(false);
464 return;
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)) {
471 aResult.Clear();
472 return;
475 Maybe<uint16_t> port = record.mRecord.GetPort();
476 if (port && *port == 0) {
477 // Found an unsafe port, skip this record.
478 continue;
481 if (record.mAlpn) {
482 if (!CheckAlpnIsUsable(std::get<1>(*(record.mAlpn)), aNoHttp2, aNoHttp3,
483 aCheckHttp3ExcludedList,
484 record.mRecord.mSvcDomainName, h3ExcludedCount)) {
485 continue;
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>()) {
510 return true;
512 if (value.mValue.is<SvcParamIpv6Hint>()) {
513 return true;
519 return false;
522 } // namespace net
523 } // namespace mozilla