xref: /aosp_15_r20/external/cronet/net/dns/resolve_context.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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