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/resolve_context.h"
6
7 #include <cstdlib>
8 #include <limits>
9 #include <utility>
10
11 #include "base/check_op.h"
12 #include "base/containers/contains.h"
13 #include "base/metrics/bucket_ranges.h"
14 #include "base/metrics/histogram.h"
15 #include "base/metrics/histogram_base.h"
16 #include "base/metrics/histogram_functions.h"
17 #include "base/metrics/histogram_macros.h"
18 #include "base/metrics/sample_vector.h"
19 #include "base/no_destructor.h"
20 #include "base/numerics/safe_conversions.h"
21 #include "base/observer_list.h"
22 #include "base/ranges/algorithm.h"
23 #include "base/strings/stringprintf.h"
24 #include "net/base/features.h"
25 #include "net/base/ip_address.h"
26 #include "net/base/network_change_notifier.h"
27 #include "net/dns/dns_server_iterator.h"
28 #include "net/dns/dns_session.h"
29 #include "net/dns/dns_util.h"
30 #include "net/dns/host_cache.h"
31 #include "net/dns/host_resolver_cache.h"
32 #include "net/dns/public/dns_over_https_config.h"
33 #include "net/dns/public/doh_provider_entry.h"
34 #include "net/dns/public/secure_dns_mode.h"
35 #include "net/url_request/url_request_context.h"
36
37 namespace net {
38
39 namespace {
40
41 // Min fallback period between queries, in case we are talking to a local DNS
42 // proxy.
43 const base::TimeDelta kMinFallbackPeriod = base::Milliseconds(10);
44
45 // Default maximum fallback period between queries, even with exponential
46 // backoff. (Can be overridden by field trial.)
47 const base::TimeDelta kDefaultMaxFallbackPeriod = base::Seconds(5);
48
49 // Maximum RTT that will fit in the RTT histograms.
50 const base::TimeDelta kRttMax = base::Seconds(30);
51 // Number of buckets in the histogram of observed RTTs.
52 const size_t kRttBucketCount = 350;
53 // Target percentile in the RTT histogram used for fallback period.
54 const int kRttPercentile = 99;
55 // Number of samples to seed the histogram with.
56 const base::HistogramBase::Count kNumSeeds = 2;
57
FindDohProvidersMatchingServerConfig(DnsOverHttpsServerConfig server_config)58 DohProviderEntry::List FindDohProvidersMatchingServerConfig(
59 DnsOverHttpsServerConfig server_config) {
60 DohProviderEntry::List matching_entries;
61 for (const DohProviderEntry* entry : DohProviderEntry::GetList()) {
62 if (entry->doh_server_config == server_config)
63 matching_entries.push_back(entry);
64 }
65
66 return matching_entries;
67 }
68
FindDohProvidersAssociatedWithAddress(IPAddress server_address)69 DohProviderEntry::List FindDohProvidersAssociatedWithAddress(
70 IPAddress server_address) {
71 DohProviderEntry::List matching_entries;
72 for (const DohProviderEntry* entry : DohProviderEntry::GetList()) {
73 if (entry->ip_addresses.count(server_address) > 0)
74 matching_entries.push_back(entry);
75 }
76
77 return matching_entries;
78 }
79
GetDefaultFallbackPeriod(const DnsConfig & config)80 base::TimeDelta GetDefaultFallbackPeriod(const DnsConfig& config) {
81 NetworkChangeNotifier::ConnectionType type =
82 NetworkChangeNotifier::GetConnectionType();
83 return GetTimeDeltaForConnectionTypeFromFieldTrialOrDefault(
84 "AsyncDnsInitialTimeoutMsByConnectionType", config.fallback_period, type);
85 }
86
GetMaxFallbackPeriod()87 base::TimeDelta GetMaxFallbackPeriod() {
88 NetworkChangeNotifier::ConnectionType type =
89 NetworkChangeNotifier::GetConnectionType();
90 return GetTimeDeltaForConnectionTypeFromFieldTrialOrDefault(
91 "AsyncDnsMaxTimeoutMsByConnectionType", kDefaultMaxFallbackPeriod, type);
92 }
93
94 class RttBuckets : public base::BucketRanges {
95 public:
RttBuckets()96 RttBuckets() : base::BucketRanges(kRttBucketCount + 1) {
97 base::Histogram::InitializeBucketRanges(
98 1,
99 base::checked_cast<base::HistogramBase::Sample>(
100 kRttMax.InMilliseconds()),
101 this);
102 }
103 };
104
GetRttBuckets()105 static RttBuckets* GetRttBuckets() {
106 static base::NoDestructor<RttBuckets> buckets;
107 return buckets.get();
108 }
109
GetRttHistogram(base::TimeDelta rtt_estimate)110 static std::unique_ptr<base::SampleVector> GetRttHistogram(
111 base::TimeDelta rtt_estimate) {
112 std::unique_ptr<base::SampleVector> histogram =
113 std::make_unique<base::SampleVector>(GetRttBuckets());
114 // Seed histogram with 2 samples at |rtt_estimate|.
115 histogram->Accumulate(base::checked_cast<base::HistogramBase::Sample>(
116 rtt_estimate.InMilliseconds()),
117 kNumSeeds);
118 return histogram;
119 }
120
121 #if defined(ENABLE_BUILT_IN_DNS)
122 constexpr size_t kDefaultCacheSize = 1000;
123 #else
124 constexpr size_t kDefaultCacheSize = 100;
125 #endif
126
CreateHostCache(bool enable_caching)127 std::unique_ptr<HostCache> CreateHostCache(bool enable_caching) {
128 if (enable_caching) {
129 return std::make_unique<HostCache>(kDefaultCacheSize);
130 } else {
131 return nullptr;
132 }
133 }
134
CreateHostResolverCache(bool enable_caching)135 std::unique_ptr<HostResolverCache> CreateHostResolverCache(
136 bool enable_caching) {
137 if (enable_caching) {
138 return std::make_unique<HostResolverCache>(kDefaultCacheSize);
139 } else {
140 return nullptr;
141 }
142 }
143
144 } // namespace
145
ServerStats(std::unique_ptr<base::SampleVector> buckets)146 ResolveContext::ServerStats::ServerStats(
147 std::unique_ptr<base::SampleVector> buckets)
148 : rtt_histogram(std::move(buckets)) {}
149
150 ResolveContext::ServerStats::ServerStats(ServerStats&&) = default;
151
152 ResolveContext::ServerStats::~ServerStats() = default;
153
ResolveContext(URLRequestContext * url_request_context,bool enable_caching)154 ResolveContext::ResolveContext(URLRequestContext* url_request_context,
155 bool enable_caching)
156 : url_request_context_(url_request_context),
157 host_cache_(CreateHostCache(enable_caching)),
158 host_resolver_cache_(CreateHostResolverCache(enable_caching)),
159 isolation_info_(IsolationInfo::CreateTransient()) {
160 max_fallback_period_ = GetMaxFallbackPeriod();
161 }
162
163 ResolveContext::~ResolveContext() = default;
164
GetDohIterator(const DnsConfig & config,const SecureDnsMode & mode,const DnsSession * session)165 std::unique_ptr<DnsServerIterator> ResolveContext::GetDohIterator(
166 const DnsConfig& config,
167 const SecureDnsMode& mode,
168 const DnsSession* session) {
169 // Make the iterator even if the session differs. The first call to the member
170 // functions will catch the out of date session.
171
172 return std::make_unique<DohDnsServerIterator>(
173 doh_server_stats_.size(), FirstServerIndex(true, session),
174 config.doh_attempts, config.attempts, mode, this, session);
175 }
176
GetClassicDnsIterator(const DnsConfig & config,const DnsSession * session)177 std::unique_ptr<DnsServerIterator> ResolveContext::GetClassicDnsIterator(
178 const DnsConfig& config,
179 const DnsSession* session) {
180 // Make the iterator even if the session differs. The first call to the member
181 // functions will catch the out of date session.
182
183 return std::make_unique<ClassicDnsServerIterator>(
184 config.nameservers.size(), FirstServerIndex(false, session),
185 config.attempts, config.attempts, this, session);
186 }
187
GetDohServerAvailability(size_t doh_server_index,const DnsSession * session) const188 bool ResolveContext::GetDohServerAvailability(size_t doh_server_index,
189 const DnsSession* session) const {
190 if (!IsCurrentSession(session))
191 return false;
192
193 CHECK_LT(doh_server_index, doh_server_stats_.size());
194 return ServerStatsToDohAvailability(doh_server_stats_[doh_server_index]);
195 }
196
NumAvailableDohServers(const DnsSession * session) const197 size_t ResolveContext::NumAvailableDohServers(const DnsSession* session) const {
198 if (!IsCurrentSession(session))
199 return 0;
200
201 return base::ranges::count_if(doh_server_stats_,
202 &ServerStatsToDohAvailability);
203 }
204
RecordServerFailure(size_t server_index,bool is_doh_server,int rv,const DnsSession * session)205 void ResolveContext::RecordServerFailure(size_t server_index,
206 bool is_doh_server,
207 int rv,
208 const DnsSession* session) {
209 DCHECK(rv != OK && rv != ERR_NAME_NOT_RESOLVED && rv != ERR_IO_PENDING);
210
211 if (!IsCurrentSession(session))
212 return;
213
214 // "FailureError" metric is only recorded for secure queries.
215 if (is_doh_server) {
216 std::string query_type =
217 GetQueryTypeForUma(server_index, true /* is_doh_server */, session);
218 DCHECK_NE(query_type, "Insecure");
219 std::string provider_id =
220 GetDohProviderIdForUma(server_index, true /* is_doh_server */, session);
221
222 base::UmaHistogramSparse(
223 base::JoinString(
224 {"Net.DNS.DnsTransaction", query_type, provider_id, "FailureError"},
225 "."),
226 std::abs(rv));
227 }
228
229 size_t num_available_doh_servers_before = NumAvailableDohServers(session);
230
231 ServerStats* stats = GetServerStats(server_index, is_doh_server);
232 ++(stats->last_failure_count);
233 stats->last_failure = base::TimeTicks::Now();
234 stats->has_failed_previously = true;
235
236 size_t num_available_doh_servers_now = NumAvailableDohServers(session);
237 if (num_available_doh_servers_now < num_available_doh_servers_before) {
238 NotifyDohStatusObserversOfUnavailable(false /* network_change */);
239
240 // TODO(crbug.com/1022059): Consider figuring out some way to only for the
241 // first context enabling DoH or the last context disabling DoH.
242 if (num_available_doh_servers_now == 0)
243 NetworkChangeNotifier::TriggerNonSystemDnsChange();
244 }
245 }
246
RecordServerSuccess(size_t server_index,bool is_doh_server,const DnsSession * session)247 void ResolveContext::RecordServerSuccess(size_t server_index,
248 bool is_doh_server,
249 const DnsSession* session) {
250 if (!IsCurrentSession(session))
251 return;
252
253 bool doh_available_before = NumAvailableDohServers(session) > 0;
254
255 ServerStats* stats = GetServerStats(server_index, is_doh_server);
256 stats->last_failure_count = 0;
257 stats->current_connection_success = true;
258 stats->last_failure = base::TimeTicks();
259 stats->last_success = base::TimeTicks::Now();
260
261 // TODO(crbug.com/1022059): Consider figuring out some way to only for the
262 // first context enabling DoH or the last context disabling DoH.
263 bool doh_available_now = NumAvailableDohServers(session) > 0;
264 if (doh_available_before != doh_available_now)
265 NetworkChangeNotifier::TriggerNonSystemDnsChange();
266 }
267
RecordRtt(size_t server_index,bool is_doh_server,base::TimeDelta rtt,int rv,const DnsSession * session)268 void ResolveContext::RecordRtt(size_t server_index,
269 bool is_doh_server,
270 base::TimeDelta rtt,
271 int rv,
272 const DnsSession* session) {
273 if (!IsCurrentSession(session))
274 return;
275
276 ServerStats* stats = GetServerStats(server_index, is_doh_server);
277
278 base::TimeDelta base_fallback_period =
279 NextFallbackPeriodHelper(stats, 0 /* num_backoffs */);
280 RecordRttForUma(server_index, is_doh_server, rtt, rv, base_fallback_period,
281 session);
282
283 // RTT values shouldn't be less than 0, but it shouldn't cause a crash if
284 // they are anyway, so clip to 0. See https://crbug.com/753568.
285 if (rtt.is_negative())
286 rtt = base::TimeDelta();
287
288 // Histogram-based method.
289 stats->rtt_histogram->Accumulate(
290 base::saturated_cast<base::HistogramBase::Sample>(rtt.InMilliseconds()),
291 1);
292 }
293
NextClassicFallbackPeriod(size_t classic_server_index,int attempt,const DnsSession * session)294 base::TimeDelta ResolveContext::NextClassicFallbackPeriod(
295 size_t classic_server_index,
296 int attempt,
297 const DnsSession* session) {
298 if (!IsCurrentSession(session))
299 return std::min(GetDefaultFallbackPeriod(session->config()),
300 max_fallback_period_);
301
302 return NextFallbackPeriodHelper(
303 GetServerStats(classic_server_index, false /* is _doh_server */),
304 attempt / current_session_->config().nameservers.size());
305 }
306
NextDohFallbackPeriod(size_t doh_server_index,const DnsSession * session)307 base::TimeDelta ResolveContext::NextDohFallbackPeriod(
308 size_t doh_server_index,
309 const DnsSession* session) {
310 if (!IsCurrentSession(session))
311 return std::min(GetDefaultFallbackPeriod(session->config()),
312 max_fallback_period_);
313
314 return NextFallbackPeriodHelper(
315 GetServerStats(doh_server_index, true /* is _doh_server */),
316 0 /* num_backoffs */);
317 }
318
ClassicTransactionTimeout(const DnsSession * session)319 base::TimeDelta ResolveContext::ClassicTransactionTimeout(
320 const DnsSession* session) {
321 if (!IsCurrentSession(session))
322 return features::kDnsMinTransactionTimeout.Get();
323
324 // Should not need to call if there are no classic servers configured.
325 DCHECK(!classic_server_stats_.empty());
326
327 return TransactionTimeoutHelper(classic_server_stats_.cbegin(),
328 classic_server_stats_.cend());
329 }
330
SecureTransactionTimeout(SecureDnsMode secure_dns_mode,const DnsSession * session)331 base::TimeDelta ResolveContext::SecureTransactionTimeout(
332 SecureDnsMode secure_dns_mode,
333 const DnsSession* session) {
334 // Currently only implemented for Secure mode as other modes are assumed to
335 // always use aggressive timeouts. If that ever changes, need to implement
336 // only accounting for available DoH servers when not Secure mode.
337 DCHECK_EQ(secure_dns_mode, SecureDnsMode::kSecure);
338
339 if (!IsCurrentSession(session))
340 return features::kDnsMinTransactionTimeout.Get();
341
342 // Should not need to call if there are no DoH servers configured.
343 DCHECK(!doh_server_stats_.empty());
344
345 return TransactionTimeoutHelper(doh_server_stats_.cbegin(),
346 doh_server_stats_.cend());
347 }
348
RegisterDohStatusObserver(DohStatusObserver * observer)349 void ResolveContext::RegisterDohStatusObserver(DohStatusObserver* observer) {
350 DCHECK(observer);
351 doh_status_observers_.AddObserver(observer);
352 }
353
UnregisterDohStatusObserver(const DohStatusObserver * observer)354 void ResolveContext::UnregisterDohStatusObserver(
355 const DohStatusObserver* observer) {
356 DCHECK(observer);
357 doh_status_observers_.RemoveObserver(observer);
358 }
359
InvalidateCachesAndPerSessionData(const DnsSession * new_session,bool network_change)360 void ResolveContext::InvalidateCachesAndPerSessionData(
361 const DnsSession* new_session,
362 bool network_change) {
363 // Network-bound ResolveContexts should never receive a cache invalidation due
364 // to a network change.
365 DCHECK(GetTargetNetwork() == handles::kInvalidNetworkHandle ||
366 !network_change);
367 if (host_cache_)
368 host_cache_->Invalidate();
369
370 // DNS config is constant for any given session, so if the current session is
371 // unchanged, any per-session data is safe to keep, even if it's dependent on
372 // a specific config.
373 if (new_session && new_session == current_session_.get())
374 return;
375
376 current_session_.reset();
377 doh_autoupgrade_success_metric_timer_.Stop();
378 classic_server_stats_.clear();
379 doh_server_stats_.clear();
380 initial_fallback_period_ = base::TimeDelta();
381 max_fallback_period_ = GetMaxFallbackPeriod();
382
383 if (!new_session) {
384 NotifyDohStatusObserversOfSessionChanged();
385 return;
386 }
387
388 current_session_ = new_session->GetWeakPtr();
389
390 initial_fallback_period_ =
391 GetDefaultFallbackPeriod(current_session_->config());
392
393 for (size_t i = 0; i < new_session->config().nameservers.size(); ++i) {
394 classic_server_stats_.emplace_back(
395 GetRttHistogram(initial_fallback_period_));
396 }
397 for (size_t i = 0; i < new_session->config().doh_config.servers().size();
398 ++i) {
399 doh_server_stats_.emplace_back(GetRttHistogram(initial_fallback_period_));
400 }
401
402 CHECK_EQ(new_session->config().nameservers.size(),
403 classic_server_stats_.size());
404 CHECK_EQ(new_session->config().doh_config.servers().size(),
405 doh_server_stats_.size());
406
407 NotifyDohStatusObserversOfSessionChanged();
408
409 if (!doh_server_stats_.empty())
410 NotifyDohStatusObserversOfUnavailable(network_change);
411 }
412
StartDohAutoupgradeSuccessTimer(const DnsSession * session)413 void ResolveContext::StartDohAutoupgradeSuccessTimer(
414 const DnsSession* session) {
415 if (!IsCurrentSession(session)) {
416 return;
417 }
418 if (doh_autoupgrade_success_metric_timer_.IsRunning()) {
419 return;
420 }
421 // We won't pass `session` to `EmitDohAutoupgradeSuccessMetrics()` but will
422 // instead reset the timer in `InvalidateCachesAndPerSessionData()` so that
423 // the former never gets called after the session changes.
424 doh_autoupgrade_success_metric_timer_.Start(
425 FROM_HERE, ResolveContext::kDohAutoupgradeSuccessMetricTimeout,
426 base::BindOnce(&ResolveContext::EmitDohAutoupgradeSuccessMetrics,
427 base::Unretained(this)));
428 }
429
GetTargetNetwork() const430 handles::NetworkHandle ResolveContext::GetTargetNetwork() const {
431 if (!url_request_context())
432 return handles::kInvalidNetworkHandle;
433
434 return url_request_context()->bound_network();
435 }
436
FirstServerIndex(bool doh_server,const DnsSession * session)437 size_t ResolveContext::FirstServerIndex(bool doh_server,
438 const DnsSession* session) {
439 if (!IsCurrentSession(session))
440 return 0u;
441
442 // DoH first server doesn't rotate, so always return 0u.
443 if (doh_server)
444 return 0u;
445
446 size_t index = classic_server_index_;
447 if (current_session_->config().rotate) {
448 classic_server_index_ = (classic_server_index_ + 1) %
449 current_session_->config().nameservers.size();
450 }
451 return index;
452 }
453
IsCurrentSession(const DnsSession * session) const454 bool ResolveContext::IsCurrentSession(const DnsSession* session) const {
455 CHECK(session);
456 if (session == current_session_.get()) {
457 CHECK_EQ(current_session_->config().nameservers.size(),
458 classic_server_stats_.size());
459 CHECK_EQ(current_session_->config().doh_config.servers().size(),
460 doh_server_stats_.size());
461 return true;
462 }
463
464 return false;
465 }
466
GetServerStats(size_t server_index,bool is_doh_server)467 ResolveContext::ServerStats* ResolveContext::GetServerStats(
468 size_t server_index,
469 bool is_doh_server) {
470 if (!is_doh_server) {
471 CHECK_LT(server_index, classic_server_stats_.size());
472 return &classic_server_stats_[server_index];
473 } else {
474 CHECK_LT(server_index, doh_server_stats_.size());
475 return &doh_server_stats_[server_index];
476 }
477 }
478
NextFallbackPeriodHelper(const ServerStats * server_stats,int num_backoffs)479 base::TimeDelta ResolveContext::NextFallbackPeriodHelper(
480 const ServerStats* server_stats,
481 int num_backoffs) {
482 // Respect initial fallback period (from config or field trial) if it exceeds
483 // max.
484 if (initial_fallback_period_ > max_fallback_period_)
485 return initial_fallback_period_;
486
487 static_assert(std::numeric_limits<base::HistogramBase::Count>::is_signed,
488 "histogram base count assumed to be signed");
489
490 // Use fixed percentile of observed samples.
491 const base::SampleVector& samples = *server_stats->rtt_histogram;
492
493 base::HistogramBase::Count total = samples.TotalCount();
494 base::HistogramBase::Count remaining_count = kRttPercentile * total / 100;
495 size_t index = 0;
496 while (remaining_count > 0 && index < GetRttBuckets()->size()) {
497 remaining_count -= samples.GetCountAtIndex(index);
498 ++index;
499 }
500
501 base::TimeDelta fallback_period =
502 base::Milliseconds(GetRttBuckets()->range(index));
503
504 fallback_period = std::max(fallback_period, kMinFallbackPeriod);
505
506 return std::min(fallback_period * (1 << num_backoffs), max_fallback_period_);
507 }
508
509 template <typename Iterator>
TransactionTimeoutHelper(Iterator server_stats_begin,Iterator server_stats_end)510 base::TimeDelta ResolveContext::TransactionTimeoutHelper(
511 Iterator server_stats_begin,
512 Iterator server_stats_end) {
513 DCHECK_GE(features::kDnsMinTransactionTimeout.Get(), base::TimeDelta());
514 DCHECK_GE(features::kDnsTransactionTimeoutMultiplier.Get(), 0.0);
515
516 // Expect at least one configured server.
517 DCHECK(server_stats_begin != server_stats_end);
518
519 base::TimeDelta shortest_fallback_period = base::TimeDelta::Max();
520 for (Iterator server_stats = server_stats_begin;
521 server_stats != server_stats_end; ++server_stats) {
522 shortest_fallback_period = std::min(
523 shortest_fallback_period,
524 NextFallbackPeriodHelper(&*server_stats, 0 /* num_backoffs */));
525 }
526
527 DCHECK_GE(shortest_fallback_period, base::TimeDelta());
528 base::TimeDelta ratio_based_timeout =
529 shortest_fallback_period *
530 features::kDnsTransactionTimeoutMultiplier.Get();
531
532 return std::max(features::kDnsMinTransactionTimeout.Get(),
533 ratio_based_timeout);
534 }
535
RecordRttForUma(size_t server_index,bool is_doh_server,base::TimeDelta rtt,int rv,base::TimeDelta base_fallback_period,const DnsSession * session)536 void ResolveContext::RecordRttForUma(size_t server_index,
537 bool is_doh_server,
538 base::TimeDelta rtt,
539 int rv,
540 base::TimeDelta base_fallback_period,
541 const DnsSession* session) {
542 DCHECK(IsCurrentSession(session));
543
544 std::string query_type =
545 GetQueryTypeForUma(server_index, is_doh_server, session);
546 std::string provider_id =
547 GetDohProviderIdForUma(server_index, is_doh_server, session);
548
549 // Skip metrics for SecureNotValidated queries unless the provider is tagged
550 // for extra logging.
551 if (query_type == "SecureNotValidated" &&
552 !GetProviderUseExtraLogging(server_index, is_doh_server, session)) {
553 return;
554 }
555
556 if (rv == OK || rv == ERR_NAME_NOT_RESOLVED) {
557 base::UmaHistogramMediumTimes(
558 base::JoinString(
559 {"Net.DNS.DnsTransaction", query_type, provider_id, "SuccessTime"},
560 "."),
561 rtt);
562 } else {
563 base::UmaHistogramMediumTimes(
564 base::JoinString(
565 {"Net.DNS.DnsTransaction", query_type, provider_id, "FailureTime"},
566 "."),
567 rtt);
568 }
569 }
570
GetQueryTypeForUma(size_t server_index,bool is_doh_server,const DnsSession * session)571 std::string ResolveContext::GetQueryTypeForUma(size_t server_index,
572 bool is_doh_server,
573 const DnsSession* session) {
574 DCHECK(IsCurrentSession(session));
575
576 if (!is_doh_server)
577 return "Insecure";
578
579 // Secure queries are validated if the DoH server state is available.
580 if (GetDohServerAvailability(server_index, session))
581 return "SecureValidated";
582
583 return "SecureNotValidated";
584 }
585
GetDohProviderIdForUma(size_t server_index,bool is_doh_server,const DnsSession * session)586 std::string ResolveContext::GetDohProviderIdForUma(size_t server_index,
587 bool is_doh_server,
588 const DnsSession* session) {
589 DCHECK(IsCurrentSession(session));
590
591 if (is_doh_server) {
592 return GetDohProviderIdForHistogramFromServerConfig(
593 session->config().doh_config.servers()[server_index]);
594 }
595
596 return GetDohProviderIdForHistogramFromNameserver(
597 session->config().nameservers[server_index]);
598 }
599
GetProviderUseExtraLogging(size_t server_index,bool is_doh_server,const DnsSession * session)600 bool ResolveContext::GetProviderUseExtraLogging(size_t server_index,
601 bool is_doh_server,
602 const DnsSession* session) {
603 DCHECK(IsCurrentSession(session));
604
605 DohProviderEntry::List matching_entries;
606 if (is_doh_server) {
607 const DnsOverHttpsServerConfig& server_config =
608 session->config().doh_config.servers()[server_index];
609 matching_entries = FindDohProvidersMatchingServerConfig(server_config);
610 } else {
611 IPAddress server_address =
612 session->config().nameservers[server_index].address();
613 matching_entries = FindDohProvidersAssociatedWithAddress(server_address);
614 }
615
616 // Use extra logging if any matching provider entries have
617 // `LoggingLevel::kExtra` set.
618 return base::Contains(matching_entries,
619 DohProviderEntry::LoggingLevel::kExtra,
620 &DohProviderEntry::logging_level);
621 }
622
NotifyDohStatusObserversOfSessionChanged()623 void ResolveContext::NotifyDohStatusObserversOfSessionChanged() {
624 for (auto& observer : doh_status_observers_)
625 observer.OnSessionChanged();
626 }
627
NotifyDohStatusObserversOfUnavailable(bool network_change)628 void ResolveContext::NotifyDohStatusObserversOfUnavailable(
629 bool network_change) {
630 for (auto& observer : doh_status_observers_)
631 observer.OnDohServerUnavailable(network_change);
632 }
633
EmitDohAutoupgradeSuccessMetrics()634 void ResolveContext::EmitDohAutoupgradeSuccessMetrics() {
635 // This method should not be called if `current_session_` is not populated.
636 CHECK(current_session_);
637
638 // If DoH auto-upgrade is not enabled, then don't emit histograms.
639 if (current_session_->config().secure_dns_mode != SecureDnsMode::kAutomatic) {
640 return;
641 }
642
643 DohServerAutoupgradeStatus status;
644 for (size_t i = 0; i < doh_server_stats_.size(); i++) {
645 auto& entry = doh_server_stats_[i];
646
647 if (ServerStatsToDohAvailability(entry)) {
648 if (!entry.has_failed_previously) {
649 // Auto-upgrade successful and no prior failures.
650 status = DohServerAutoupgradeStatus::kSuccessWithNoPriorFailures;
651 } else {
652 // Auto-upgrade successful but some prior failures.
653 status = DohServerAutoupgradeStatus::kSuccessWithSomePriorFailures;
654 }
655 } else {
656 if (entry.last_success.is_null()) {
657 if (entry.last_failure.is_null()) {
658 // Skip entries that we've never attempted to use.
659 continue;
660 }
661
662 // Auto-upgrade failed and DoH requests have never worked. It's possible
663 // that an invalid DoH resolver config was provided by the user via
664 // enterprise policy (in which case this state will always be associated
665 // with the 'Other' provider_id), but it's also possible that there's an
666 // issue with the user's network configuration or the provider's
667 // infrastructure.
668 status = DohServerAutoupgradeStatus::kFailureWithNoPriorSuccesses;
669 } else {
670 // Auto-upgrade is failing currently but has worked in the past.
671 status = DohServerAutoupgradeStatus::kFailureWithSomePriorSuccesses;
672 }
673 }
674
675 std::string provider_id = GetDohProviderIdForUma(i, /*is_doh_server=*/true,
676 current_session_.get());
677
678 base::UmaHistogramEnumeration(
679 base::JoinString(
680 {"Net.DNS.ResolveContext.DohAutoupgrade", provider_id, "Status"},
681 "."),
682 status);
683 }
684 }
685
686 // static
ServerStatsToDohAvailability(const ResolveContext::ServerStats & stats)687 bool ResolveContext::ServerStatsToDohAvailability(
688 const ResolveContext::ServerStats& stats) {
689 return stats.last_failure_count < kAutomaticModeFailureLimit &&
690 stats.current_connection_success;
691 }
692
693 } // namespace net
694