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