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