blob: d04d28b81911b37f9f16bb2f7b35bbe695f011bd [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/dns/dns_server_iterator.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "net/dns/dns_config.h"
#include "net/dns/dns_session.h"
#include "net/dns/resolve_context.h"
namespace net {
DnsServerIterator::DnsServerIterator(size_t nameservers_size,
size_t starting_index,
int max_times_returned,
int max_failures,
const ResolveContext* resolve_context,
const DnsSession* session)
: times_returned_(nameservers_size, 0),
max_times_returned_(max_times_returned),
max_failures_(max_failures),
resolve_context_(resolve_context),
next_index_(starting_index),
session_(session) {}
DnsServerIterator::~DnsServerIterator() = default;
size_t DohDnsServerIterator::GetNextAttemptIndex() {
DCHECK(resolve_context_->IsCurrentSession(session_));
DCHECK(AttemptAvailable());
// Because AttemptAvailable() should always be true before running this
// function we can assume that an attemptable DoH server exists.
// Check if the next index is available and hasn't hit its failure limit. If
// not, try the next one and so on until we've tried them all.
base::Optional<size_t> least_recently_failed_index;
base::TimeTicks least_recently_failed_time;
size_t previous_index = next_index_;
size_t curr_index;
do {
curr_index = next_index_;
next_index_ = (next_index_ + 1) % times_returned_.size();
// If the DoH mode is "secure" then don't check GetDohServerAvailability()
// because we try every server regardless of availability.
bool secure_or_available_server =
secure_dns_mode_ == DnsConfig::SecureDnsMode::SECURE ||
resolve_context_->GetDohServerAvailability(curr_index, session_);
// If we've tried this server |max_times_returned_| already, then we're done
// with it. Similarly skip this server if it isn't available and we're not
// in secure mode.
if (times_returned_[curr_index] >= max_times_returned_ ||
!secure_or_available_server)
continue;
if (resolve_context_->doh_server_stats_[curr_index].last_failure_count <
max_failures_) {
times_returned_[curr_index]++;
return curr_index;
}
// Update the least recently failed server if needed.
base::TimeTicks curr_index_failure_time =
resolve_context_->doh_server_stats_[curr_index].last_failure;
if (!least_recently_failed_index ||
curr_index_failure_time < least_recently_failed_time) {
least_recently_failed_time = curr_index_failure_time;
least_recently_failed_index = curr_index;
}
} while (next_index_ != previous_index);
// At this point the only available servers we haven't attempted
// |max_times_returned_| times are at their failure limit. Return the server
// with the least recent failure.
DCHECK(least_recently_failed_index.has_value());
times_returned_[least_recently_failed_index.value()]++;
return least_recently_failed_index.value();
}
bool DohDnsServerIterator::AttemptAvailable() {
if (!resolve_context_->IsCurrentSession(session_))
return false;
for (size_t i = 0; i < times_returned_.size(); i++) {
// If the DoH mode is "secure" then don't check GetDohServerAvailability()
// because we try every server regardless of availability.
bool secure_or_available_server =
secure_dns_mode_ == DnsConfig::SecureDnsMode::SECURE ||
resolve_context_->GetDohServerAvailability(i, session_);
if (times_returned_[i] < max_times_returned_ && secure_or_available_server)
return true;
}
return false;
}
size_t ClassicDnsServerIterator::GetNextAttemptIndex() {
DCHECK(resolve_context_->IsCurrentSession(session_));
DCHECK(AttemptAvailable());
// Because AttemptAvailable() should always be true before running this
// function we can assume that an attemptable DNS server exists.
// Check if the next index is available and hasn't hit its failure limit. If
// not, try the next one and so on until we've tried them all.
base::Optional<size_t> least_recently_failed_index;
base::TimeTicks least_recently_failed_time;
size_t previous_index = next_index_;
size_t curr_index;
do {
curr_index = next_index_;
next_index_ = (next_index_ + 1) % times_returned_.size();
// If we've tried this server |max_times_returned_| already, then we're done
// with it.
if (times_returned_[curr_index] >= max_times_returned_)
continue;
if (resolve_context_->classic_server_stats_[curr_index].last_failure_count <
max_failures_) {
times_returned_[curr_index]++;
return curr_index;
}
// Update the least recently failed server if needed.
base::TimeTicks curr_index_failure_time =
resolve_context_->classic_server_stats_[curr_index].last_failure;
if (!least_recently_failed_index ||
curr_index_failure_time < least_recently_failed_time) {
least_recently_failed_time = curr_index_failure_time;
least_recently_failed_index = curr_index;
}
} while (next_index_ != previous_index);
// At this point the only servers we haven't attempted |max_times_returned_|
// times are at their failure limit. Return the server with the least recent
// failure.
DCHECK(least_recently_failed_index.has_value());
times_returned_[least_recently_failed_index.value()]++;
return least_recently_failed_index.value();
}
bool ClassicDnsServerIterator::AttemptAvailable() {
if (!resolve_context_->IsCurrentSession(session_))
return false;
for (int i : times_returned_) {
if (i < max_times_returned_)
return true;
}
return false;
}
} // namespace net