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