xref: /aosp_15_r20/external/cronet/net/dns/dns_response_result_extractor.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/dns/dns_response_result_extractor.h"
6 
7 #include <limits.h>
8 #include <stdint.h>
9 
10 #include <iterator>
11 #include <map>
12 #include <memory>
13 #include <optional>
14 #include <ostream>
15 #include <set>
16 #include <string>
17 #include <string_view>
18 #include <unordered_set>
19 #include <vector>
20 
21 #include "base/check.h"
22 #include "base/containers/contains.h"
23 #include "base/dcheck_is_on.h"
24 #include "base/metrics/histogram_macros.h"
25 #include "base/notreached.h"
26 #include "base/numerics/checked_math.h"
27 #include "base/numerics/ostream_operators.h"
28 #include "base/rand_util.h"
29 #include "base/ranges/algorithm.h"
30 #include "base/strings/string_util.h"
31 #include "base/time/clock.h"
32 #include "base/time/time.h"
33 #include "net/base/address_list.h"
34 #include "net/base/connection_endpoint_metadata.h"
35 #include "net/base/host_port_pair.h"
36 #include "net/base/ip_address.h"
37 #include "net/base/ip_endpoint.h"
38 #include "net/base/net_errors.h"
39 #include "net/dns/dns_alias_utility.h"
40 #include "net/dns/dns_names_util.h"
41 #include "net/dns/dns_response.h"
42 #include "net/dns/dns_util.h"
43 #include "net/dns/host_cache.h"
44 #include "net/dns/host_resolver_internal_result.h"
45 #include "net/dns/https_record_rdata.h"
46 #include "net/dns/public/dns_protocol.h"
47 #include "net/dns/public/dns_query_type.h"
48 #include "net/dns/record_parsed.h"
49 #include "net/dns/record_rdata.h"
50 
51 namespace net {
52 
53 namespace {
54 
55 using AliasMap = std::map<std::string,
56                           std::unique_ptr<const RecordParsed>,
57                           dns_names_util::DomainNameComparator>;
58 using ExtractionError = DnsResponseResultExtractor::ExtractionError;
59 using RecordsOrError =
60     base::expected<std::vector<std::unique_ptr<const RecordParsed>>,
61                    ExtractionError>;
62 using ResultsOrError = DnsResponseResultExtractor::ResultsOrError;
63 using Source = HostResolverInternalResult::Source;
64 
SaveMetricsForAdditionalHttpsRecord(const RecordParsed & record,bool is_unsolicited)65 void SaveMetricsForAdditionalHttpsRecord(const RecordParsed& record,
66                                          bool is_unsolicited) {
67   const HttpsRecordRdata* rdata = record.rdata<HttpsRecordRdata>();
68   DCHECK(rdata);
69 
70   // These values are persisted to logs. Entries should not be renumbered and
71   // numeric values should never be reused.
72   enum class UnsolicitedHttpsRecordStatus {
73     kMalformed = 0,  // No longer recorded.
74     kAlias = 1,
75     kService = 2,
76     kMaxValue = kService
77   } status;
78 
79   if (rdata->IsAlias()) {
80     status = UnsolicitedHttpsRecordStatus::kAlias;
81   } else {
82     status = UnsolicitedHttpsRecordStatus::kService;
83   }
84 
85   if (is_unsolicited) {
86     UMA_HISTOGRAM_ENUMERATION("Net.DNS.DnsTask.AdditionalHttps.Unsolicited",
87                               status);
88   } else {
89     UMA_HISTOGRAM_ENUMERATION("Net.DNS.DnsTask.AdditionalHttps.Requested",
90                               status);
91   }
92 }
93 
94 // Sort service targets per RFC2782.  In summary, sort first by `priority`,
95 // lowest first.  For targets with the same priority, secondary sort randomly
96 // using `weight` with higher weighted objects more likely to go first.
SortServiceTargets(const std::vector<const SrvRecordRdata * > & rdatas)97 std::vector<HostPortPair> SortServiceTargets(
98     const std::vector<const SrvRecordRdata*>& rdatas) {
99   std::map<uint16_t, std::unordered_set<const SrvRecordRdata*>>
100       ordered_by_priority;
101   for (const SrvRecordRdata* rdata : rdatas) {
102     ordered_by_priority[rdata->priority()].insert(rdata);
103   }
104 
105   std::vector<HostPortPair> sorted_targets;
106   for (auto& priority : ordered_by_priority) {
107     // With (num results) <= UINT16_MAX (and in practice, much less) and
108     // (weight per result) <= UINT16_MAX, then it should be the case that
109     // (total weight) <= UINT32_MAX, but use CheckedNumeric for extra safety.
110     auto total_weight = base::MakeCheckedNum<uint32_t>(0);
111     for (const SrvRecordRdata* rdata : priority.second) {
112       total_weight += rdata->weight();
113     }
114 
115     // Add 1 to total weight because, to deal with 0-weight targets, we want
116     // our random selection to be inclusive [0, total].
117     total_weight++;
118 
119     // Order by weighted random. Make such random selections, removing from
120     // |priority.second| until |priority.second| only contains 1 rdata.
121     while (priority.second.size() >= 2) {
122       uint32_t random_selection =
123           base::RandGenerator(total_weight.ValueOrDie());
124       const SrvRecordRdata* selected_rdata = nullptr;
125       for (const SrvRecordRdata* rdata : priority.second) {
126         // >= to always select the first target on |random_selection| == 0,
127         // even if its weight is 0.
128         if (rdata->weight() >= random_selection) {
129           selected_rdata = rdata;
130           break;
131         }
132         random_selection -= rdata->weight();
133       }
134 
135       DCHECK(selected_rdata);
136       sorted_targets.emplace_back(selected_rdata->target(),
137                                   selected_rdata->port());
138       total_weight -= selected_rdata->weight();
139       size_t removed = priority.second.erase(selected_rdata);
140       DCHECK_EQ(1u, removed);
141     }
142 
143     DCHECK_EQ(1u, priority.second.size());
144     DCHECK_EQ((total_weight - 1).ValueOrDie(),
145               (*priority.second.begin())->weight());
146     const SrvRecordRdata* rdata = *priority.second.begin();
147     sorted_targets.emplace_back(rdata->target(), rdata->port());
148   }
149 
150   return sorted_targets;
151 }
152 
153 // Validates that all `aliases` form a single non-looping chain, starting from
154 // `query_name` and that all alias records are valid. Also validates that all
155 // `data_records` are at the final name at the end of the alias chain.
156 // TODO(crbug.com/1381506): Consider altering chain TTLs so that each TTL is
157 // less than or equal to all previous links in the chain.
ValidateNamesAndAliases(std::string_view query_name,const AliasMap & aliases,const std::vector<std::unique_ptr<const RecordParsed>> & data_records,std::string & out_final_chain_name)158 ExtractionError ValidateNamesAndAliases(
159     std::string_view query_name,
160     const AliasMap& aliases,
161     const std::vector<std::unique_ptr<const RecordParsed>>& data_records,
162     std::string& out_final_chain_name) {
163   // Validate that all aliases form a single non-looping chain, starting from
164   // `query_name`.
165   size_t aliases_in_chain = 0;
166   std::string target_name =
167       dns_names_util::UrlCanonicalizeNameIfAble(query_name);
168   for (auto alias = aliases.find(target_name);
169        alias != aliases.end() && aliases_in_chain <= aliases.size();
170        alias = aliases.find(target_name)) {
171     aliases_in_chain++;
172 
173     const CnameRecordRdata* cname_data =
174         alias->second->rdata<CnameRecordRdata>();
175     if (!cname_data) {
176       return ExtractionError::kMalformedCname;
177     }
178 
179     target_name =
180         dns_names_util::UrlCanonicalizeNameIfAble(cname_data->cname());
181     if (!dns_names_util::IsValidDnsRecordName(target_name)) {
182       return ExtractionError::kMalformedCname;
183     }
184   }
185 
186   if (aliases_in_chain != aliases.size()) {
187     return ExtractionError::kBadAliasChain;
188   }
189 
190   // All records must match final alias name.
191   for (const auto& record : data_records) {
192     DCHECK_NE(record->type(), dns_protocol::kTypeCNAME);
193     if (!base::EqualsCaseInsensitiveASCII(
194             target_name,
195             dns_names_util::UrlCanonicalizeNameIfAble(record->name()))) {
196       return ExtractionError::kNameMismatch;
197     }
198   }
199 
200   out_final_chain_name = std::move(target_name);
201   return ExtractionError::kOk;
202 }
203 
204 // Common results (aliases and errors) are extracted into
205 // `out_non_data_results`.
ExtractResponseRecords(const DnsResponse & response,DnsQueryType query_type,base::Time now,base::TimeTicks now_ticks,std::set<std::unique_ptr<HostResolverInternalResult>> & out_non_data_results)206 RecordsOrError ExtractResponseRecords(
207     const DnsResponse& response,
208     DnsQueryType query_type,
209     base::Time now,
210     base::TimeTicks now_ticks,
211     std::set<std::unique_ptr<HostResolverInternalResult>>&
212         out_non_data_results) {
213   DCHECK_EQ(response.question_count(), 1u);
214 
215   std::vector<std::unique_ptr<const RecordParsed>> data_records;
216   std::optional<base::TimeDelta> response_ttl;
217 
218   DnsRecordParser parser = response.Parser();
219 
220   // Expected to be validated by DnsTransaction.
221   DCHECK_EQ(DnsQueryTypeToQtype(query_type), response.GetSingleQType());
222 
223   AliasMap aliases;
224   for (unsigned i = 0; i < response.answer_count(); ++i) {
225     std::unique_ptr<const RecordParsed> record =
226         RecordParsed::CreateFrom(&parser, now);
227 
228     if (!record || !dns_names_util::IsValidDnsRecordName(record->name())) {
229       return base::unexpected(ExtractionError::kMalformedRecord);
230     }
231 
232     if (record->klass() == dns_protocol::kClassIN &&
233         record->type() == dns_protocol::kTypeCNAME) {
234       std::string canonicalized_name =
235           dns_names_util::UrlCanonicalizeNameIfAble(record->name());
236       DCHECK(dns_names_util::IsValidDnsRecordName(canonicalized_name));
237 
238       bool added =
239           aliases.emplace(canonicalized_name, std::move(record)).second;
240       // Per RFC2181, multiple CNAME records are not allowed for the same name.
241       if (!added) {
242         return base::unexpected(ExtractionError::kMultipleCnames);
243       }
244     } else if (record->klass() == dns_protocol::kClassIN &&
245                record->type() == DnsQueryTypeToQtype(query_type)) {
246       base::TimeDelta ttl = base::Seconds(record->ttl());
247       response_ttl =
248           std::min(response_ttl.value_or(base::TimeDelta::Max()), ttl);
249 
250       data_records.push_back(std::move(record));
251     }
252   }
253 
254   std::string final_chain_name;
255   ExtractionError name_and_alias_validation_error = ValidateNamesAndAliases(
256       response.GetSingleDottedName(), aliases, data_records, final_chain_name);
257   if (name_and_alias_validation_error != ExtractionError::kOk) {
258     return base::unexpected(name_and_alias_validation_error);
259   }
260 
261   std::set<std::unique_ptr<HostResolverInternalResult>> non_data_results;
262   for (const auto& alias : aliases) {
263     DCHECK(alias.second->rdata<CnameRecordRdata>());
264     non_data_results.insert(std::make_unique<HostResolverInternalAliasResult>(
265         alias.first, query_type, now_ticks + base::Seconds(alias.second->ttl()),
266         now + base::Seconds(alias.second->ttl()), Source::kDns,
267         alias.second->rdata<CnameRecordRdata>()->cname()));
268   }
269 
270   std::optional<base::TimeDelta> error_ttl;
271   for (unsigned i = 0; i < response.authority_count(); ++i) {
272     DnsResourceRecord record;
273     if (!parser.ReadRecord(&record)) {
274       // Stop trying to process records if things get malformed in the authority
275       // section.
276       break;
277     }
278 
279     if (record.type == dns_protocol::kTypeSOA) {
280       base::TimeDelta ttl = base::Seconds(record.ttl);
281       error_ttl = std::min(error_ttl.value_or(base::TimeDelta::Max()), ttl);
282     }
283   }
284 
285   // For NXDOMAIN or NODATA (NOERROR with 0 answers matching the qtype), cache
286   // an error if an error TTL was found from SOA records. Also, ignore the error
287   // if we somehow have result records (most likely if the server incorrectly
288   // sends NXDOMAIN with results). Note that, per the weird QNAME definition in
289   // RFC2308, section 1, as well as the clarifications in RFC6604, section 3,
290   // and in RFC8020, section 2, the cached error is specific to the final chain
291   // name, not the query name.
292   //
293   // TODO([email protected]): Differentiate nxdomain errors by making it
294   // cacheable across any query type (per RFC2308, Section 5).
295   bool is_cachable_error = data_records.empty() &&
296                            (response.rcode() == dns_protocol::kRcodeNXDOMAIN ||
297                             response.rcode() == dns_protocol::kRcodeNOERROR);
298   if (is_cachable_error && error_ttl.has_value()) {
299     non_data_results.insert(std::make_unique<HostResolverInternalErrorResult>(
300         final_chain_name, query_type, now_ticks + error_ttl.value(),
301         now + error_ttl.value(), Source::kDns, ERR_NAME_NOT_RESOLVED));
302   }
303 
304   for (unsigned i = 0; i < response.additional_answer_count(); ++i) {
305     std::unique_ptr<const RecordParsed> record =
306         RecordParsed::CreateFrom(&parser, base::Time::Now());
307     if (record && record->klass() == dns_protocol::kClassIN &&
308         record->type() == dns_protocol::kTypeHttps) {
309       bool is_unsolicited = query_type != DnsQueryType::HTTPS;
310       SaveMetricsForAdditionalHttpsRecord(*record, is_unsolicited);
311     }
312   }
313 
314   out_non_data_results = std::move(non_data_results);
315   return data_records;
316 }
317 
ExtractAddressResults(const DnsResponse & response,DnsQueryType query_type,base::Time now,base::TimeTicks now_ticks)318 ResultsOrError ExtractAddressResults(const DnsResponse& response,
319                                      DnsQueryType query_type,
320                                      base::Time now,
321                                      base::TimeTicks now_ticks) {
322   DCHECK_EQ(response.question_count(), 1u);
323   DCHECK(query_type == DnsQueryType::A || query_type == DnsQueryType::AAAA);
324 
325   std::set<std::unique_ptr<HostResolverInternalResult>> results;
326   RecordsOrError records =
327       ExtractResponseRecords(response, query_type, now, now_ticks, results);
328   if (!records.has_value()) {
329     return base::unexpected(records.error());
330   }
331 
332   std::vector<IPEndPoint> ip_endpoints;
333   auto min_ttl = base::TimeDelta::Max();
334   for (const auto& record : records.value()) {
335     IPAddress address;
336     if (query_type == DnsQueryType::A) {
337       const ARecordRdata* rdata = record->rdata<ARecordRdata>();
338       DCHECK(rdata);
339       address = rdata->address();
340       DCHECK(address.IsIPv4());
341     } else {
342       DCHECK_EQ(query_type, DnsQueryType::AAAA);
343       const AAAARecordRdata* rdata = record->rdata<AAAARecordRdata>();
344       DCHECK(rdata);
345       address = rdata->address();
346       DCHECK(address.IsIPv6());
347     }
348     ip_endpoints.emplace_back(address, /*port=*/0);
349 
350     base::TimeDelta ttl = base::Seconds(record->ttl());
351     min_ttl = std::min(ttl, min_ttl);
352   }
353 
354   if (!ip_endpoints.empty()) {
355     results.insert(std::make_unique<HostResolverInternalDataResult>(
356         records->front()->name(), query_type, now_ticks + min_ttl,
357         now + min_ttl, Source::kDns, std::move(ip_endpoints),
358         std::vector<std::string>{}, std::vector<HostPortPair>{}));
359   }
360 
361   return results;
362 }
363 
ExtractTxtResults(const DnsResponse & response,base::Time now,base::TimeTicks now_ticks)364 ResultsOrError ExtractTxtResults(const DnsResponse& response,
365                                  base::Time now,
366                                  base::TimeTicks now_ticks) {
367   std::set<std::unique_ptr<HostResolverInternalResult>> results;
368   RecordsOrError txt_records = ExtractResponseRecords(
369       response, DnsQueryType::TXT, now, now_ticks, results);
370   if (!txt_records.has_value()) {
371     return base::unexpected(txt_records.error());
372   }
373 
374   std::vector<std::string> strings;
375   base::TimeDelta min_ttl = base::TimeDelta::Max();
376   for (const auto& record : txt_records.value()) {
377     const TxtRecordRdata* rdata = record->rdata<net::TxtRecordRdata>();
378     DCHECK(rdata);
379     strings.insert(strings.end(), rdata->texts().begin(), rdata->texts().end());
380 
381     base::TimeDelta ttl = base::Seconds(record->ttl());
382     min_ttl = std::min(ttl, min_ttl);
383   }
384 
385   if (!strings.empty()) {
386     results.insert(std::make_unique<HostResolverInternalDataResult>(
387         txt_records->front()->name(), DnsQueryType::TXT, now_ticks + min_ttl,
388         now + min_ttl, Source::kDns, std::vector<IPEndPoint>{},
389         std::move(strings), std::vector<HostPortPair>{}));
390   }
391 
392   return results;
393 }
394 
ExtractPointerResults(const DnsResponse & response,base::Time now,base::TimeTicks now_ticks)395 ResultsOrError ExtractPointerResults(const DnsResponse& response,
396                                      base::Time now,
397                                      base::TimeTicks now_ticks) {
398   std::set<std::unique_ptr<HostResolverInternalResult>> results;
399   RecordsOrError ptr_records = ExtractResponseRecords(
400       response, DnsQueryType::PTR, now, now_ticks, results);
401   if (!ptr_records.has_value()) {
402     return base::unexpected(ptr_records.error());
403   }
404 
405   std::vector<HostPortPair> pointers;
406   auto min_ttl = base::TimeDelta::Max();
407   for (const auto& record : ptr_records.value()) {
408     const PtrRecordRdata* rdata = record->rdata<net::PtrRecordRdata>();
409     DCHECK(rdata);
410     std::string pointer = rdata->ptrdomain();
411 
412     // Skip pointers to the root domain.
413     if (!pointer.empty()) {
414       pointers.emplace_back(std::move(pointer), 0);
415 
416       base::TimeDelta ttl = base::Seconds(record->ttl());
417       min_ttl = std::min(ttl, min_ttl);
418     }
419   }
420 
421   if (!pointers.empty()) {
422     results.insert(std::make_unique<HostResolverInternalDataResult>(
423         ptr_records->front()->name(), DnsQueryType::PTR, now_ticks + min_ttl,
424         now + min_ttl, Source::kDns, std::vector<IPEndPoint>{},
425         std::vector<std::string>{}, std::move(pointers)));
426   }
427 
428   return results;
429 }
430 
ExtractServiceResults(const DnsResponse & response,base::Time now,base::TimeTicks now_ticks)431 ResultsOrError ExtractServiceResults(const DnsResponse& response,
432                                      base::Time now,
433                                      base::TimeTicks now_ticks) {
434   std::set<std::unique_ptr<HostResolverInternalResult>> results;
435   RecordsOrError srv_records = ExtractResponseRecords(
436       response, DnsQueryType::SRV, now, now_ticks, results);
437   if (!srv_records.has_value()) {
438     return base::unexpected(srv_records.error());
439   }
440 
441   std::vector<const SrvRecordRdata*> fitered_rdatas;
442   auto min_ttl = base::TimeDelta::Max();
443   for (const auto& record : srv_records.value()) {
444     const SrvRecordRdata* rdata = record->rdata<net::SrvRecordRdata>();
445     DCHECK(rdata);
446 
447     // Skip pointers to the root domain.
448     if (!rdata->target().empty()) {
449       fitered_rdatas.push_back(rdata);
450 
451       base::TimeDelta ttl = base::Seconds(record->ttl());
452       min_ttl = std::min(ttl, min_ttl);
453     }
454   }
455 
456   std::vector<HostPortPair> ordered_service_targets =
457       SortServiceTargets(fitered_rdatas);
458 
459   if (!ordered_service_targets.empty()) {
460     results.insert(std::make_unique<HostResolverInternalDataResult>(
461         srv_records->front()->name(), DnsQueryType::SRV, now_ticks + min_ttl,
462         now + min_ttl, Source::kDns, std::vector<IPEndPoint>{},
463         std::vector<std::string>{}, std::move(ordered_service_targets)));
464   }
465 
466   return results;
467 }
468 
UnwrapRecordPtr(const std::unique_ptr<const RecordParsed> & ptr)469 const RecordParsed* UnwrapRecordPtr(
470     const std::unique_ptr<const RecordParsed>& ptr) {
471   return ptr.get();
472 }
473 
RecordIsAlias(const RecordParsed * record)474 bool RecordIsAlias(const RecordParsed* record) {
475   DCHECK(record->rdata<HttpsRecordRdata>());
476   return record->rdata<HttpsRecordRdata>()->IsAlias();
477 }
478 
ExtractHttpsResults(const DnsResponse & response,std::string_view original_domain_name,uint16_t request_port,base::Time now,base::TimeTicks now_ticks)479 ResultsOrError ExtractHttpsResults(const DnsResponse& response,
480                                    std::string_view original_domain_name,
481                                    uint16_t request_port,
482                                    base::Time now,
483                                    base::TimeTicks now_ticks) {
484   DCHECK(!original_domain_name.empty());
485 
486   std::set<std::unique_ptr<HostResolverInternalResult>> results;
487   RecordsOrError https_records = ExtractResponseRecords(
488       response, DnsQueryType::HTTPS, now, now_ticks, results);
489   if (!https_records.has_value()) {
490     return base::unexpected(https_records.error());
491   }
492 
493   // Min TTL among records of full use to Chrome.
494   std::optional<base::TimeDelta> min_ttl;
495 
496   // Min TTL among all records considered compatible with Chrome, per
497   // RFC9460#section-8.
498   std::optional<base::TimeDelta> min_compatible_ttl;
499 
500   std::multimap<HttpsRecordPriority, ConnectionEndpointMetadata> metadatas;
501   bool compatible_record_found = false;
502   bool default_alpn_found = false;
503   for (const auto& record : https_records.value()) {
504     const HttpsRecordRdata* rdata = record->rdata<HttpsRecordRdata>();
505     DCHECK(rdata);
506 
507     base::TimeDelta ttl = base::Seconds(record->ttl());
508 
509     // Chrome does not yet support alias records.
510     if (rdata->IsAlias()) {
511       // Alias records are always considered compatible because they do not
512       // support "mandatory" params.
513       compatible_record_found = true;
514       min_compatible_ttl =
515           std::min(ttl, min_compatible_ttl.value_or(base::TimeDelta::Max()));
516 
517       continue;
518     }
519 
520     const ServiceFormHttpsRecordRdata* service = rdata->AsServiceForm();
521     if (service->IsCompatible()) {
522       compatible_record_found = true;
523       min_compatible_ttl =
524           std::min(ttl, min_compatible_ttl.value_or(base::TimeDelta::Max()));
525     } else {
526       // Ignore services incompatible with Chrome's HTTPS record parser.
527       // draft-ietf-dnsop-svcb-https-12#section-8
528       continue;
529     }
530 
531     std::string target_name = dns_names_util::UrlCanonicalizeNameIfAble(
532         service->service_name().empty() ? record->name()
533                                         : service->service_name());
534 
535     // Chrome does not yet support followup queries. So only support services at
536     // the original domain name or the canonical name (the record name).
537     // Note: HostCache::Entry::GetEndpoints() will not return metadatas which
538     // target name is different from the canonical name of A/AAAA query results.
539     if (!base::EqualsCaseInsensitiveASCII(
540             target_name,
541             dns_names_util::UrlCanonicalizeNameIfAble(original_domain_name)) &&
542         !base::EqualsCaseInsensitiveASCII(
543             target_name,
544             dns_names_util::UrlCanonicalizeNameIfAble(record->name()))) {
545       continue;
546     }
547 
548     // Ignore services at a different port from the request port. Chrome does
549     // not yet support endpoints diverging by port.  Note that before supporting
550     // port redirects, Chrome must ensure redirects to the "bad port list" are
551     // disallowed. Unclear if such logic would belong here or in socket
552     // connection logic.
553     if (service->port().has_value() &&
554         service->port().value() != request_port) {
555       continue;
556     }
557 
558     ConnectionEndpointMetadata metadata;
559 
560     metadata.supported_protocol_alpns = service->alpn_ids();
561     if (service->default_alpn() &&
562         !base::Contains(metadata.supported_protocol_alpns,
563                         dns_protocol::kHttpsServiceDefaultAlpn)) {
564       metadata.supported_protocol_alpns.push_back(
565           dns_protocol::kHttpsServiceDefaultAlpn);
566     }
567 
568     // Services with no supported ALPNs (those with "no-default-alpn" and no or
569     // empty "alpn") are not self-consistent and are rejected.
570     // draft-ietf-dnsop-svcb-https-12#section-7.1.1 and
571     // draft-ietf-dnsop-svcb-https-12#section-2.4.3.
572     if (metadata.supported_protocol_alpns.empty()) {
573       continue;
574     }
575 
576     metadata.ech_config_list = ConnectionEndpointMetadata::EchConfigList(
577         service->ech_config().cbegin(), service->ech_config().cend());
578 
579     metadata.target_name = std::move(target_name);
580 
581     metadatas.emplace(service->priority(), std::move(metadata));
582 
583     min_ttl = std::min(ttl, min_ttl.value_or(base::TimeDelta::Max()));
584 
585     if (service->default_alpn()) {
586       default_alpn_found = true;
587     }
588   }
589 
590   // Ignore all records if any are an alias record. Chrome does not yet support
591   // alias records, but aliases take precedence over any other records.
592   if (base::ranges::any_of(https_records.value(), &RecordIsAlias,
593                            &UnwrapRecordPtr)) {
594     metadatas.clear();
595   }
596 
597   // Ignore all records if they all mark "no-default-alpn". Domains should
598   // always provide at least one endpoint allowing default ALPN to ensure a
599   // reasonable expectation of connection success.
600   // draft-ietf-dnsop-svcb-https-12#section-7.1.2
601   if (!default_alpn_found) {
602     metadatas.clear();
603   }
604 
605   if (metadatas.empty() && compatible_record_found) {
606     // Empty metadata result signifies that compatible HTTPS records were
607     // received but with no contained metadata of use to Chrome. Use the min TTL
608     // of all compatible records.
609     CHECK(min_compatible_ttl.has_value());
610     results.insert(std::make_unique<HostResolverInternalMetadataResult>(
611         https_records->front()->name(), DnsQueryType::HTTPS,
612         now_ticks + min_compatible_ttl.value(),
613         now + min_compatible_ttl.value(), Source::kDns,
614         /*metadatas=*/
615         std::multimap<HttpsRecordPriority, ConnectionEndpointMetadata>{}));
616   } else if (!metadatas.empty()) {
617     // Use min TTL only of those records contributing useful metadata.
618     CHECK(min_ttl.has_value());
619     results.insert(std::make_unique<HostResolverInternalMetadataResult>(
620         https_records->front()->name(), DnsQueryType::HTTPS,
621         now_ticks + min_ttl.value(), now + min_ttl.value(), Source::kDns,
622         std::move(metadatas)));
623   }
624 
625   return results;
626 }
627 
628 }  // namespace
629 
DnsResponseResultExtractor(const DnsResponse & response,const base::Clock & clock,const base::TickClock & tick_clock)630 DnsResponseResultExtractor::DnsResponseResultExtractor(
631     const DnsResponse& response,
632     const base::Clock& clock,
633     const base::TickClock& tick_clock)
634     : response_(response), clock_(clock), tick_clock_(tick_clock) {}
635 
636 DnsResponseResultExtractor::~DnsResponseResultExtractor() = default;
637 
ExtractDnsResults(DnsQueryType query_type,std::string_view original_domain_name,uint16_t request_port) const638 ResultsOrError DnsResponseResultExtractor::ExtractDnsResults(
639     DnsQueryType query_type,
640     std::string_view original_domain_name,
641     uint16_t request_port) const {
642   DCHECK(!original_domain_name.empty());
643 
644   switch (query_type) {
645     case DnsQueryType::UNSPECIFIED:
646       // Should create multiple transactions with specified types.
647       NOTREACHED();
648       return base::unexpected(ExtractionError::kUnexpected);
649     case DnsQueryType::A:
650     case DnsQueryType::AAAA:
651       return ExtractAddressResults(*response_, query_type, clock_->Now(),
652                                    tick_clock_->NowTicks());
653     case DnsQueryType::TXT:
654       return ExtractTxtResults(*response_, clock_->Now(),
655                                tick_clock_->NowTicks());
656     case DnsQueryType::PTR:
657       return ExtractPointerResults(*response_, clock_->Now(),
658                                    tick_clock_->NowTicks());
659     case DnsQueryType::SRV:
660       return ExtractServiceResults(*response_, clock_->Now(),
661                                    tick_clock_->NowTicks());
662     case DnsQueryType::HTTPS:
663       return ExtractHttpsResults(*response_, original_domain_name, request_port,
664                                  clock_->Now(), tick_clock_->NowTicks());
665   }
666 }
667 
668 }  // namespace net
669