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/dns_server_iterator.h"
6
7 #include "base/time/time.h"
8 #include "net/dns/dns_session.h"
9 #include "net/dns/resolve_context.h"
10 #include "third_party/abseil-cpp/absl/types/optional.h"
11
12 namespace net {
DnsServerIterator(size_t nameservers_size,size_t starting_index,int max_times_returned,int max_failures,const ResolveContext * resolve_context,const DnsSession * session)13 DnsServerIterator::DnsServerIterator(size_t nameservers_size,
14 size_t starting_index,
15 int max_times_returned,
16 int max_failures,
17 const ResolveContext* resolve_context,
18 const DnsSession* session)
19 : times_returned_(nameservers_size, 0),
20 max_times_returned_(max_times_returned),
21 max_failures_(max_failures),
22 resolve_context_(resolve_context),
23 next_index_(starting_index),
24 session_(session) {}
25
26 DnsServerIterator::~DnsServerIterator() = default;
27
GetNextAttemptIndex()28 size_t DohDnsServerIterator::GetNextAttemptIndex() {
29 DCHECK(resolve_context_->IsCurrentSession(session_));
30 DCHECK(AttemptAvailable());
31
32 // Because AttemptAvailable() should always be true before running this
33 // function we can assume that an attemptable DoH server exists.
34
35 // Check if the next index is available and hasn't hit its failure limit. If
36 // not, try the next one and so on until we've tried them all.
37 absl::optional<size_t> least_recently_failed_index;
38 base::TimeTicks least_recently_failed_time;
39
40 size_t previous_index = next_index_;
41 size_t curr_index;
42
43 do {
44 curr_index = next_index_;
45 next_index_ = (next_index_ + 1) % times_returned_.size();
46
47 // If the DoH mode is "secure" then don't check GetDohServerAvailability()
48 // because we try every server regardless of availability.
49 bool secure_or_available_server =
50 secure_dns_mode_ == SecureDnsMode::kSecure ||
51 resolve_context_->GetDohServerAvailability(curr_index, session_);
52
53 // If we've tried this server |max_times_returned_| already, then we're done
54 // with it. Similarly skip this server if it isn't available and we're not
55 // in secure mode.
56 if (times_returned_[curr_index] >= max_times_returned_ ||
57 !secure_or_available_server)
58 continue;
59
60 if (resolve_context_->doh_server_stats_[curr_index].last_failure_count <
61 max_failures_) {
62 times_returned_[curr_index]++;
63 return curr_index;
64 }
65
66 // Update the least recently failed server if needed.
67 base::TimeTicks curr_index_failure_time =
68 resolve_context_->doh_server_stats_[curr_index].last_failure;
69 if (!least_recently_failed_index ||
70 curr_index_failure_time < least_recently_failed_time) {
71 least_recently_failed_time = curr_index_failure_time;
72 least_recently_failed_index = curr_index;
73 }
74 } while (next_index_ != previous_index);
75
76 // At this point the only available servers we haven't attempted
77 // |max_times_returned_| times are at their failure limit. Return the server
78 // with the least recent failure.
79
80 DCHECK(least_recently_failed_index.has_value());
81 times_returned_[least_recently_failed_index.value()]++;
82 return least_recently_failed_index.value();
83 }
84
AttemptAvailable()85 bool DohDnsServerIterator::AttemptAvailable() {
86 if (!resolve_context_->IsCurrentSession(session_))
87 return false;
88
89 for (size_t i = 0; i < times_returned_.size(); i++) {
90 // If the DoH mode is "secure" then don't check GetDohServerAvailability()
91 // because we try every server regardless of availability.
92 bool secure_or_available_server =
93 secure_dns_mode_ == SecureDnsMode::kSecure ||
94 resolve_context_->GetDohServerAvailability(i, session_);
95
96 if (times_returned_[i] < max_times_returned_ && secure_or_available_server)
97 return true;
98 }
99 return false;
100 }
101
GetNextAttemptIndex()102 size_t ClassicDnsServerIterator::GetNextAttemptIndex() {
103 DCHECK(resolve_context_->IsCurrentSession(session_));
104 DCHECK(AttemptAvailable());
105
106 // Because AttemptAvailable() should always be true before running this
107 // function we can assume that an attemptable DNS server exists.
108
109 // Check if the next index is available and hasn't hit its failure limit. If
110 // not, try the next one and so on until we've tried them all.
111 absl::optional<size_t> least_recently_failed_index;
112 base::TimeTicks least_recently_failed_time;
113
114 size_t previous_index = next_index_;
115 size_t curr_index;
116
117 do {
118 curr_index = next_index_;
119 next_index_ = (next_index_ + 1) % times_returned_.size();
120
121 // If we've tried this server |max_times_returned_| already, then we're done
122 // with it.
123 if (times_returned_[curr_index] >= max_times_returned_)
124 continue;
125
126 if (resolve_context_->classic_server_stats_[curr_index].last_failure_count <
127 max_failures_) {
128 times_returned_[curr_index]++;
129 return curr_index;
130 }
131
132 // Update the least recently failed server if needed.
133 base::TimeTicks curr_index_failure_time =
134 resolve_context_->classic_server_stats_[curr_index].last_failure;
135 if (!least_recently_failed_index ||
136 curr_index_failure_time < least_recently_failed_time) {
137 least_recently_failed_time = curr_index_failure_time;
138 least_recently_failed_index = curr_index;
139 }
140 } while (next_index_ != previous_index);
141
142 // At this point the only servers we haven't attempted |max_times_returned_|
143 // times are at their failure limit. Return the server with the least recent
144 // failure.
145
146 DCHECK(least_recently_failed_index.has_value());
147 times_returned_[least_recently_failed_index.value()]++;
148 return least_recently_failed_index.value();
149 }
150
AttemptAvailable()151 bool ClassicDnsServerIterator::AttemptAvailable() {
152 if (!resolve_context_->IsCurrentSession(session_))
153 return false;
154
155 for (int i : times_returned_) {
156 if (i < max_times_returned_)
157 return true;
158 }
159 return false;
160 }
161
162 } // namespace net
163