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