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