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