Commit 03adad67 authored by Eric Orth's avatar Eric Orth Committed by Commit Bot

Implement DNS transaction timeout

Unless |fast_timeout| is used (which will maintain previous behavior
of timing out immediately after the last attempt), on fallback of the
last attempt, a timer is run to time out the transaction.

Also doing the same on errors with the last attempt as long as at least
one previous attempt is still pending. And the transaction will also
return errors immediately if it was from the last pending attempt.

Bug: 1109792
Change-Id: Ibc8ed54428fcba247ed3526fa41584e27a118e81
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2464068
Commit-Queue: Eric Orth <ericorth@chromium.org>
Reviewed-by: default avatarMatt Menke <mmenke@chromium.org>
Cr-Commit-Position: refs/heads/master@{#818606}
parent fc48b8dd
......@@ -4,6 +4,7 @@
#include "net/dns/dns_transaction.h"
#include <algorithm>
#include <memory>
#include <set>
#include <string>
......@@ -31,6 +32,7 @@
#include "base/strings/stringprintf.h"
#include "base/threading/thread_checker.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/timer/elapsed_timer.h"
#include "base/timer/timer.h"
#include "base/values.h"
#include "build/build_config.h"
......@@ -130,8 +132,7 @@ base::Value NetLogStartParams(const std::string& hostname, uint16_t qtype) {
// matches. Logging is done in the socket and in the outer DnsTransaction.
class DnsAttempt {
public:
explicit DnsAttempt(size_t server_index)
: result_(ERR_FAILED), server_index_(server_index) {}
explicit DnsAttempt(size_t server_index) : server_index_(server_index) {}
virtual ~DnsAttempt() = default;
// Starts the attempt. Returns ERR_IO_PENDING if cannot complete synchronously
......@@ -165,21 +166,10 @@ class DnsAttempt {
return dict;
}
void set_result(int result) { result_ = result; }
// True if current attempt is pending (waiting for server response).
bool is_pending() const { return result_ == ERR_IO_PENDING; }
// True if attempt is completed (received server response).
bool is_completed() const {
return (result_ == OK) || (result_ == ERR_NAME_NOT_RESOLVED) ||
(result_ == ERR_DNS_SERVER_REQUIRES_TCP);
}
virtual bool IsPending() const = 0;
private:
// Result of last operation.
int result_;
const size_t server_index_;
DISALLOW_COPY_AND_ASSIGN(DnsAttempt);
......@@ -223,6 +213,8 @@ class DnsUDPAttempt : public DnsAttempt {
return socket_->NetLog();
}
bool IsPending() const override { return next_state_ != STATE_NONE; }
private:
enum State {
STATE_SEND_QUERY,
......@@ -257,13 +249,9 @@ class DnsUDPAttempt : public DnsAttempt {
}
} while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
set_result(rv);
if (rv == ERR_IO_PENDING)
return rv;
if (rv == OK)
if (rv != ERR_IO_PENDING)
DCHECK_EQ(STATE_NONE, next_state_);
return rv;
}
......@@ -527,6 +515,8 @@ class DnsHTTPAttempt : public DnsAttempt, public URLRequest::Delegate {
}
}
bool IsPending() const override { return !callback_.is_null(); }
private:
void ResponseCompleted(int net_error) {
request_.reset();
......@@ -620,7 +610,6 @@ class DnsTCPAttempt : public DnsAttempt {
int rv = socket_->Connect(
base::BindOnce(&DnsTCPAttempt::OnIOComplete, base::Unretained(this)));
if (rv == ERR_IO_PENDING) {
set_result(rv);
return rv;
}
return DoLoop(rv);
......@@ -637,6 +626,8 @@ class DnsTCPAttempt : public DnsAttempt {
return socket_->NetLog();
}
bool IsPending() const override { return next_state_ != STATE_NONE; }
private:
enum State {
STATE_CONNECT_COMPLETE,
......@@ -683,9 +674,9 @@ class DnsTCPAttempt : public DnsAttempt {
}
} while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
set_result(rv);
if (rv == OK)
if (rv != ERR_IO_PENDING)
DCHECK_EQ(STATE_NONE, next_state_);
return rv;
}
......@@ -1054,6 +1045,7 @@ class DnsTransactionImpl : public DnsTransaction,
secure_(secure),
secure_dns_mode_(secure_dns_mode),
callback_(std::move(callback)),
fast_timeout_(fast_timeout),
net_log_(net_log),
qnames_initial_size_(0),
attempts_count_(0),
......@@ -1089,6 +1081,7 @@ class DnsTransactionImpl : public DnsTransaction,
DCHECK(attempts_.empty());
net_log_.BeginEvent(NetLogEventType::DNS_TRANSACTION,
[&] { return NetLogStartParams(hostname_, qtype_); });
time_from_start_ = std::make_unique<base::ElapsedTimer>();
AttemptResult result(PrepareSearch(), nullptr);
if (result.rv == OK) {
qnames_initial_size_ = qnames_.size();
......@@ -1211,6 +1204,8 @@ class DnsTransactionImpl : public DnsTransaction,
}
AttemptResult MakeAttempt() {
DCHECK(MoreAttemptsAllowed());
DnsConfig config = session_->config();
if (secure_) {
DCHECK_GT(config.dns_over_https_servers.size(), 0u);
......@@ -1430,6 +1425,8 @@ class DnsTransactionImpl : public DnsTransaction,
// Resolves the result of a DnsAttempt until a terminal result is reached
// or it will complete asynchronously (ERR_IO_PENDING).
AttemptResult ProcessAttemptResult(AttemptResult result) {
DCHECK(!callback_.is_null());
while (result.rv != ERR_IO_PENDING) {
LogResponse(result.attempt);
......@@ -1463,17 +1460,25 @@ class DnsTransactionImpl : public DnsTransaction,
break;
case ERR_CONNECTION_REFUSED:
case ERR_DNS_TIMED_OUT:
timer_.Stop();
if (result.attempt) {
DCHECK(result.attempt == attempts_.back().get());
resolve_context_->RecordServerFailure(
result.attempt->server_index(), secure_ /* is_doh_server */,
result.rv, session_.get());
}
if (MoreAttemptsAllowed()) {
result = MakeAttempt();
} else {
return result;
}
break;
}
if (!fast_timeout_ && AnyAttemptPending()) {
StartTimeoutTimer();
return AttemptResult(ERR_IO_PENDING, nullptr);
}
return result;
case ERR_DNS_SERVER_REQUIRES_TCP:
result = RetryUdpAttemptAsTcp(result.attempt);
break;
......@@ -1491,28 +1496,43 @@ class DnsTransactionImpl : public DnsTransaction,
// ERR_DNS_TIMED_OUT case above). As the failure was already recorded
// at fallback time and is no longer being waited on, ignore this
// failure.
if (result.attempt != attempts_.back().get()) {
return AttemptResult(ERR_IO_PENDING, nullptr);
if (result.attempt == attempts_.back().get()) {
timer_.Stop();
resolve_context_->RecordServerFailure(
result.attempt->server_index(), secure_ /* is_doh_server */,
result.rv, session_.get());
if (MoreAttemptsAllowed()) {
result = MakeAttempt();
break;
}
resolve_context_->RecordServerFailure(result.attempt->server_index(),
secure_ /* is_doh_server */,
result.rv, session_.get());
if (!MoreAttemptsAllowed()) {
if (fast_timeout_) {
return result;
}
result = MakeAttempt();
break;
// No more attempts can be made, but there may be other attempts
// still pending, so start the timeout timer.
StartTimeoutTimer();
}
// If any attempts are still pending, continue to wait for them.
if (AnyAttemptPending()) {
DCHECK(timer_.IsRunning());
return AttemptResult(ERR_IO_PENDING, nullptr);
}
return result;
}
}
return result;
}
// Clears and cancels all non-completed attempts. If |leave_attempt| is not
// null, it is not cleared even if complete.
// Clears and cancels all pending attempts. If |leave_attempt| is not
// null, that attempt is not cleared even if pending.
void ClearAttempts(const DnsAttempt* leave_attempt) {
for (auto it = attempts_.begin(); it != attempts_.end();) {
if (!(*it)->is_completed() && it->get() != leave_attempt) {
if ((*it)->IsPending() && it->get() != leave_attempt) {
it = attempts_.erase(it);
} else {
++it;
......@@ -1520,6 +1540,13 @@ class DnsTransactionImpl : public DnsTransaction,
}
}
bool AnyAttemptPending() {
return std::any_of(attempts_.begin(), attempts_.end(),
[](std::unique_ptr<DnsAttempt>& attempt) {
return attempt->IsPending();
});
}
void OnFallbackPeriodExpired() {
if (callback_.is_null())
return;
......@@ -1530,6 +1557,29 @@ class DnsTransactionImpl : public DnsTransaction,
DoCallback(result);
}
void StartTimeoutTimer() {
DCHECK(!fast_timeout_);
DCHECK(!timer_.IsRunning());
DCHECK(!callback_.is_null());
// Timeout determination not currently implemented for Classic DNS because
// such transactions are assumed to always use fast timeout. If that ever
// changes, need to implement a ResolveContext::ClassicTransactionTimeout().
DCHECK(secure_);
base::TimeDelta timeout = resolve_context_->SecureTransactionTimeout(
secure_dns_mode_, session_.get());
timeout -= time_from_start_->Elapsed();
timer_.Start(FROM_HERE, timeout, this, &DnsTransactionImpl::OnTimeout);
}
void OnTimeout() {
if (callback_.is_null())
return;
DoCallback(AttemptResult(ERR_DNS_TIMED_OUT, nullptr));
}
scoped_refptr<DnsSession> session_;
std::string hostname_;
uint16_t qtype_;
......@@ -1539,6 +1589,11 @@ class DnsTransactionImpl : public DnsTransaction,
// Cleared in DoCallback.
DnsTransactionFactory::CallbackType callback_;
// When true, transaction should time out immediately on expiration of the
// last attempt fallback period rather than waiting the overall transaction
// timeout period.
const bool fast_timeout_;
NetLogWithSource net_log_;
// Search list of fully-qualified DNS names to query next (in DNS format).
......@@ -1557,6 +1612,7 @@ class DnsTransactionImpl : public DnsTransaction,
std::unique_ptr<DnsServerIterator> dns_server_iterator_;
base::OneShotTimer timer_;
std::unique_ptr<base::ElapsedTimer> time_from_start_;
// TODO(ericorth@chromium.org): Use base::UnownedPtr once available.
ResolveContext* resolve_context_;
......
......@@ -104,14 +104,12 @@ class NET_EXPORT_PRIVATE DnsTransactionFactory {
// |secure| specifies whether DNS lookups should be performed using DNS-over-
// HTTPS (DoH) or using plaintext DNS.
//
// |fast_timeout| Not yet implemented. When true (and implemented), the
// transaction will timeout quickly after making its DNS attempts, without
// necessarily waiting long enough to allow slower-than-average requests to
// complete. Intended as an optimization for cases where the caller has
// reasonable fallback options to the transaction and it would be beneficial
// to move on to those options sooner on signals that the transaction is
// potentially slow or problematic.
// TODO(crbug.com/1109792): Implement it.
// When |fast_timeout| is true, the transaction will timeout quickly after
// making its DNS attempts, without necessarily waiting long enough to allow
// slower-than-average requests to complete. Intended as an optimization for
// cases where the caller has reasonable fallback options to the transaction
// and it would be beneficial to move on to those options sooner on signals
// that the transaction is potentially slow or problematic.
virtual std::unique_ptr<DnsTransaction> CreateTransaction(
const std::string& hostname,
uint16_t qtype,
......
......@@ -1173,7 +1173,7 @@ TEST_F(DnsTransactionTest, NoDomain) {
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTestWithMockTime, Timeout) {
TEST_F(DnsTransactionTestWithMockTime, Timeout_FastTimeout) {
config_.attempts = 3;
ConfigureFactory();
......@@ -1182,8 +1182,13 @@ TEST_F(DnsTransactionTestWithMockTime, Timeout) {
AddHangingQuery(kT0HostName, kT0Qtype);
TransactionHelper helper0(ERR_DNS_TIMED_OUT);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
std::unique_ptr<DnsTransaction> transaction =
transaction_factory_->CreateTransaction(
kT0HostName, kT0Qtype, helper0.CompletionCallback(),
NetLogWithSource(), false /* secure */, SecureDnsMode::kOff,
resolve_context_.get(), true /* fast_timeout */);
helper0.StartTransaction(std::move(transaction));
// Finish when the third attempt expires its fallback period.
base::RunLoop().RunUntilIdle();
......@@ -2552,23 +2557,69 @@ TEST_F(DnsTransactionTest, HttpsPostLookupWithLog) {
}
// Test for when a slow DoH response is delayed until after the initial fallback
// period and no more attempts are configured.
// period (but succeeds before the full timeout period).
TEST_F(DnsTransactionTestWithMockTime, SlowHttpsResponse_SingleAttempt) {
config_.doh_attempts = 1;
ConfigureDohServers(false /* use_post */);
// Assume fallback period is less than timeout.
ASSERT_LT(resolve_context_->NextDohFallbackPeriod(0 /* doh_server_index */,
session_.get()),
resolve_context_->SecureTransactionTimeout(SecureDnsMode::kSecure,
session_.get()));
// Simulate a slow response by using an ERR_IO_PENDING read error to delay
// until SequencedSocketData::Resume() is called.
auto data = std::make_unique<DnsSocketData>(
0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128);
data->AddReadError(ERR_IO_PENDING, ASYNC);
data->AddResponseData(kT0ResponseDatagram, base::size(kT0ResponseDatagram),
ASYNC);
SequencedSocketData* sequenced_socket_data = data->GetProvider();
AddSocketData(std::move(data), false /* enqueue_transaction_id */);
TransactionHelper helper(kT0RecordCount);
std::unique_ptr<DnsTransaction> transaction =
transaction_factory_->CreateTransaction(
kT0HostName, kT0Qtype, helper.CompletionCallback(),
NetLogWithSource(), true /* secure */, SecureDnsMode::kSecure,
resolve_context_.get(), false /* fast_timeout */);
helper.StartTransaction(std::move(transaction));
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(helper.has_completed());
FastForwardBy(resolve_context_->NextDohFallbackPeriod(
0 /* doh_server_index */, session_.get()));
EXPECT_FALSE(helper.has_completed());
sequenced_socket_data->Resume();
helper.RunUntilComplete();
}
// Test for when a slow DoH response is delayed until after the initial fallback
// period but fast timeout is enabled, resulting in timeout failure.
TEST_F(DnsTransactionTestWithMockTime,
SlowHttpsResponse_SingleAttempt_FastTimeout) {
config_.doh_attempts = 1;
ConfigureDohServers(false /* use_post */);
AddHangingQuery(kT0HostName, kT0Qtype,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, 0 /* id */,
false /* enqueue_transaction_id */);
TransactionHelper helper(ERR_DNS_TIMED_OUT);
helper.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
std::unique_ptr<DnsTransaction> transaction =
transaction_factory_->CreateTransaction(
kT0HostName, kT0Qtype, helper.CompletionCallback(),
NetLogWithSource(), true /* secure */, SecureDnsMode::kSecure,
resolve_context_.get(), true /* fast_timeout */);
helper.StartTransaction(std::move(transaction));
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(helper.has_completed());
// Only one attempt configured, so expect immediate failure after fallback
// period.
// Only one attempt configured and fast timeout enabled, so expect immediate
// failure after fallback period.
FastForwardBy(resolve_context_->NextDohFallbackPeriod(
0 /* doh_server_index */, session_.get()));
EXPECT_TRUE(helper.has_completed());
......@@ -2592,8 +2643,13 @@ TEST_F(DnsTransactionTestWithMockTime, SlowHttpsResponse_TwoAttempts) {
AddSocketData(std::move(data), false /* enqueue_transaction_id */);
TransactionHelper helper(kT0RecordCount);
helper.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
std::unique_ptr<DnsTransaction> transaction =
transaction_factory_->CreateTransaction(
kT0HostName, kT0Qtype, helper.CompletionCallback(),
NetLogWithSource(), true /* secure */, SecureDnsMode::kSecure,
resolve_context_.get(), false /* fast_timeout */);
helper.StartTransaction(std::move(transaction));
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(helper.has_completed());
ASSERT_TRUE(sequenced_socket_data->IsPaused());
......@@ -2613,6 +2669,330 @@ TEST_F(DnsTransactionTestWithMockTime, SlowHttpsResponse_TwoAttempts) {
helper.RunUntilComplete();
}
// Test for when a slow DoH response is delayed until after the full timeout
// period.
TEST_F(DnsTransactionTestWithMockTime, HttpsTimeout) {
config_.doh_attempts = 1;
ConfigureDohServers(false /* use_post */);
// Assume fallback period is less than timeout.
ASSERT_LT(resolve_context_->NextDohFallbackPeriod(0 /* doh_server_index */,
session_.get()),
resolve_context_->SecureTransactionTimeout(SecureDnsMode::kSecure,
session_.get()));
AddHangingQuery(kT0HostName, kT0Qtype,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, 0 /* id */,
false /* enqueue_transaction_id */);
TransactionHelper helper(ERR_DNS_TIMED_OUT);
std::unique_ptr<DnsTransaction> transaction =
transaction_factory_->CreateTransaction(
kT0HostName, kT0Qtype, helper.CompletionCallback(),
NetLogWithSource(), true /* secure */, SecureDnsMode::kSecure,
resolve_context_.get(), false /* fast_timeout */);
helper.StartTransaction(std::move(transaction));
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(helper.has_completed());
// Stop a tiny bit short to ensure transaction doesn't finish early.
const base::TimeDelta kTimeHoldback = base::TimeDelta::FromMilliseconds(5);
base::TimeDelta timeout = resolve_context_->SecureTransactionTimeout(
SecureDnsMode::kSecure, session_.get());
ASSERT_LT(kTimeHoldback, timeout);
FastForwardBy(timeout - kTimeHoldback);
EXPECT_FALSE(helper.has_completed());
FastForwardBy(kTimeHoldback);
EXPECT_TRUE(helper.has_completed());
}
// Test for when two slow DoH responses are delayed until after the full timeout
// period.
TEST_F(DnsTransactionTestWithMockTime, HttpsTimeout2) {
config_.doh_attempts = 2;
ConfigureDohServers(false /* use_post */);
// Assume fallback period is less than timeout.
ASSERT_LT(resolve_context_->NextDohFallbackPeriod(0 /* doh_server_index */,
session_.get()),
resolve_context_->SecureTransactionTimeout(SecureDnsMode::kSecure,
session_.get()));
AddHangingQuery(kT0HostName, kT0Qtype,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, 0 /* id */,
false /* enqueue_transaction_id */);
AddHangingQuery(kT0HostName, kT0Qtype,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, 0 /* id */,
false /* enqueue_transaction_id */);
TransactionHelper helper(ERR_DNS_TIMED_OUT);
std::unique_ptr<DnsTransaction> transaction =
transaction_factory_->CreateTransaction(
kT0HostName, kT0Qtype, helper.CompletionCallback(),
NetLogWithSource(), true /* secure */, SecureDnsMode::kSecure,
resolve_context_.get(), false /* fast_timeout */);
helper.StartTransaction(std::move(transaction));
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(helper.has_completed());
base::TimeDelta fallback_period = resolve_context_->NextDohFallbackPeriod(
0 /* doh_server_index */, session_.get());
FastForwardBy(fallback_period);
EXPECT_FALSE(helper.has_completed());
// Timeout is from start of transaction, so need to keep track of the
// remainder after other fast forwards.
base::TimeDelta timeout = resolve_context_->SecureTransactionTimeout(
SecureDnsMode::kSecure, session_.get());
base::TimeDelta timeout_remainder = timeout - fallback_period;
// Fallback period for second attempt.
fallback_period = resolve_context_->NextDohFallbackPeriod(
0 /* doh_server_index */, session_.get());
ASSERT_LT(fallback_period, timeout_remainder);
FastForwardBy(fallback_period);
EXPECT_FALSE(helper.has_completed());
timeout_remainder -= fallback_period;
// Stop a tiny bit short to ensure transaction doesn't finish early.
const base::TimeDelta kTimeHoldback = base::TimeDelta::FromMilliseconds(5);
ASSERT_LT(kTimeHoldback, timeout_remainder);
FastForwardBy(timeout_remainder - kTimeHoldback);
EXPECT_FALSE(helper.has_completed());
FastForwardBy(kTimeHoldback);
EXPECT_TRUE(helper.has_completed());
}
// Test for when attempt fallback periods go beyond the full timeout period.
TEST_F(DnsTransactionTestWithMockTime, LongHttpsTimeouts) {
const int kNumAttempts = 20;
config_.doh_attempts = kNumAttempts;
ConfigureDohServers(false /* use_post */);
// Assume sum of fallback periods is greater than timeout.
ASSERT_GT(kNumAttempts * resolve_context_->NextDohFallbackPeriod(
0 /* doh_server_index */, session_.get()),
resolve_context_->SecureTransactionTimeout(SecureDnsMode::kSecure,
session_.get()));
for (int i = 0; i < kNumAttempts; ++i) {
AddHangingQuery(kT0HostName, kT0Qtype,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, 0 /* id */,
false /* enqueue_transaction_id */);
}
TransactionHelper helper(ERR_DNS_TIMED_OUT);
std::unique_ptr<DnsTransaction> transaction =
transaction_factory_->CreateTransaction(
kT0HostName, kT0Qtype, helper.CompletionCallback(),
NetLogWithSource(), true /* secure */, SecureDnsMode::kSecure,
resolve_context_.get(), false /* fast_timeout */);
helper.StartTransaction(std::move(transaction));
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(helper.has_completed());
for (int i = 0; i < kNumAttempts - 1; ++i) {
FastForwardBy(resolve_context_->NextDohFallbackPeriod(
0 /* doh_server_index */, session_.get()));
EXPECT_FALSE(helper.has_completed());
}
// Expect transaction to time out immediately after the last fallback period.
FastForwardBy(resolve_context_->NextDohFallbackPeriod(
0 /* doh_server_index */, session_.get()));
EXPECT_TRUE(helper.has_completed());
}
// Test for when the last of multiple HTTPS attempts fails (SERVFAIL) before
// a previous attempt succeeds.
TEST_F(DnsTransactionTestWithMockTime, LastHttpsAttemptFails) {
config_.doh_attempts = 2;
ConfigureDohServers(false /* use_post */);
// Simulate a slow response by using an ERR_IO_PENDING read error to delay
// until SequencedSocketData::Resume() is called.
auto data = std::make_unique<DnsSocketData>(
0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128);
data->AddReadError(ERR_IO_PENDING, ASYNC);
data->AddResponseData(kT0ResponseDatagram, base::size(kT0ResponseDatagram),
ASYNC);
SequencedSocketData* sequenced_socket_data = data->GetProvider();
AddSocketData(std::move(data), false /* enqueue_transaction_id */);
AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL,
SYNCHRONOUS, Transport::HTTPS,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, 0 /* id */,
false /* enqueue_transaction_id */);
TransactionHelper helper(kT0RecordCount);
std::unique_ptr<DnsTransaction> transaction =
transaction_factory_->CreateTransaction(
kT0HostName, kT0Qtype, helper.CompletionCallback(),
NetLogWithSource(), true /* secure */, SecureDnsMode::kSecure,
resolve_context_.get(), false /* fast_timeout */);
helper.StartTransaction(std::move(transaction));
// Wait for one timeout period to start (and fail) the second attempt.
FastForwardBy(resolve_context_->NextDohFallbackPeriod(
0 /* doh_server_index */, session_.get()));
EXPECT_FALSE(helper.has_completed());
// Complete the first attempt and expect immediate success.
sequenced_socket_data->Resume();
helper.RunUntilComplete();
}
// Test for when the last of multiple HTTPS attempts fails (SERVFAIL), and a
// previous attempt never completes.
TEST_F(DnsTransactionTestWithMockTime, LastHttpsAttemptFails_Timeout) {
config_.doh_attempts = 2;
ConfigureDohServers(false /* use_post */);
AddHangingQuery(kT0HostName, kT0Qtype,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, 0 /* id */,
false /* enqueue_transaction_id */);
AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL,
SYNCHRONOUS, Transport::HTTPS,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, 0 /* id */,
false /* enqueue_transaction_id */);
TransactionHelper helper(ERR_DNS_TIMED_OUT);
std::unique_ptr<DnsTransaction> transaction =
transaction_factory_->CreateTransaction(
kT0HostName, kT0Qtype, helper.CompletionCallback(),
NetLogWithSource(), true /* secure */, SecureDnsMode::kSecure,
resolve_context_.get(), false /* fast_timeout */);
helper.StartTransaction(std::move(transaction));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(helper.has_completed());
// Second attempt fails immediately after first fallback period, but because
// fast timeout is disabled, the transaction will attempt to wait for the
// first attempt.
base::TimeDelta fallback_period = resolve_context_->NextDohFallbackPeriod(
0 /* doh_server_index */, session_.get());
FastForwardBy(fallback_period);
EXPECT_FALSE(helper.has_completed());
// Timeout is from start of transaction, so need to keep track of the
// remainder after other fast forwards.
base::TimeDelta timeout = resolve_context_->SecureTransactionTimeout(
SecureDnsMode::kSecure, session_.get());
base::TimeDelta timeout_remainder = timeout - fallback_period;
// Stop a tiny bit short to ensure transaction doesn't finish early.
const base::TimeDelta kTimeHoldback = base::TimeDelta::FromMilliseconds(5);
ASSERT_LT(kTimeHoldback, timeout_remainder);
FastForwardBy(timeout_remainder - kTimeHoldback);
EXPECT_FALSE(helper.has_completed());
FastForwardBy(kTimeHoldback);
EXPECT_TRUE(helper.has_completed());
}
// Test for when the last of multiple HTTPS attempts fails (SERVFAIL) before
// a previous attempt can complete, but fast timeouts is enabled.
TEST_F(DnsTransactionTestWithMockTime, LastHttpsAttemptFails_FastTimeout) {
config_.doh_attempts = 2;
ConfigureDohServers(false /* use_post */);
AddHangingQuery(kT0HostName, kT0Qtype,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, 0 /* id */,
false /* enqueue_transaction_id */);
AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL,
SYNCHRONOUS, Transport::HTTPS,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, 0 /* id */,
false /* enqueue_transaction_id */);
TransactionHelper helper(ERR_DNS_SERVER_FAILED);
std::unique_ptr<DnsTransaction> transaction =
transaction_factory_->CreateTransaction(
kT0HostName, kT0Qtype, helper.CompletionCallback(),
NetLogWithSource(), true /* secure */, SecureDnsMode::kSecure,
resolve_context_.get(), true /* fast_timeout */);
helper.StartTransaction(std::move(transaction));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(helper.has_completed());
// With fast timeout enabled, expect the transaction to complete with failure
// immediately on failure of the last transaction.
FastForwardBy(resolve_context_->NextDohFallbackPeriod(
0 /* doh_server_index */, session_.get()));
EXPECT_TRUE(helper.has_completed());
}
// Test for when the last of multiple HTTPS attempts fails (SERVFAIL) before
// a previous attempt later fails as well.
TEST_F(DnsTransactionTestWithMockTime, LastHttpsAttemptFailsFirst) {
config_.doh_attempts = 2;
ConfigureDohServers(false /* use_post */);
// Simulate a slow response by using an ERR_IO_PENDING read error to delay
// until SequencedSocketData::Resume() is called.
auto data = std::make_unique<DnsSocketData>(
0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128);
data->AddReadError(ERR_IO_PENDING, ASYNC);
data->AddRcode(dns_protocol::kRcodeSERVFAIL, ASYNC);
SequencedSocketData* sequenced_socket_data = data->GetProvider();
AddSocketData(std::move(data), false /* enqueue_transaction_id */);
AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL,
SYNCHRONOUS, Transport::HTTPS,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, 0 /* id */,
false /* enqueue_transaction_id */);
TransactionHelper helper(ERR_DNS_SERVER_FAILED);
std::unique_ptr<DnsTransaction> transaction =
transaction_factory_->CreateTransaction(
kT0HostName, kT0Qtype, helper.CompletionCallback(),
NetLogWithSource(), true /* secure */, SecureDnsMode::kSecure,
resolve_context_.get(), false /* fast_timeout */);
helper.StartTransaction(std::move(transaction));
// Wait for one timeout period to start (and fail) the second attempt.
FastForwardBy(resolve_context_->NextDohFallbackPeriod(
0 /* doh_server_index */, session_.get()));
EXPECT_FALSE(helper.has_completed());
// Complete the first attempt and expect immediate completion.
sequenced_socket_data->Resume();
helper.RunUntilComplete();
}
// Test for when multiple HTTPS attempts fail (SERVFAIL) in order, making the
// last started attempt also the last attempt to be pending.
TEST_F(DnsTransactionTestWithMockTime, LastHttpsAttemptFailsLast) {
config_.doh_attempts = 2;
ConfigureDohServers(false /* use_post */);
AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL,
SYNCHRONOUS, Transport::HTTPS,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, 0 /* id */,
false /* enqueue_transaction_id */);
AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL,
SYNCHRONOUS, Transport::HTTPS,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, 0 /* id */,
false /* enqueue_transaction_id */);
TransactionHelper helper(ERR_DNS_SERVER_FAILED);
std::unique_ptr<DnsTransaction> transaction =
transaction_factory_->CreateTransaction(
kT0HostName, kT0Qtype, helper.CompletionCallback(),
NetLogWithSource(), true /* secure */, SecureDnsMode::kSecure,
resolve_context_.get(), false /* fast_timeout */);
helper.StartTransaction(std::move(transaction));
// Expect both attempts will run quickly without waiting for fallbacks or
// transaction timeout.
helper.RunUntilComplete();
}
TEST_F(DnsTransactionTest, TcpLookup_UdpRetry) {
AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment