blob: 79d8553077a4618991efa4df49defd4ef9e2c2cb [file] [log] [blame]
// Copyright (c) 2012 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_transaction.h"
#include <stdint.h>
#include <limits>
#include <utility>
#include <vector>
#include "base/base64url.h"
#include "base/bind.h"
#include "base/containers/circular_deque.h"
#include "base/optional.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/sys_byteorder.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/values.h"
#include "net/base/ip_address.h"
#include "net/base/port_util.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/base/url_util.h"
#include "net/cookies/cookie_access_result.h"
#include "net/cookies/cookie_util.h"
#include "net/dns/dns_config.h"
#include "net/dns/dns_query.h"
#include "net/dns/dns_response.h"
#include "net/dns/dns_server_iterator.h"
#include "net/dns/dns_session.h"
#include "net/dns/dns_socket_allocator.h"
#include "net/dns/dns_test_util.h"
#include "net/dns/dns_util.h"
#include "net/dns/public/dns_over_https_server_config.h"
#include "net/dns/public/dns_protocol.h"
#include "net/dns/resolve_context.h"
#include "net/log/net_log.h"
#include "net/log/net_log_capture_mode.h"
#include "net/log/net_log_with_source.h"
#include "net/log/test_net_log.h"
#include "net/proxy_resolution/proxy_config_service_fixed.h"
#include "net/socket/socket_test_util.h"
#include "net/test/gtest_util.h"
#include "net/test/test_with_task_environment.h"
#include "net/test/url_request/url_request_failed_job.h"
#include "net/third_party/uri_template/uri_template.h"
#include "net/url_request/url_request_filter.h"
#include "net/url_request/url_request_interceptor.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using net::test::IsOk;
namespace net {
namespace {
base::TimeDelta kFallbackPeriod = base::TimeDelta::FromSeconds(1);
const char kMockHostname[] = "mock.http";
std::string DomainFromDot(const base::StringPiece& dotted) {
std::string out;
EXPECT_TRUE(DNSDomainFromDot(dotted, &out));
return out;
}
enum class Transport { UDP, TCP, HTTPS };
// A SocketDataProvider builder.
class DnsSocketData {
public:
// The ctor takes parameters for the DnsQuery.
DnsSocketData(uint16_t id,
const char* dotted_name,
uint16_t qtype,
IoMode mode,
Transport transport,
const OptRecordRdata* opt_rdata = nullptr,
DnsQuery::PaddingStrategy padding_strategy =
DnsQuery::PaddingStrategy::NONE)
: query_(new DnsQuery(id,
DomainFromDot(dotted_name),
qtype,
opt_rdata,
padding_strategy)),
transport_(transport) {
if (Transport::TCP == transport_) {
std::unique_ptr<uint16_t> length(new uint16_t);
*length = base::HostToNet16(query_->io_buffer()->size());
writes_.push_back(MockWrite(mode,
reinterpret_cast<const char*>(length.get()),
sizeof(uint16_t), num_reads_and_writes()));
lengths_.push_back(std::move(length));
}
writes_.push_back(MockWrite(mode, query_->io_buffer()->data(),
query_->io_buffer()->size(),
num_reads_and_writes()));
}
~DnsSocketData() = default;
// All responses must be added before GetProvider.
// Adds pre-built DnsResponse. |tcp_length| will be used in TCP mode only.
void AddResponseWithLength(std::unique_ptr<DnsResponse> response,
IoMode mode,
uint16_t tcp_length) {
CHECK(!provider_.get());
if (Transport::TCP == transport_) {
std::unique_ptr<uint16_t> length(new uint16_t);
*length = base::HostToNet16(tcp_length);
reads_.push_back(MockRead(mode,
reinterpret_cast<const char*>(length.get()),
sizeof(uint16_t), num_reads_and_writes()));
lengths_.push_back(std::move(length));
}
reads_.push_back(MockRead(mode, response->io_buffer()->data(),
response->io_buffer_size(),
num_reads_and_writes()));
responses_.push_back(std::move(response));
}
// Adds pre-built DnsResponse.
void AddResponse(std::unique_ptr<DnsResponse> response, IoMode mode) {
uint16_t tcp_length = response->io_buffer_size();
AddResponseWithLength(std::move(response), mode, tcp_length);
}
// Adds pre-built response from |data| buffer.
void AddResponseData(const uint8_t* data, size_t length, IoMode mode) {
CHECK(!provider_.get());
AddResponse(std::make_unique<DnsResponse>(
reinterpret_cast<const char*>(data), length, 0),
mode);
}
// Adds pre-built response from |data| buffer.
void AddResponseData(const uint8_t* data,
size_t length,
int offset,
IoMode mode) {
CHECK(!provider_.get());
AddResponse(
std::make_unique<DnsResponse>(reinterpret_cast<const char*>(data),
length - offset, offset),
mode);
}
// Add no-answer (RCODE only) response matching the query.
void AddRcode(int rcode, IoMode mode) {
std::unique_ptr<DnsResponse> response(new DnsResponse(
query_->io_buffer()->data(), query_->io_buffer()->size(), 0));
dns_protocol::Header* header =
reinterpret_cast<dns_protocol::Header*>(response->io_buffer()->data());
header->flags |= base::HostToNet16(dns_protocol::kFlagResponse | rcode);
AddResponse(std::move(response), mode);
}
// Add error response.
void AddReadError(int error, IoMode mode) {
reads_.push_back(MockRead(mode, error, num_reads_and_writes()));
}
// Build, if needed, and return the SocketDataProvider. No new responses
// should be added afterwards.
SequencedSocketData* GetProvider() {
if (provider_.get())
return provider_.get();
// Terminate the reads with ERR_IO_PENDING to prevent overrun and default to
// timeout.
if (transport_ != Transport::HTTPS) {
reads_.push_back(MockRead(SYNCHRONOUS, ERR_IO_PENDING,
writes_.size() + reads_.size()));
}
provider_.reset(new SequencedSocketData(reads_, writes_));
if (Transport::TCP == transport_ || Transport::HTTPS == transport_) {
provider_->set_connect_data(MockConnect(reads_[0].mode, OK));
}
return provider_.get();
}
uint16_t query_id() const { return query_->id(); }
IOBufferWithSize* query_buffer() { return query_->io_buffer(); }
private:
size_t num_reads_and_writes() const { return reads_.size() + writes_.size(); }
std::unique_ptr<DnsQuery> query_;
Transport transport_;
std::vector<std::unique_ptr<uint16_t>> lengths_;
std::vector<std::unique_ptr<DnsResponse>> responses_;
std::vector<MockWrite> writes_;
std::vector<MockRead> reads_;
std::unique_ptr<SequencedSocketData> provider_;
DISALLOW_COPY_AND_ASSIGN(DnsSocketData);
};
class TestSocketFactory;
// A variant of MockUDPClientSocket which always fails to Connect.
class FailingUDPClientSocket : public MockUDPClientSocket {
public:
FailingUDPClientSocket(SocketDataProvider* data, net::NetLog* net_log)
: MockUDPClientSocket(data, net_log) {}
~FailingUDPClientSocket() override = default;
int Connect(const IPEndPoint& endpoint) override {
return ERR_CONNECTION_REFUSED;
}
private:
DISALLOW_COPY_AND_ASSIGN(FailingUDPClientSocket);
};
// A variant of MockUDPClientSocket which notifies the factory OnConnect.
class TestUDPClientSocket : public MockUDPClientSocket {
public:
TestUDPClientSocket(TestSocketFactory* factory,
SocketDataProvider* data,
net::NetLog* net_log)
: MockUDPClientSocket(data, net_log), factory_(factory) {}
~TestUDPClientSocket() override = default;
int Connect(const IPEndPoint& endpoint) override;
private:
TestSocketFactory* factory_;
DISALLOW_COPY_AND_ASSIGN(TestUDPClientSocket);
};
// Creates TestUDPClientSockets and keeps endpoints reported via OnConnect.
class TestSocketFactory : public MockClientSocketFactory {
public:
TestSocketFactory() = default;
~TestSocketFactory() override = default;
std::unique_ptr<DatagramClientSocket> CreateDatagramClientSocket(
DatagramSocket::BindType bind_type,
NetLog* net_log,
const NetLogSource& source) override {
if (fail_next_socket_) {
fail_next_socket_ = false;
return std::unique_ptr<DatagramClientSocket>(
new FailingUDPClientSocket(&empty_data_, net_log));
}
SocketDataProvider* data_provider = mock_data().GetNext();
auto socket =
std::make_unique<TestUDPClientSocket>(this, data_provider, net_log);
// Even using DEFAULT_BIND, actual sockets have been measured to very rarely
// repeat the same source port multiple times in a row. Need to mimic that
// functionality here, so DnsUdpTracker doesn't misdiagnose repeated port
// as low entropy.
if (diverse_source_ports_)
socket->set_source_port(next_source_port_++);
return socket;
}
void OnConnect(const IPEndPoint& endpoint) {
remote_endpoints_.emplace_back(endpoint);
}
struct RemoteNameserver {
explicit RemoteNameserver(IPEndPoint insecure_nameserver)
: insecure_nameserver(insecure_nameserver) {}
explicit RemoteNameserver(DnsOverHttpsServerConfig secure_nameserver)
: secure_nameserver(secure_nameserver) {}
base::Optional<IPEndPoint> insecure_nameserver;
base::Optional<DnsOverHttpsServerConfig> secure_nameserver;
};
std::vector<RemoteNameserver> remote_endpoints_;
bool fail_next_socket_ = false;
bool diverse_source_ports_ = true;
private:
StaticSocketDataProvider empty_data_;
uint16_t next_source_port_ = 123;
};
int TestUDPClientSocket::Connect(const IPEndPoint& endpoint) {
factory_->OnConnect(endpoint);
return MockUDPClientSocket::Connect(endpoint);
}
// Helper class that holds a DnsTransaction and handles OnTransactionComplete.
class TransactionHelper {
public:
// If |expected_answer_count| < 0 then it is the expected net error.
explicit TransactionHelper(int expected_answer_count)
: expected_answer_count_(expected_answer_count) {}
// Mark that the transaction shall be destroyed immediately upon callback.
void set_cancel_in_callback() { cancel_in_callback_ = true; }
void StartTransaction(DnsTransactionFactory* factory,
const char* hostname,
uint16_t qtype,
bool secure,
ResolveContext* context) {
std::unique_ptr<DnsTransaction> transaction = factory->CreateTransaction(
hostname, qtype, CompletionCallback(),
NetLogWithSource::Make(&net_log_, net::NetLogSourceType::NONE), secure,
factory->GetSecureDnsModeForTest(), context, true /* fast_timeout */);
transaction->SetRequestPriority(DEFAULT_PRIORITY);
EXPECT_EQ(qtype, transaction->GetType());
StartTransaction(std::move(transaction));
}
void StartTransaction(std::unique_ptr<DnsTransaction> transaction) {
EXPECT_FALSE(transaction_);
transaction_ = std::move(transaction);
qtype_ = transaction_->GetType();
transaction_->Start();
}
void Cancel() {
ASSERT_TRUE(transaction_.get() != nullptr);
transaction_.reset(nullptr);
}
DnsTransactionFactory::CallbackType CompletionCallback() {
return base::BindOnce(&TransactionHelper::OnTransactionComplete,
base::Unretained(this));
}
void OnTransactionComplete(DnsTransaction* t,
int rv,
const DnsResponse* response,
base::Optional<std::string> doh_provider_id) {
EXPECT_FALSE(completed_);
EXPECT_EQ(transaction_.get(), t);
completed_ = true;
response_ = response;
transaction_complete_run_loop_.Quit();
if (cancel_in_callback_) {
Cancel();
return;
}
if (response)
EXPECT_TRUE(response->IsValid());
if (expected_answer_count_ >= 0) {
ASSERT_THAT(rv, IsOk());
ASSERT_TRUE(response != nullptr);
EXPECT_EQ(static_cast<unsigned>(expected_answer_count_),
response->answer_count());
EXPECT_EQ(qtype_, response->qtype());
DnsRecordParser parser = response->Parser();
DnsResourceRecord record;
for (int i = 0; i < expected_answer_count_; ++i) {
EXPECT_TRUE(parser.ReadRecord(&record));
}
} else {
EXPECT_EQ(expected_answer_count_, rv);
}
}
bool has_completed() const { return completed_; }
const DnsResponse* response() const { return response_; }
NetLog* net_log() { return &net_log_; }
// Runs until the completion callback is called. Transaction must have already
// been started or this will never complete.
void RunUntilComplete() {
DCHECK(transaction_);
DCHECK(!transaction_complete_run_loop_.running());
transaction_complete_run_loop_.Run();
DCHECK(has_completed());
}
private:
uint16_t qtype_ = 0;
std::unique_ptr<DnsTransaction> transaction_;
const DnsResponse* response_ = nullptr;
int expected_answer_count_;
bool cancel_in_callback_ = false;
base::RunLoop transaction_complete_run_loop_;
bool completed_ = false;
TestNetLog net_log_;
};
// Callback that allows a test to modify HttpResponseinfo
// before the response is sent to the requester. This allows
// response headers to be changed.
typedef base::RepeatingCallback<void(URLRequest* request,
HttpResponseInfo* info)>
ResponseModifierCallback;
// Callback that allows the test to substitute its own implementation
// of URLRequestJob to handle the request.
typedef base::RepeatingCallback<std::unique_ptr<URLRequestJob>(
URLRequest* request,
SocketDataProvider* data_provider)>
DohJobMakerCallback;
// Subclass of URLRequestJob which takes a SocketDataProvider with data
// representing both a DNS over HTTPS query and response.
class URLRequestMockDohJob : public URLRequestJob, public AsyncSocket {
public:
URLRequestMockDohJob(
URLRequest* request,
SocketDataProvider* data_provider,
ResponseModifierCallback response_modifier = ResponseModifierCallback())
: URLRequestJob(request),
content_length_(0),
leftover_data_len_(0),
data_provider_(data_provider),
response_modifier_(response_modifier) {
data_provider_->Initialize(this);
MatchQueryData(request, data_provider);
}
// Compare the query contained in either the POST body or the body
// parameter of the GET query to the write data of the SocketDataProvider.
static void MatchQueryData(URLRequest* request,
SocketDataProvider* data_provider) {
std::string decoded_query;
if (request->method() == "GET") {
std::string encoded_query;
EXPECT_TRUE(GetValueForKeyInQuery(request->url(), "dns", &encoded_query));
EXPECT_GT(encoded_query.size(), 0ul);
EXPECT_TRUE(base::Base64UrlDecode(
encoded_query, base::Base64UrlDecodePolicy::IGNORE_PADDING,
&decoded_query));
} else if (request->method() == "POST") {
const UploadDataStream* stream = request->get_upload_for_testing();
auto* readers = stream->GetElementReaders();
EXPECT_TRUE(readers);
EXPECT_FALSE(readers->empty());
for (auto& reader : *readers) {
const UploadBytesElementReader* byte_reader = reader->AsBytesReader();
decoded_query +=
std::string(byte_reader->bytes(), byte_reader->length());
}
}
std::string query(decoded_query);
MockWriteResult result(SYNCHRONOUS, 1);
while (result.result > 0 && query.length() > 0) {
result = data_provider->OnWrite(query);
if (result.result > 0)
query = query.substr(result.result);
}
}
static std::string GetMockHttpsUrl(const std::string& path) {
return "https://" + (kMockHostname + ("/" + path));
}
// URLRequestJob implementation:
void Start() override {
// Start reading asynchronously so that all error reporting and data
// callbacks happen as they would for network requests.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&URLRequestMockDohJob::StartAsync,
weak_factory_.GetWeakPtr()));
}
~URLRequestMockDohJob() override {
if (data_provider_)
data_provider_->DetachSocket();
}
int ReadRawData(IOBuffer* buf, int buf_size) override {
if (!data_provider_)
return ERR_FAILED;
if (leftover_data_len_ > 0) {
int rv = DoBufferCopy(leftover_data_, leftover_data_len_, buf, buf_size);
return rv;
}
if (data_provider_->AllReadDataConsumed())
return 0;
MockRead read = data_provider_->OnRead();
if (read.result < ERR_IO_PENDING)
return read.result;
if (read.result == ERR_IO_PENDING) {
pending_buf_ = buf;
pending_buf_size_ = buf_size;
return ERR_IO_PENDING;
}
return DoBufferCopy(read.data, read.data_len, buf, buf_size);
}
void GetResponseInfo(HttpResponseInfo* info) override {
// Send back mock headers.
std::string raw_headers;
raw_headers.append(
"HTTP/1.1 200 OK\n"
"Content-type: application/dns-message\n");
if (content_length_ > 0) {
raw_headers.append(base::StringPrintf("Content-Length: %1d\n",
static_cast<int>(content_length_)));
}
info->headers = base::MakeRefCounted<HttpResponseHeaders>(
HttpUtil::AssembleRawHeaders(raw_headers));
if (response_modifier_)
response_modifier_.Run(request(), info);
}
// AsyncSocket implementation:
void OnReadComplete(const MockRead& data) override {
EXPECT_NE(data.result, ERR_IO_PENDING);
if (data.result < 0)
return ReadRawDataComplete(data.result);
ReadRawDataComplete(DoBufferCopy(data.data, data.data_len, pending_buf_,
pending_buf_size_));
}
void OnWriteComplete(int rv) override {}
void OnConnectComplete(const MockConnect& data) override {}
void OnDataProviderDestroyed() override { data_provider_ = nullptr; }
private:
void StartAsync() {
if (!request_)
return;
if (content_length_)
set_expected_content_size(content_length_);
NotifyHeadersComplete();
}
int DoBufferCopy(const char* data,
int data_len,
IOBuffer* buf,
int buf_size) {
if (data_len > buf_size) {
memcpy(buf->data(), data, buf_size);
leftover_data_ = data + buf_size;
leftover_data_len_ = data_len - buf_size;
return buf_size;
}
memcpy(buf->data(), data, data_len);
return data_len;
}
const int content_length_;
const char* leftover_data_;
int leftover_data_len_;
SocketDataProvider* data_provider_;
const ResponseModifierCallback response_modifier_;
IOBuffer* pending_buf_;
int pending_buf_size_;
base::WeakPtrFactory<URLRequestMockDohJob> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(URLRequestMockDohJob);
};
class DnsTransactionTestBase : public testing::Test {
public:
DnsTransactionTestBase() = default;
~DnsTransactionTestBase() override {
// All queued transaction IDs should be used by a transaction calling
// GetNextId().
CHECK(transaction_ids_.empty());
}
// Generates |nameservers| for DnsConfig.
void ConfigureNumServers(size_t num_servers) {
CHECK_LE(num_servers, 255u);
config_.nameservers.clear();
for (size_t i = 0; i < num_servers; ++i) {
config_.nameservers.push_back(
IPEndPoint(IPAddress(192, 168, 1, i), dns_protocol::kDefaultPort));
}
}
// Configures the DnsConfig DNS-over-HTTPS server(s), which either
// accept GET or POST requests based on use_post. If a
// ResponseModifierCallback is provided it will be called to construct the
// HTTPResponse.
void ConfigureDohServers(bool use_post,
size_t num_doh_servers = 1,
bool make_available = true) {
GURL url(URLRequestMockDohJob::GetMockHttpsUrl("doh_test"));
URLRequestFilter* filter = URLRequestFilter::GetInstance();
filter->AddHostnameInterceptor(url.scheme(), url.host(),
std::make_unique<DohJobInterceptor>(this));
CHECK_LE(num_doh_servers, 255u);
for (size_t i = 0; i < num_doh_servers; ++i) {
std::string server_template(URLRequestMockDohJob::GetMockHttpsUrl(
base::StringPrintf("doh_test_%zu", i)) +
"{?dns}");
config_.dns_over_https_servers.push_back(
DnsOverHttpsServerConfig(server_template, use_post));
}
ConfigureFactory();
if (make_available) {
for (size_t server_index = 0; server_index < num_doh_servers;
++server_index) {
resolve_context_->RecordServerSuccess(
server_index, true /* is_doh_server */, session_.get());
}
}
}
// Called after fully configuring |config|.
void ConfigureFactory() {
socket_factory_.reset(new TestSocketFactory());
session_ = new DnsSession(
config_,
std::make_unique<DnsSocketAllocator>(
socket_factory_.get(), config_.nameservers, nullptr /* net_log */),
base::BindRepeating(&DnsTransactionTestBase::GetNextId,
base::Unretained(this)),
nullptr /* NetLog */);
resolve_context_->InvalidateCachesAndPerSessionData(
session_.get(), false /* network_change */);
transaction_factory_ = DnsTransactionFactory::CreateFactory(session_.get());
}
void AddSocketData(std::unique_ptr<DnsSocketData> data,
bool enqueue_transaction_id = true) {
CHECK(socket_factory_.get());
if (enqueue_transaction_id)
transaction_ids_.push_back(data->query_id());
socket_factory_->AddSocketDataProvider(data->GetProvider());
socket_data_.push_back(std::move(data));
}
// Add expected query for |dotted_name| and |qtype| with |id| and response
// taken verbatim from |data| of |data_length| bytes. The transaction id in
// |data| should equal |id|, unless testing mismatched response.
void AddQueryAndResponse(uint16_t id,
const char* dotted_name,
uint16_t qtype,
const uint8_t* response_data,
size_t response_length,
IoMode mode,
Transport transport,
const OptRecordRdata* opt_rdata = nullptr,
DnsQuery::PaddingStrategy padding_strategy =
DnsQuery::PaddingStrategy::NONE,
bool enqueue_transaction_id = true) {
CHECK(socket_factory_.get());
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
id, dotted_name, qtype, mode, transport, opt_rdata, padding_strategy));
data->AddResponseData(response_data, response_length, mode);
AddSocketData(std::move(data), enqueue_transaction_id);
}
void AddQueryAndErrorResponse(uint16_t id,
const char* dotted_name,
uint16_t qtype,
int error,
IoMode mode,
Transport transport,
const OptRecordRdata* opt_rdata = nullptr,
DnsQuery::PaddingStrategy padding_strategy =
DnsQuery::PaddingStrategy::NONE,
bool enqueue_transaction_id = true) {
CHECK(socket_factory_.get());
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
id, dotted_name, qtype, mode, transport, opt_rdata, padding_strategy));
data->AddReadError(error, mode);
AddSocketData(std::move(data), enqueue_transaction_id);
}
void AddAsyncQueryAndResponse(uint16_t id,
const char* dotted_name,
uint16_t qtype,
const uint8_t* data,
size_t data_length,
const OptRecordRdata* opt_rdata = nullptr) {
AddQueryAndResponse(id, dotted_name, qtype, data, data_length, ASYNC,
Transport::UDP, opt_rdata);
}
void AddSyncQueryAndResponse(uint16_t id,
const char* dotted_name,
uint16_t qtype,
const uint8_t* data,
size_t data_length,
const OptRecordRdata* opt_rdata = nullptr) {
AddQueryAndResponse(id, dotted_name, qtype, data, data_length, SYNCHRONOUS,
Transport::UDP, opt_rdata);
}
// Add expected query of |dotted_name| and |qtype| and no response.
void AddHangingQuery(
const char* dotted_name,
uint16_t qtype,
DnsQuery::PaddingStrategy padding_strategy =
DnsQuery::PaddingStrategy::NONE,
uint16_t id = base::RandInt(0, std::numeric_limits<uint16_t>::max()),
bool enqueue_transaction_id = true) {
std::unique_ptr<DnsSocketData> data(
new DnsSocketData(id, dotted_name, qtype, ASYNC, Transport::UDP,
nullptr /* opt_rdata */, padding_strategy));
AddSocketData(std::move(data), enqueue_transaction_id);
}
// Add expected query of |dotted_name| and |qtype| and matching response with
// no answer and RCODE set to |rcode|. The id will be generated randomly.
void AddQueryAndRcode(
const char* dotted_name,
uint16_t qtype,
int rcode,
IoMode mode,
Transport trans,
DnsQuery::PaddingStrategy padding_strategy =
DnsQuery::PaddingStrategy::NONE,
uint16_t id = base::RandInt(0, std::numeric_limits<uint16_t>::max()),
bool enqueue_transaction_id = true) {
CHECK_NE(dns_protocol::kRcodeNOERROR, rcode);
std::unique_ptr<DnsSocketData> data(
new DnsSocketData(id, dotted_name, qtype, mode, trans,
nullptr /* opt_rdata */, padding_strategy));
data->AddRcode(rcode, mode);
AddSocketData(std::move(data), enqueue_transaction_id);
}
void AddAsyncQueryAndRcode(const char* dotted_name,
uint16_t qtype,
int rcode) {
AddQueryAndRcode(dotted_name, qtype, rcode, ASYNC, Transport::UDP);
}
void AddSyncQueryAndRcode(const char* dotted_name,
uint16_t qtype,
int rcode) {
AddQueryAndRcode(dotted_name, qtype, rcode, SYNCHRONOUS, Transport::UDP);
}
// Checks if the sockets were connected in the order matching the indices in
// |servers|.
void CheckServerOrder(const size_t* servers, size_t num_attempts) {
ASSERT_EQ(num_attempts, socket_factory_->remote_endpoints_.size());
auto num_insecure_nameservers = session_->config().nameservers.size();
for (size_t i = 0; i < num_attempts; ++i) {
if (servers[i] < num_insecure_nameservers) {
// Check insecure server match.
EXPECT_EQ(
socket_factory_->remote_endpoints_[i].insecure_nameserver.value(),
session_->config().nameservers[servers[i]]);
} else {
// Check secure server match.
EXPECT_EQ(
socket_factory_->remote_endpoints_[i].secure_nameserver.value(),
session_->config()
.dns_over_https_servers[servers[i] - num_insecure_nameservers]);
}
}
}
std::unique_ptr<URLRequestJob> MaybeInterceptRequest(URLRequest* request) {
// If the path indicates a redirect, skip checking the list of
// configured servers, because it won't be there and we still want
// to handle it.
bool server_found = request->url().path() == "/redirect-destination";
for (auto server : config_.dns_over_https_servers) {
if (server_found)
break;
std::string url_base =
GetURLFromTemplateWithoutParameters(server.server_template);
if (server.use_post && request->method() == "POST") {
if (url_base == request->url().spec()) {
server_found = true;
socket_factory_->remote_endpoints_.emplace_back(server);
}
} else if (!server.use_post && request->method() == "GET") {
std::string prefix = url_base + "?dns=";
auto mispair = std::mismatch(prefix.begin(), prefix.end(),
request->url().spec().begin());
if (mispair.first == prefix.end()) {
server_found = true;
socket_factory_->remote_endpoints_.emplace_back(server);
}
}
}
EXPECT_TRUE(server_found);
EXPECT_TRUE(
request->isolation_info().network_isolation_key().IsTransient());
// All DoH requests for the same ResolveContext should use the same
// IsolationInfo, so network objects like sockets can be reused between
// requests.
if (!expect_multiple_isolation_infos_) {
if (!isolation_info_) {
isolation_info_ =
std::make_unique<IsolationInfo>(request->isolation_info());
} else {
EXPECT_TRUE(
isolation_info_->IsEqualForTesting(request->isolation_info()));
}
}
EXPECT_FALSE(request->allow_credentials());
EXPECT_TRUE(request->disable_secure_dns());
std::string accept;
EXPECT_TRUE(request->extra_request_headers().GetHeader("Accept", &accept));
EXPECT_EQ(accept, "application/dns-message");
std::string language;
EXPECT_TRUE(request->extra_request_headers().GetHeader("Accept-Language",
&language));
EXPECT_EQ(language, "*");
std::string user_agent;
EXPECT_TRUE(
request->extra_request_headers().GetHeader("User-Agent", &user_agent));
EXPECT_EQ(user_agent, "Chrome");
SocketDataProvider* provider = socket_factory_->mock_data().GetNext();
if (doh_job_maker_)
return doh_job_maker_.Run(request, provider);
return std::make_unique<URLRequestMockDohJob>(request, provider,
response_modifier_);
}
class DohJobInterceptor : public URLRequestInterceptor {
public:
explicit DohJobInterceptor(DnsTransactionTestBase* test) : test_(test) {}
~DohJobInterceptor() override {}
// URLRequestInterceptor implementation:
std::unique_ptr<URLRequestJob> MaybeInterceptRequest(
URLRequest* request) const override {
return test_->MaybeInterceptRequest(request);
}
private:
DnsTransactionTestBase* test_;
DISALLOW_COPY_AND_ASSIGN(DohJobInterceptor);
};
void SetResponseModifierCallback(ResponseModifierCallback response_modifier) {
response_modifier_ = response_modifier;
}
void SetDohJobMakerCallback(DohJobMakerCallback doh_job_maker) {
doh_job_maker_ = doh_job_maker;
}
void SetUp() override {
// By default set one server,
ConfigureNumServers(1);
// and no retransmissions,
config_.attempts = 1;
// and an arbitrary fallback period.
config_.fallback_period = kFallbackPeriod;
request_context_ = std::make_unique<TestURLRequestContext>();
resolve_context_ = std::make_unique<ResolveContext>(
request_context_.get(), false /* enable_caching */);
ConfigureFactory();
}
void TearDown() override {
// Check that all socket data was at least written to.
for (size_t i = 0; i < socket_data_.size(); ++i) {
EXPECT_TRUE(socket_data_[i]->GetProvider()->AllWriteDataConsumed()) << i;
}
URLRequestFilter* filter = URLRequestFilter::GetInstance();
filter->ClearHandlers();
}
void set_expect_multiple_isolation_infos(
bool expect_multiple_isolation_infos) {
expect_multiple_isolation_infos_ = expect_multiple_isolation_infos;
}
protected:
int GetNextId(int min, int max) {
EXPECT_FALSE(transaction_ids_.empty());
int id = transaction_ids_.front();
transaction_ids_.pop_front();
EXPECT_GE(id, min);
EXPECT_LE(id, max);
return id;
}
DnsConfig config_;
std::vector<std::unique_ptr<DnsSocketData>> socket_data_;
base::circular_deque<int> transaction_ids_;
std::unique_ptr<TestSocketFactory> socket_factory_;
std::unique_ptr<TestURLRequestContext> request_context_;
std::unique_ptr<ResolveContext> resolve_context_;
scoped_refptr<DnsSession> session_;
std::unique_ptr<DnsTransactionFactory> transaction_factory_;
ResponseModifierCallback response_modifier_;
DohJobMakerCallback doh_job_maker_;
// Whether multiple IsolationInfos should be expected (due to there being
// multiple RequestContexts in use).
bool expect_multiple_isolation_infos_ = false;
// IsolationInfo used by DoH requests. Populated on first DoH request, and
// compared to IsolationInfo used by all subsequent requests, unless
// |expect_multiple_isolation_infos_| is true.
std::unique_ptr<IsolationInfo> isolation_info_;
};
class DnsTransactionTest : public DnsTransactionTestBase,
public WithTaskEnvironment {
public:
DnsTransactionTest() = default;
~DnsTransactionTest() override = default;
};
class DnsTransactionTestWithMockTime : public DnsTransactionTestBase,
public WithTaskEnvironment {
protected:
DnsTransactionTestWithMockTime()
: WithTaskEnvironment(
base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
~DnsTransactionTestWithMockTime() override = default;
};
TEST_F(DnsTransactionTest, Lookup) {
AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
kT0ResponseDatagram,
base::size(kT0ResponseDatagram));
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, LookupWithEDNSOption) {
OptRecordRdata expected_opt_rdata;
const OptRecordRdata::Opt ednsOpt(123, "\xbe\xef");
transaction_factory_->AddEDNSOption(ednsOpt);
expected_opt_rdata.AddOpt(ednsOpt);
AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
kT0ResponseDatagram, base::size(kT0ResponseDatagram),
&expected_opt_rdata);
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, LookupWithMultipleEDNSOptions) {
OptRecordRdata expected_opt_rdata;
for (const auto& ednsOpt : {
// Two options with the same code, to check that both are included.
OptRecordRdata::Opt(1, "\xde\xad"),
OptRecordRdata::Opt(1, "\xbe\xef"),
// Try a different code and different length of data.
OptRecordRdata::Opt(2, "\xff"),
}) {
transaction_factory_->AddEDNSOption(ednsOpt);
expected_opt_rdata.AddOpt(ednsOpt);
}
AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
kT0ResponseDatagram, base::size(kT0ResponseDatagram),
&expected_opt_rdata);
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
// Concurrent lookup tests assume that DnsTransaction::Start immediately
// consumes a socket from ClientSocketFactory.
TEST_F(DnsTransactionTest, ConcurrentLookup) {
AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
kT0ResponseDatagram,
base::size(kT0ResponseDatagram));
AddAsyncQueryAndResponse(1 /* id */, kT1HostName, kT1Qtype,
kT1ResponseDatagram,
base::size(kT1ResponseDatagram));
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
TransactionHelper helper1(kT1RecordCount);
helper1.StartTransaction(transaction_factory_.get(), kT1HostName, kT1Qtype,
false /* secure */, resolve_context_.get());
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(helper0.has_completed());
EXPECT_TRUE(helper1.has_completed());
}
TEST_F(DnsTransactionTest, CancelLookup) {
AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
kT0ResponseDatagram,
base::size(kT0ResponseDatagram));
AddAsyncQueryAndResponse(1 /* id */, kT1HostName, kT1Qtype,
kT1ResponseDatagram,
base::size(kT1ResponseDatagram));
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
TransactionHelper helper1(kT1RecordCount);
helper1.StartTransaction(transaction_factory_.get(), kT1HostName, kT1Qtype,
false /* secure */, resolve_context_.get());
helper0.Cancel();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(helper0.has_completed());
EXPECT_TRUE(helper1.has_completed());
}
TEST_F(DnsTransactionTest, DestroyFactory) {
AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
kT0ResponseDatagram,
base::size(kT0ResponseDatagram));
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
// Destroying the client does not affect running requests.
transaction_factory_.reset(nullptr);
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, CancelFromCallback) {
AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
kT0ResponseDatagram,
base::size(kT0ResponseDatagram));
TransactionHelper helper0(kT0RecordCount);
helper0.set_cancel_in_callback();
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, MismatchedResponseSync) {
config_.attempts = 2;
ConfigureFactory();
// First attempt receives mismatched response synchronously.
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::UDP));
data->AddResponseData(kT1ResponseDatagram, base::size(kT1ResponseDatagram),
SYNCHRONOUS);
AddSocketData(std::move(data));
// Second attempt receives valid response synchronously.
std::unique_ptr<DnsSocketData> data1(new DnsSocketData(
0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::UDP));
data1->AddResponseData(kT0ResponseDatagram, base::size(kT0ResponseDatagram),
SYNCHRONOUS);
AddSocketData(std::move(data1));
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, MismatchedResponseAsync) {
config_.attempts = 2;
ConfigureFactory();
// First attempt receives mismatched response asynchronously.
std::unique_ptr<DnsSocketData> data0(new DnsSocketData(
0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::UDP));
data0->AddResponseData(kT1ResponseDatagram, base::size(kT1ResponseDatagram),
ASYNC);
AddSocketData(std::move(data0));
// Second attempt receives valid response asynchronously.
std::unique_ptr<DnsSocketData> data1(new DnsSocketData(
0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::UDP));
data1->AddResponseData(kT0ResponseDatagram, base::size(kT0ResponseDatagram),
ASYNC);
AddSocketData(std::move(data1));
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, MismatchedResponseFail) {
ConfigureFactory();
// Attempt receives mismatched response and fails because only one attempt is
// allowed.
AddAsyncQueryAndResponse(1 /* id */, kT0HostName, kT0Qtype,
kT0ResponseDatagram,
base::size(kT0ResponseDatagram));
TransactionHelper helper0(ERR_DNS_MALFORMED_RESPONSE);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, MismatchedResponseNxdomain) {
config_.attempts = 2;
ConfigureFactory();
// First attempt receives mismatched response followed by valid NXDOMAIN
// response.
// Second attempt receives valid NXDOMAIN response.
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::UDP));
data->AddResponseData(kT1ResponseDatagram, base::size(kT1ResponseDatagram),
SYNCHRONOUS);
data->AddRcode(dns_protocol::kRcodeNXDOMAIN, ASYNC);
AddSocketData(std::move(data));
AddSyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNXDOMAIN);
TransactionHelper helper0(ERR_NAME_NOT_RESOLVED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, ServerFail) {
AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL);
TransactionHelper helper0(ERR_DNS_SERVER_FAILED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
ASSERT_NE(helper0.response(), nullptr);
EXPECT_EQ(helper0.response()->rcode(), dns_protocol::kRcodeSERVFAIL);
}
TEST_F(DnsTransactionTest, NoDomain) {
AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNXDOMAIN);
TransactionHelper helper0(ERR_NAME_NOT_RESOLVED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTestWithMockTime, Timeout_FastTimeout) {
config_.attempts = 3;
ConfigureFactory();
AddHangingQuery(kT0HostName, kT0Qtype);
AddHangingQuery(kT0HostName, kT0Qtype);
AddHangingQuery(kT0HostName, kT0Qtype);
TransactionHelper helper0(ERR_DNS_TIMED_OUT);
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();
EXPECT_FALSE(helper0.has_completed());
FastForwardBy(
resolve_context_->NextClassicFallbackPeriod(0, 0, session_.get()));
EXPECT_FALSE(helper0.has_completed());
FastForwardBy(
resolve_context_->NextClassicFallbackPeriod(0, 1, session_.get()));
EXPECT_FALSE(helper0.has_completed());
FastForwardBy(
resolve_context_->NextClassicFallbackPeriod(0, 2, session_.get()));
EXPECT_TRUE(helper0.has_completed());
}
TEST_F(DnsTransactionTestWithMockTime, ServerFallbackAndRotate) {
// Test that we fallback on both server failure and fallback period
// expiration.
config_.attempts = 2;
// The next request should start from the next server.
config_.rotate = true;
ConfigureNumServers(3);
ConfigureFactory();
// Responses for first request.
AddHangingQuery(kT0HostName, kT0Qtype);
AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL);
AddHangingQuery(kT0HostName, kT0Qtype);
AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL);
AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNXDOMAIN);
// Responses for second request.
AddAsyncQueryAndRcode(kT1HostName, kT1Qtype, dns_protocol::kRcodeSERVFAIL);
AddAsyncQueryAndRcode(kT1HostName, kT1Qtype, dns_protocol::kRcodeSERVFAIL);
AddAsyncQueryAndRcode(kT1HostName, kT1Qtype, dns_protocol::kRcodeNXDOMAIN);
TransactionHelper helper0(ERR_NAME_NOT_RESOLVED);
TransactionHelper helper1(ERR_NAME_NOT_RESOLVED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(helper0.has_completed());
FastForwardUntilNoTasksRemain();
EXPECT_TRUE(helper0.has_completed());
helper1.StartTransaction(transaction_factory_.get(), kT1HostName, kT1Qtype,
false /* secure */, resolve_context_.get());
helper1.RunUntilComplete();
size_t kOrder[] = {
// The first transaction.
0,
1,
2,
0,
1,
// The second transaction starts from the next server, and 0 is skipped
// because it already has 2 consecutive failures.
1,
2,
1,
};
CheckServerOrder(kOrder, base::size(kOrder));
}
TEST_F(DnsTransactionTest, SuffixSearchAboveNdots) {
config_.ndots = 2;
config_.search.push_back("a");
config_.search.push_back("b");
config_.search.push_back("c");
config_.rotate = true;
ConfigureNumServers(2);
ConfigureFactory();
AddAsyncQueryAndRcode("x.y.z", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
AddAsyncQueryAndRcode("x.y.z.a", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
AddAsyncQueryAndRcode("x.y.z.b", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
AddAsyncQueryAndRcode("x.y.z.c", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
TransactionHelper helper0(ERR_NAME_NOT_RESOLVED);
helper0.StartTransaction(transaction_factory_.get(), "x.y.z",
dns_protocol::kTypeA, false /* secure */,
resolve_context_.get());
helper0.RunUntilComplete();
// Also check if suffix search causes server rotation.
size_t kOrder0[] = {0, 1, 0, 1};
CheckServerOrder(kOrder0, base::size(kOrder0));
}
TEST_F(DnsTransactionTest, SuffixSearchBelowNdots) {
config_.ndots = 2;
config_.search.push_back("a");
config_.search.push_back("b");
config_.search.push_back("c");
ConfigureFactory();
// Responses for first transaction.
AddAsyncQueryAndRcode("x.y.a", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
AddAsyncQueryAndRcode("x.y.b", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
AddAsyncQueryAndRcode("x.y.c", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
AddAsyncQueryAndRcode("x.y", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
// Responses for second transaction.
AddAsyncQueryAndRcode("x.a", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
AddAsyncQueryAndRcode("x.b", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
AddAsyncQueryAndRcode("x.c", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
// Responses for third transaction.
AddAsyncQueryAndRcode("x", dns_protocol::kTypeAAAA,
dns_protocol::kRcodeNXDOMAIN);
TransactionHelper helper0(ERR_NAME_NOT_RESOLVED);
helper0.StartTransaction(transaction_factory_.get(), "x.y",
dns_protocol::kTypeA, false /* secure */,
resolve_context_.get());
helper0.RunUntilComplete();
// A single-label name.
TransactionHelper helper1(ERR_NAME_NOT_RESOLVED);
helper1.StartTransaction(transaction_factory_.get(), "x",
dns_protocol::kTypeA, false /* secure */,
resolve_context_.get());
helper1.RunUntilComplete();
// A fully-qualified name.
TransactionHelper helper2(ERR_NAME_NOT_RESOLVED);
helper2.StartTransaction(transaction_factory_.get(), "x.",
dns_protocol::kTypeAAAA, false /* secure */,
resolve_context_.get());
helper2.RunUntilComplete();
}
TEST_F(DnsTransactionTest, EmptySuffixSearch) {
// Responses for first transaction.
AddAsyncQueryAndRcode("x", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
// A fully-qualified name.
TransactionHelper helper0(ERR_NAME_NOT_RESOLVED);
helper0.StartTransaction(transaction_factory_.get(), "x.",
dns_protocol::kTypeA, false /* secure */,
resolve_context_.get());
helper0.RunUntilComplete();
// A single label name is not even attempted.
TransactionHelper helper1(ERR_DNS_SEARCH_EMPTY);
helper1.StartTransaction(transaction_factory_.get(), "singlelabel",
dns_protocol::kTypeA, false /* secure */,
resolve_context_.get());
helper1.RunUntilComplete();
}
TEST_F(DnsTransactionTest, DontAppendToMultiLabelName) {
config_.search.push_back("a");
config_.search.push_back("b");
config_.search.push_back("c");
config_.append_to_multi_label_name = false;
ConfigureFactory();
// Responses for first transaction.
AddAsyncQueryAndRcode("x.y.z", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
// Responses for second transaction.
AddAsyncQueryAndRcode("x.y", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
// Responses for third transaction.
AddAsyncQueryAndRcode("x.a", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
AddAsyncQueryAndRcode("x.b", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
AddAsyncQueryAndRcode("x.c", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
TransactionHelper helper0(ERR_NAME_NOT_RESOLVED);
helper0.StartTransaction(transaction_factory_.get(), "x.y.z",
dns_protocol::kTypeA, false /* secure */,
resolve_context_.get());
helper0.RunUntilComplete();
TransactionHelper helper1(ERR_NAME_NOT_RESOLVED);
helper1.StartTransaction(transaction_factory_.get(), "x.y",
dns_protocol::kTypeA, false /* secure */,
resolve_context_.get());
helper1.RunUntilComplete();
TransactionHelper helper2(ERR_NAME_NOT_RESOLVED);
helper2.StartTransaction(transaction_factory_.get(), "x",
dns_protocol::kTypeA, false /* secure */,
resolve_context_.get());
helper2.RunUntilComplete();
}
const uint8_t kResponseNoData[] = {
0x00, 0x00, 0x81, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
// Question
0x01, 'x', 0x01, 'y', 0x01, 'z', 0x01, 'b', 0x00, 0x00, 0x01, 0x00, 0x01,
// Authority section, SOA record, TTL 0x3E6
0x01, 'z', 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x03, 0xE6,
// Minimal RDATA, 18 bytes
0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
TEST_F(DnsTransactionTest, SuffixSearchStop) {
config_.ndots = 2;
config_.search.push_back("a");
config_.search.push_back("b");
config_.search.push_back("c");
ConfigureFactory();
AddAsyncQueryAndRcode("x.y.z", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
AddAsyncQueryAndRcode("x.y.z.a", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
AddAsyncQueryAndResponse(0 /* id */, "x.y.z.b", dns_protocol::kTypeA,
kResponseNoData, base::size(kResponseNoData));
TransactionHelper helper0(0 /* answers */);
helper0.StartTransaction(transaction_factory_.get(), "x.y.z",
dns_protocol::kTypeA, false /* secure */,
resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, SyncFirstQuery) {
config_.search.push_back("lab.ccs.neu.edu");
config_.search.push_back("ccs.neu.edu");
ConfigureFactory();
AddSyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
kT0ResponseDatagram, base::size(kT0ResponseDatagram));
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, SyncFirstQueryWithSearch) {
config_.search.push_back("lab.ccs.neu.edu");
config_.search.push_back("ccs.neu.edu");
ConfigureFactory();
AddSyncQueryAndRcode("www.lab.ccs.neu.edu", kT2Qtype,
dns_protocol::kRcodeNXDOMAIN);
// "www.ccs.neu.edu"
AddAsyncQueryAndResponse(2 /* id */, kT2HostName, kT2Qtype,
kT2ResponseDatagram,
base::size(kT2ResponseDatagram));
TransactionHelper helper0(kT2RecordCount);
helper0.StartTransaction(transaction_factory_.get(), "www", kT2Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, SyncSearchQuery) {
config_.search.push_back("lab.ccs.neu.edu");
config_.search.push_back("ccs.neu.edu");
ConfigureFactory();
AddAsyncQueryAndRcode("www.lab.ccs.neu.edu", dns_protocol::kTypeA,
dns_protocol::kRcodeNXDOMAIN);
AddSyncQueryAndResponse(2 /* id */, kT2HostName, kT2Qtype,
kT2ResponseDatagram, base::size(kT2ResponseDatagram));
TransactionHelper helper0(kT2RecordCount);
helper0.StartTransaction(transaction_factory_.get(), "www", kT2Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, ConnectFailure) {
// Prep socket factory for a single socket with connection failure.
MockConnect connect_data;
connect_data.result = ERR_FAILED;
StaticSocketDataProvider data_provider;
data_provider.set_connect_data(connect_data);
socket_factory_->AddSocketDataProvider(&data_provider);
transaction_ids_.push_back(0); // Needed to make a DnsUDPAttempt.
TransactionHelper helper0(ERR_CONNECTION_REFUSED);
helper0.StartTransaction(transaction_factory_.get(), "www.chromium.org",
dns_protocol::kTypeA, false /* secure */,
resolve_context_.get());
helper0.RunUntilComplete();
EXPECT_FALSE(helper0.response());
EXPECT_FALSE(session_->udp_tracker()->low_entropy());
}
TEST_F(DnsTransactionTest, ConnectFailure_SocketLimitReached) {
// Prep socket factory for a single socket with connection failure.
MockConnect connect_data;
connect_data.result = ERR_INSUFFICIENT_RESOURCES;
StaticSocketDataProvider data_provider;
data_provider.set_connect_data(connect_data);
socket_factory_->AddSocketDataProvider(&data_provider);
transaction_ids_.push_back(0); // Needed to make a DnsUDPAttempt.
TransactionHelper helper0(ERR_CONNECTION_REFUSED);
helper0.StartTransaction(transaction_factory_.get(), "www.chromium.org",
dns_protocol::kTypeA, false /* secure */,
resolve_context_.get());
helper0.RunUntilComplete();
EXPECT_FALSE(helper0.response());
EXPECT_TRUE(session_->udp_tracker()->low_entropy());
}
TEST_F(DnsTransactionTest, ConnectFailureFollowedBySuccess) {
// Retry after server failure.
config_.attempts = 2;
ConfigureFactory();
// First server connection attempt fails.
transaction_ids_.push_back(0); // Needed to make a DnsUDPAttempt.
socket_factory_->fail_next_socket_ = true;
// Second DNS query succeeds.
AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
kT0ResponseDatagram,
base::size(kT0ResponseDatagram));
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, HttpsGetLookup) {
ConfigureDohServers(false /* use_post */);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, HttpsGetFailure) {
ConfigureDohServers(false /* use_post */);
AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL,
SYNCHRONOUS, Transport::HTTPS,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, 0 /* id */,
false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_DNS_SERVER_FAILED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
ASSERT_NE(helper0.response(), nullptr);
EXPECT_EQ(helper0.response()->rcode(), dns_protocol::kRcodeSERVFAIL);
}
TEST_F(DnsTransactionTest, HttpsGetMalformed) {
ConfigureDohServers(false /* use_post */);
// Use T1 response, which is malformed for a T0 request.
AddQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT1ResponseDatagram,
base::size(kT1ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_DNS_MALFORMED_RESPONSE);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, HttpsPostLookup) {
ConfigureDohServers(true /* use_post */);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, HttpsPostFailure) {
ConfigureDohServers(true /* use_post */);
AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL,
SYNCHRONOUS, Transport::HTTPS,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, 0 /* id */,
false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_DNS_SERVER_FAILED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
ASSERT_NE(helper0.response(), nullptr);
EXPECT_EQ(helper0.response()->rcode(), dns_protocol::kRcodeSERVFAIL);
}
TEST_F(DnsTransactionTest, HttpsPostMalformed) {
ConfigureDohServers(true /* use_post */);
// Use T1 response, which is malformed for a T0 request.
AddQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT1ResponseDatagram,
base::size(kT1ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_DNS_MALFORMED_RESPONSE);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, HttpsPostLookupAsync) {
ConfigureDohServers(true /* use_post */);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
std::unique_ptr<URLRequestJob> DohJobMakerCallbackFailLookup(
URLRequest* request,
SocketDataProvider* data) {
URLRequestMockDohJob::MatchQueryData(request, data);
return std::make_unique<URLRequestFailedJob>(
request, URLRequestFailedJob::START, ERR_NAME_NOT_RESOLVED);
}
TEST_F(DnsTransactionTest, HttpsPostLookupFailDohServerLookup) {
ConfigureDohServers(true /* use_post */);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_DNS_SECURE_RESOLVER_HOSTNAME_RESOLUTION_FAILED);
SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailLookup));
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
std::unique_ptr<URLRequestJob> DohJobMakerCallbackFailStart(
URLRequest* request,
SocketDataProvider* data) {
URLRequestMockDohJob::MatchQueryData(request, data);
return std::make_unique<URLRequestFailedJob>(
request, URLRequestFailedJob::START, ERR_FAILED);
}
TEST_F(DnsTransactionTest, HttpsPostLookupFailStart) {
ConfigureDohServers(true /* use_post */);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_FAILED);
SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailStart));
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
std::unique_ptr<URLRequestJob> DohJobMakerCallbackFailSync(
URLRequest* request,
SocketDataProvider* data) {
URLRequestMockDohJob::MatchQueryData(request, data);
return std::make_unique<URLRequestFailedJob>(
request, URLRequestFailedJob::READ_SYNC, ERR_FAILED);
}
TEST_F(DnsTransactionTest, HttpsPostLookupFailSync) {
ConfigureDohServers(true /* use_post */);
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128));
data->AddResponseWithLength(std::make_unique<DnsResponse>(), SYNCHRONOUS, 0);
AddSocketData(std::move(data), false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_DNS_MALFORMED_RESPONSE);
SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailSync));
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
std::unique_ptr<URLRequestJob> DohJobMakerCallbackFailAsync(
URLRequest* request,
SocketDataProvider* data) {
URLRequestMockDohJob::MatchQueryData(request, data);
return std::make_unique<URLRequestFailedJob>(
request, URLRequestFailedJob::READ_ASYNC, ERR_FAILED);
}
TEST_F(DnsTransactionTest, HttpsPostLookupFailAsync) {
ConfigureDohServers(true /* use_post */);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_DNS_MALFORMED_RESPONSE);
SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailAsync));
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, HttpsPostLookup2Sync) {
ConfigureDohServers(true /* use_post */);
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128));
data->AddResponseData(kT0ResponseDatagram, 20, SYNCHRONOUS);
data->AddResponseData(kT0ResponseDatagram + 20,
base::size(kT0ResponseDatagram) - 20, SYNCHRONOUS);
AddSocketData(std::move(data), false /* enqueue_transaction_id */);
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, HttpsPostLookup2Async) {
ConfigureDohServers(true /* use_post */);
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128));
data->AddResponseData(kT0ResponseDatagram, 20, ASYNC);
data->AddResponseData(kT0ResponseDatagram + 20,
base::size(kT0ResponseDatagram) - 20, ASYNC);
AddSocketData(std::move(data), false /* enqueue_transaction_id */);
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, HttpsPostLookupAsyncWithAsyncZeroRead) {
ConfigureDohServers(true /* use_post */);
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128));
data->AddResponseData(kT0ResponseDatagram, base::size(kT0ResponseDatagram),
ASYNC);
data->AddResponseData(kT0ResponseDatagram, 0, ASYNC);
AddSocketData(std::move(data), false /* enqueue_transaction_id */);
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, HttpsPostLookupSyncWithAsyncZeroRead) {
ConfigureDohServers(true /* use_post */);
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128));
data->AddResponseData(kT0ResponseDatagram, base::size(kT0ResponseDatagram),
SYNCHRONOUS);
data->AddResponseData(kT0ResponseDatagram, 0, ASYNC);
AddSocketData(std::move(data), false /* enqueue_transaction_id */);
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, HttpsPostLookupAsyncThenSync) {
ConfigureDohServers(true /* use_post */);
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128));
data->AddResponseData(kT0ResponseDatagram, 20, ASYNC);
data->AddResponseData(kT0ResponseDatagram + 20,
base::size(kT0ResponseDatagram) - 20, SYNCHRONOUS);
AddSocketData(std::move(data), false /* enqueue_transaction_id */);
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, HttpsPostLookupAsyncThenSyncError) {
ConfigureDohServers(true /* use_post */);
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128));
data->AddResponseData(kT0ResponseDatagram, 20, ASYNC);
data->AddReadError(ERR_FAILED, SYNCHRONOUS);
AddSocketData(std::move(data), false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_FAILED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, HttpsPostLookupAsyncThenAsyncError) {
ConfigureDohServers(true /* use_post */);
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128));
data->AddResponseData(kT0ResponseDatagram, 20, ASYNC);
data->AddReadError(ERR_FAILED, ASYNC);
AddSocketData(std::move(data), false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_FAILED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, HttpsPostLookupSyncThenAsyncError) {
ConfigureDohServers(true /* use_post */);
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128));
data->AddResponseData(kT0ResponseDatagram, 20, SYNCHRONOUS);
data->AddReadError(ERR_FAILED, ASYNC);
AddSocketData(std::move(data), false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_FAILED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, HttpsPostLookupSyncThenSyncError) {
ConfigureDohServers(true /* use_post */);
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128));
data->AddResponseData(kT0ResponseDatagram, 20, SYNCHRONOUS);
data->AddReadError(ERR_FAILED, SYNCHRONOUS);
AddSocketData(std::move(data), false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_FAILED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, HttpsNotAvailable) {
ConfigureDohServers(true /* use_post */, 1 /* num_doh_servers */,
false /* make_available */);
ASSERT_FALSE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
TransactionHelper helper0(ERR_BLOCKED_BY_CLIENT);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, HttpsMarkHttpsBad) {
config_.attempts = 1;
ConfigureDohServers(true /* use_post */, 3);
AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED,
SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED,
SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndErrorResponse(0 /* id */, kT0HostName, kT0Qtype,
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndErrorResponse(0 /* id */, kT0HostName, kT0Qtype,
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(kT0RecordCount);
TransactionHelper helper1(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
// UDP server 0 is our only UDP server, so it will be good. HTTPS
// servers 0 and 1 failed and will be marked bad. HTTPS server 2 succeeded
// so it will be good.
// The expected order of the HTTPS servers is therefore 2, 0, then 1.
{
std::unique_ptr<DnsServerIterator> classic_itr =
resolve_context_->GetClassicDnsIterator(session_->config(),
session_.get());
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
EXPECT_TRUE(classic_itr->AttemptAvailable());
EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 0u);
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 2u);
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 1u);
}
size_t kOrder0[] = {1, 2, 3};
CheckServerOrder(kOrder0, base::size(kOrder0));
helper1.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper1.RunUntilComplete();
// UDP server 0 is still our only UDP server, so it will be good by
// definition. HTTPS server 2 started out as good, so it was tried first and
// failed. HTTPS server 0 then had the oldest failure so it would be the next
// good server and then it failed so it's marked bad. Next attempt was HTTPS
// server 1, which succeeded so it's good. The expected order of the HTTPS
// servers is therefore 1, 2, then 0.
{
std::unique_ptr<DnsServerIterator> classic_itr =
resolve_context_->GetClassicDnsIterator(session_->config(),
session_.get());
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
EXPECT_EQ(classic_itr->GetNextAttemptIndex(), 0u);
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 1u);
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 2u);
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
}
size_t kOrder1[] = {
1, 2, 3, /* transaction0 */
3, 1, 2 /* transaction1 */
};
CheckServerOrder(kOrder1, base::size(kOrder1));
}
TEST_F(DnsTransactionTest, HttpsPostFailThenHTTPFallback) {
ConfigureDohServers(true /* use_post */, 2);
AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL, ASYNC,
Transport::HTTPS,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, 0 /* id */,
false /* enqueue_transaction_id */);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
size_t kOrder0[] = {1, 2};
CheckServerOrder(kOrder0, base::size(kOrder0));
}
TEST_F(DnsTransactionTest, HttpsPostFailTwice) {
config_.attempts = 3;
ConfigureDohServers(true /* use_post */, 2);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_FAILED);
SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailStart));
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
size_t kOrder0[] = {1, 2};
CheckServerOrder(kOrder0, base::size(kOrder0));
}
TEST_F(DnsTransactionTest, HttpsNotAvailableThenHttpFallback) {
ConfigureDohServers(true /* use_post */, 2 /* num_doh_servers */,
false /* make_available */);
// Make just server 1 available.
resolve_context_->RecordServerSuccess(
1u /* server_index */, true /* is_doh_server*/, session_.get());
{
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 1u);
EXPECT_FALSE(doh_itr->AttemptAvailable());
}
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
size_t kOrder0[] = {2};
CheckServerOrder(kOrder0, base::size(kOrder0));
{
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 1u);
EXPECT_FALSE(doh_itr->AttemptAvailable());
}
}
// Fail first DoH server, then no fallbacks marked available in AUTOMATIC mode.
TEST_F(DnsTransactionTest, HttpsFailureThenNotAvailable_Automatic) {
config_.secure_dns_mode = SecureDnsMode::kAutomatic;
ConfigureDohServers(true /* use_post */, 3 /* num_doh_servers */,
false /* make_available */);
// Make just server 0 available.
resolve_context_->RecordServerSuccess(
0u /* server_index */, true /* is_doh_server*/, session_.get());
{
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
EXPECT_FALSE(doh_itr->AttemptAvailable());
}
AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED,
SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_CONNECTION_REFUSED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
// Expect fallback not attempted because other servers not available in
// AUTOMATIC mode until they have recorded a success.
size_t kOrder0[] = {1};
CheckServerOrder(kOrder0, base::size(kOrder0));
{
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
EXPECT_FALSE(doh_itr->AttemptAvailable());
}
}
// Test a secure transaction failure in SECURE mode when other DoH servers are
// only available for fallback because of
TEST_F(DnsTransactionTest, HttpsFailureThenNotAvailable_Secure) {
config_.secure_dns_mode = SecureDnsMode::kSecure;
ConfigureDohServers(true /* use_post */, 3 /* num_doh_servers */,
false /* make_available */);
// Make just server 0 available.
resolve_context_->RecordServerSuccess(
0u /* server_index */, true /* is_doh_server*/, session_.get());
{
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kSecure, session_.get());
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 1u);
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 2u);
}
AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED,
SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED,
SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED,
SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_CONNECTION_REFUSED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
// Expect fallback to attempt all servers because SECURE mode does not require
// server availability.
size_t kOrder0[] = {1, 2, 3};
CheckServerOrder(kOrder0, base::size(kOrder0));
// Expect server 0 to be preferred due to least recent failure.
{
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kSecure, session_.get());
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
}
}
TEST_F(DnsTransactionTest, MaxHttpsFailures_NonConsecutive) {
config_.attempts = 1;
ConfigureDohServers(false /* use_post */);
{
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
}
for (size_t i = 0; i < ResolveContext::kAutomaticModeFailureLimit - 1; i++) {
AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED,
SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper failure(ERR_CONNECTION_REFUSED);
failure.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
failure.RunUntilComplete();
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
}
// A success should reset the failure counter for DoH.
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper success(kT0RecordCount);
success.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
success.RunUntilComplete();
{
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
}
// One more failure should not pass the threshold because failures were reset.
AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED,
SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper last_failure(ERR_CONNECTION_REFUSED);
last_failure.StartTransaction(transaction_factory_.get(), kT0HostName,
kT0Qtype, true /* secure */,
resolve_context_.get());
last_failure.RunUntilComplete();
{
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
}
}
TEST_F(DnsTransactionTest, MaxHttpsFailures_Consecutive) {
config_.attempts = 1;
ConfigureDohServers(false /* use_post */);
{
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
}
for (size_t i = 0; i < ResolveContext::kAutomaticModeFailureLimit - 1; i++) {
AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED,
SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper failure(ERR_CONNECTION_REFUSED);
failure.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
failure.RunUntilComplete();
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
}
// One more failure should pass the threshold.
AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED,
SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper last_failure(ERR_CONNECTION_REFUSED);
last_failure.StartTransaction(transaction_factory_.get(), kT0HostName,
kT0Qtype, true /* secure */,
resolve_context_.get());
last_failure.RunUntilComplete();
{
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
EXPECT_FALSE(doh_itr->AttemptAvailable());
}
}
// Test that a secure transaction started before a DoH server becomes
// unavailable can complete and make the server available again.
TEST_F(DnsTransactionTest, SuccessfulTransactionStartedBeforeUnavailable) {
ConfigureDohServers(false /* use_post */);
{
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
}
// Create a socket data to first return ERR_IO_PENDING. This will pause the
// response and not return the second response until
// SequencedSocketData::Resume() is called.
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0, 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 delayed_success(kT0RecordCount);
delayed_success.StartTransaction(transaction_factory_.get(), kT0HostName,
kT0Qtype, true /* secure */,
resolve_context_.get());
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(delayed_success.has_completed());
// Trigger DoH server unavailability with a bunch of failures.
for (size_t i = 0; i < ResolveContext::kAutomaticModeFailureLimit; i++) {
AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED,
SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper failure(ERR_CONNECTION_REFUSED);
failure.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
failure.RunUntilComplete();
}
EXPECT_FALSE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
// Resume first query.
ASSERT_FALSE(delayed_success.has_completed());
sequenced_socket_data->Resume();
delayed_success.RunUntilComplete();
// Expect DoH server is available again.
EXPECT_TRUE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
}
void MakeResponseWithCookie(URLRequest* request, HttpResponseInfo* info) {
info->headers->AddHeader("Set-Cookie", "test-cookie=you-fail");
}
class CookieCallback {
public:
CookieCallback()
: result_(false), loop_to_quit_(std::make_unique<base::RunLoop>()) {}
void SetCookieCallback(CookieAccessResult result) {
result_ = result.status.IsInclude();
loop_to_quit_->Quit();
}
void GetCookieListCallback(
const net::CookieAccessResultList& list,
const net::CookieAccessResultList& excluded_cookies) {
list_ = cookie_util::StripAccessResults(list);
loop_to_quit_->Quit();
}
void Reset() { loop_to_quit_.reset(new base::RunLoop()); }
void WaitUntilDone() { loop_to_quit_->Run(); }
size_t cookie_list_size() { return list_.size(); }
private:
net::CookieList list_;
bool result_;
std::unique_ptr<base::RunLoop> loop_to_quit_;
DISALLOW_COPY_AND_ASSIGN(CookieCallback);
};
TEST_F(DnsTransactionTest, HttpsPostTestNoCookies) {
ConfigureDohServers(true /* use_post */);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(kT0RecordCount);
TransactionHelper helper1(kT0RecordCount);
SetResponseModifierCallback(base::BindRepeating(MakeResponseWithCookie));
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
CookieCallback callback;
request_context_->cookie_store()->GetCookieListWithOptionsAsync(
GURL(GetURLFromTemplateWithoutParameters(
config_.dns_over_https_servers[0].server_template)),
CookieOptions::MakeAllInclusive(),
base::BindOnce(&CookieCallback::GetCookieListCallback,
base::Unretained(&callback)));
callback.WaitUntilDone();
EXPECT_EQ(0u, callback.cookie_list_size());
callback.Reset();
GURL cookie_url(GetURLFromTemplateWithoutParameters(
config_.dns_over_https_servers[0].server_template));
auto cookie = CanonicalCookie::Create(
cookie_url, "test-cookie=you-still-fail", base::Time::Now(),
base::nullopt /* server_time */);
request_context_->cookie_store()->SetCanonicalCookieAsync(
std::move(cookie), cookie_url, CookieOptions(),
base::BindOnce(&CookieCallback::SetCookieCallback,
base::Unretained(&callback)));
helper1.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper1.RunUntilComplete();
}
void MakeResponseWithoutLength(URLRequest* request, HttpResponseInfo* info) {
info->headers->RemoveHeader("Content-Length");
}
TEST_F(DnsTransactionTest, HttpsPostNoContentLength) {
ConfigureDohServers(true /* use_post */);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(kT0RecordCount);
SetResponseModifierCallback(base::BindRepeating(MakeResponseWithoutLength));
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
void MakeResponseWithBadRequestResponse(URLRequest* request,
HttpResponseInfo* info) {
info->headers->ReplaceStatusLine("HTTP/1.1 400 Bad Request");
}
TEST_F(DnsTransactionTest, HttpsPostWithBadRequestResponse) {
ConfigureDohServers(true /* use_post */);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_DNS_MALFORMED_RESPONSE);
SetResponseModifierCallback(
base::BindRepeating(MakeResponseWithBadRequestResponse));
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
void MakeResponseWrongType(URLRequest* request, HttpResponseInfo* info) {
info->headers->RemoveHeader("Content-Type");
info->headers->AddHeader("Content-Type", "text/html");
}
TEST_F(DnsTransactionTest, HttpsPostWithWrongType) {
ConfigureDohServers(true /* use_post */);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_DNS_MALFORMED_RESPONSE);
SetResponseModifierCallback(base::BindRepeating(MakeResponseWrongType));
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
void MakeResponseRedirect(URLRequest* request, HttpResponseInfo* info) {
if (request->url_chain().size() < 2) {
info->headers->ReplaceStatusLine("HTTP/1.1 302 Found");
info->headers->AddHeader("Location",
"/redirect-destination?" + request->url().query());
}
}
TEST_F(DnsTransactionTest, HttpsGetRedirect) {
ConfigureDohServers(false /* use_post */);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(kT0RecordCount);
SetResponseModifierCallback(base::BindRepeating(MakeResponseRedirect));
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
void MakeResponseNoType(URLRequest* request, HttpResponseInfo* info) {
info->headers->RemoveHeader("Content-Type");
}
TEST_F(DnsTransactionTest, HttpsPostWithNoType) {
ConfigureDohServers(true /* use_post */);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_DNS_MALFORMED_RESPONSE);
SetResponseModifierCallback(base::BindRepeating(MakeResponseNoType));
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, CanLookupDohServerName) {
config_.search.push_back("http");
ConfigureDohServers(true /* use_post */);
AddQueryAndErrorResponse(0, kMockHostname, dns_protocol::kTypeA,
ERR_NAME_NOT_RESOLVED, SYNCHRONOUS, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(ERR_NAME_NOT_RESOLVED);
helper0.StartTransaction(transaction_factory_.get(), "mock",
dns_protocol::kTypeA, true /* secure */,
resolve_context_.get());
helper0.RunUntilComplete();
}
class CountingObserver : public net::NetLog::ThreadSafeObserver {
public:
CountingObserver() : count_(0), dict_count_(0) {}
~CountingObserver() override {
if (net_log())
net_log()->RemoveObserver(this);
}
void OnAddEntry(const NetLogEntry& entry) override {
++count_;
if (!entry.params.is_none() && entry.params.is_dict())
dict_count_++;
}
int count() const { return count_; }
int dict_count() const { return dict_count_; }
private:
int count_;
int dict_count_;
};
TEST_F(DnsTransactionTest, HttpsPostLookupWithLog) {
ConfigureDohServers(true /* use_post */);
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TransactionHelper helper0(kT0RecordCount);
CountingObserver observer;
helper0.net_log()->AddObserver(&observer, NetLogCaptureMode::kEverything);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
true /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(observer.count(), 5);
EXPECT_EQ(observer.dict_count(), 3);
}
// Test for when a slow DoH response is delayed until after the initial fallback
// 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);
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 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());
}
// Test for when a slow DoH response is delayed until after the initial fallback
// period but a retry is configured.
TEST_F(DnsTransactionTestWithMockTime, SlowHttpsResponse_TwoAttempts) {
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 */);
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());
ASSERT_TRUE(sequenced_socket_data->IsPaused());
// Another attempt configured, so transaction should not fail after initial
// fallback period. Setup the second attempt to never receive a response.
AddHangingQuery(kT0HostName, kT0Qtype,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128, 0 /* id */,
false /* enqueue_transaction_id */);
FastForwardBy(resolve_context_->NextDohFallbackPeriod(
0 /* doh_server_index */, session_.get()));
EXPECT_FALSE(helper.has_completed());
// Expect first attempt to continue in parallel with retry, so expect the
// transaction to complete when the first query is allowed to resume.
sequenced_socket_data->Resume();
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);
AddQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), ASYNC, Transport::TCP);
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, TcpLookup_LowEntropy) {
socket_factory_->diverse_source_ports_ = false;
for (int i = 0; i <= DnsUdpTracker::kPortReuseThreshold; ++i) {
AddQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), ASYNC, Transport::UDP);
}
AddQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), ASYNC, Transport::TCP);
for (int i = 0; i <= DnsUdpTracker::kPortReuseThreshold; ++i) {
TransactionHelper udp_helper(kT0RecordCount);
udp_helper.StartTransaction(transaction_factory_.get(), kT0HostName,
kT0Qtype, false /* secure */,
resolve_context_.get());
udp_helper.RunUntilComplete();
}
ASSERT_TRUE(session_->udp_tracker()->low_entropy());
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
EXPECT_TRUE(session_->udp_tracker()->low_entropy());
}
TEST_F(DnsTransactionTest, TCPFailure) {
AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL, ASYNC,
Transport::TCP);
TransactionHelper helper0(ERR_DNS_SERVER_FAILED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
ASSERT_NE(helper0.response(), nullptr);
EXPECT_EQ(helper0.response()->rcode(), dns_protocol::kRcodeSERVFAIL);
}
TEST_F(DnsTransactionTest, TCPMalformed) {
AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP));
// Valid response but length too short.
// This must be truncated in the question section. The DnsResponse doesn't
// examine the answer section until asked to parse it, so truncating it in
// the answer section would result in the DnsTransaction itself succeeding.
data->AddResponseWithLength(
std::make_unique<DnsResponse>(
reinterpret_cast<const char*>(kT0ResponseDatagram),
base::size(kT0ResponseDatagram), 0),
ASYNC, static_cast<uint16_t>(kT0QuerySize - 1));
AddSocketData(std::move(data));
TransactionHelper helper0(ERR_DNS_MALFORMED_RESPONSE);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTestWithMockTime, TcpTimeout_UdpRetry) {
ConfigureFactory();
AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
AddSocketData(std::make_unique<DnsSocketData>(
1 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP));
TransactionHelper helper0(ERR_DNS_TIMED_OUT);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(helper0.has_completed());
FastForwardUntilNoTasksRemain();
EXPECT_TRUE(helper0.has_completed());
}
TEST_F(DnsTransactionTestWithMockTime, TcpTimeout_LowEntropy) {
ConfigureFactory();
socket_factory_->diverse_source_ports_ = false;
for (int i = 0; i <= DnsUdpTracker::kPortReuseThreshold; ++i) {
AddQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram,
base::size(kT0ResponseDatagram), ASYNC, Transport::UDP);
}
AddSocketData(std::make_unique<DnsSocketData>(
1 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP));
for (int i = 0; i <= DnsUdpTracker::kPortReuseThreshold; ++i) {
TransactionHelper udp_helper(kT0RecordCount);
udp_helper.StartTransaction(transaction_factory_.get(), kT0HostName,
kT0Qtype, false /* secure */,
resolve_context_.get());
udp_helper.RunUntilComplete();
}
ASSERT_TRUE(session_->udp_tracker()->low_entropy());
TransactionHelper helper0(ERR_DNS_TIMED_OUT);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(helper0.has_completed());
FastForwardUntilNoTasksRemain();
EXPECT_TRUE(helper0.has_completed());
}
TEST_F(DnsTransactionTest, TCPReadReturnsZeroAsync) {
AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP));
// Return all but the last byte of the response.
data->AddResponseWithLength(
std::make_unique<DnsResponse>(
reinterpret_cast<const char*>(kT0ResponseDatagram),
base::size(kT0ResponseDatagram) - 1, 0),
ASYNC, static_cast<uint16_t>(base::size(kT0ResponseDatagram)));
// Then return a 0-length read.
data->AddReadError(0, ASYNC);
AddSocketData(std::move(data));
TransactionHelper helper0(ERR_CONNECTION_CLOSED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, TCPReadReturnsZeroSynchronous) {
AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP));
// Return all but the last byte of the response.
data->AddResponseWithLength(
std::make_unique<DnsResponse>(
reinterpret_cast<const char*>(kT0ResponseDatagram),
base::size(kT0ResponseDatagram) - 1, 0),
SYNCHRONOUS, static_cast<uint16_t>(base::size(kT0ResponseDatagram)));
// Then return a 0-length read.
data->AddReadError(0, SYNCHRONOUS);
AddSocketData(std::move(data));
TransactionHelper helper0(ERR_CONNECTION_CLOSED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, TCPConnectionClosedAsync) {
AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP));
data->AddReadError(ERR_CONNECTION_CLOSED, ASYNC);
AddSocketData(std::move(data));
TransactionHelper helper0(ERR_CONNECTION_CLOSED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, TCPConnectionClosedSynchronous) {
AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP));
data->AddReadError(ERR_CONNECTION_CLOSED, SYNCHRONOUS);
AddSocketData(std::move(data));
TransactionHelper helper0(ERR_CONNECTION_CLOSED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, MismatchedThenNxdomainThenTCP) {
config_.attempts = 2;
ConfigureFactory();
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::UDP));
// First attempt gets a mismatched response.
data->AddResponseData(kT1ResponseDatagram, base::size(kT1ResponseDatagram),
SYNCHRONOUS);
// Second read from first attempt gets TCP required.
data->AddRcode(dns_protocol::kFlagTC, ASYNC);
AddSocketData(std::move(data));
// Second attempt gets NXDOMAIN, which happens before the TCP required.
AddSyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNXDOMAIN);
TransactionHelper helper0(ERR_NAME_NOT_RESOLVED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, MismatchedThenOkThenTCP) {
config_.attempts = 2;
ConfigureFactory();
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::UDP));
// First attempt gets a mismatched response.
data->AddResponseData(kT1ResponseDatagram, base::size(kT1ResponseDatagram),
SYNCHRONOUS);
// Second read from first attempt gets TCP required.
data->AddRcode(dns_protocol::kFlagTC, ASYNC);
AddSocketData(std::move(data));
// Second attempt gets a valid response, which happens before the TCP
// required.
AddSyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
kT0ResponseDatagram, base::size(kT0ResponseDatagram));
TransactionHelper helper0(kT0RecordCount);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, MismatchedThenRefusedThenTCP) {
// Set up the expected sequence of events:
// 1) First attempt (UDP) gets a synchronous mismatched response. On such
// malformed responses, DnsTransaction triggers an immediate retry to read
// again from the socket within the same "attempt".
// 2) Second read (within the first attempt) starts. Test is configured to
// give an asynchronous TCP required response which will complete later.
// On asynchronous action after a malformed response, the attempt will
// immediately produce a retriable error result while the retry continues,
// thus forking the running attempts.
// 3) Error result triggers a second attempt (UDP) which test gives a
// synchronous ERR_CONNECTION_REFUSED, which is a retriable error, but
// DnsTransaction has exhausted max retries (2 attempts), so this result
// gets posted as the result of the transaction and other running attempts
// should be cancelled.
// 4) First attempt should be cancelled when the transaction result is posted,
// so first attempt's second read should never complete. If it did
// complete, it would complete with a TCP-required error, and
// DnsTransaction would start a TCP attempt and clear previous attempts. It
// would be very bad if that then cleared the attempt posted as the final
// result, as result handling does not expect that memory to go away.
config_.attempts = 2;
ConfigureFactory();
// Attempt 1.
std::unique_ptr<DnsSocketData> data(new DnsSocketData(
0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::UDP));
data->AddResponseData(kT1ResponseDatagram, base::size(kT1ResponseDatagram),
SYNCHRONOUS);
data->AddRcode(dns_protocol::kFlagTC, ASYNC);
AddSocketData(std::move(data));
// Attempt 2.
AddQueryAndErrorResponse(0 /* id */, kT0HostName, kT0Qtype,
ERR_CONNECTION_REFUSED, SYNCHRONOUS, Transport::UDP);
TransactionHelper helper0(ERR_CONNECTION_REFUSED);
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
false /* secure */, resolve_context_.get());
helper0.RunUntilComplete();
}
TEST_F(DnsTransactionTest, InvalidQuery) {
ConfigureFactory();
TransactionHelper helper0(ERR_INVALID_ARGUMENT);
helper0.StartTransaction(transaction_factory_.get(), ".",
dns_protocol::kTypeA, false /* secure */,
resolve_context_.get());
helper0.RunUntilComplete();
TransactionHelper helper1(ERR_INVALID_ARGUMENT);
helper1.StartTransaction(transaction_factory_.get(), "foo,bar.com",
dns_protocol::kTypeA, false /* secure */,
resolve_context_.get());
helper1.RunUntilComplete();
}
TEST_F(DnsTransactionTestWithMockTime, ProbeUntilSuccess) {
ConfigureDohServers(true /* use_post */, 1 /* num_doh_servers */,
false /* make_available */);
AddQueryAndErrorResponse(0 /* id */, kT4HostName, kT4Qtype,
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndErrorResponse(0 /* id */, kT4HostName, kT4Qtype,
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
base::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
std::unique_ptr<DnsProbeRunner> runner =
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
runner->Start(false /* network_change */);
// The first probe happens without any delay.
RunUntilIdle();
std::unique_ptr<DnsServerIterator> doh_itr = resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
EXPECT_FALSE(doh_itr->AttemptAvailable());
// Expect the server to still be unavailable after the second probe.
FastForwardBy(runner->GetDelayUntilNextProbeForTest(0));
EXPECT_FALSE(doh_itr->AttemptAvailable());
// Expect the server to be available after the successful third probe.
FastForwardBy(runner->GetDelayUntilNextProbeForTest(0));
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
}
// Test that if a probe attempt hangs, additional probes will still run on
// schedule
TEST_F(DnsTransactionTestWithMockTime, HungProbe) {
ConfigureDohServers(true /* use_post */, 1 /* num_doh_servers */,
false /* make_available */);
// Create a socket data to first return ERR_IO_PENDING. This will pause the
// probe and not return the error until SequencedSocketData::Resume() is
// called.
auto data = std::make_unique<DnsSocketData>(
0 /* id */, kT4HostName, kT4Qtype, ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128);
data->AddReadError(ERR_IO_PENDING, ASYNC);
data->AddReadError(ERR_CONNECTION_REFUSED, ASYNC);
data->AddResponseData(kT4ResponseDatagram, base::size(kT4ResponseDatagram),
ASYNC);
SequencedSocketData* sequenced_socket_data = data->GetProvider();
AddSocketData(std::move(data), false /* enqueue_transaction_id */);
// Add success for second probe.
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
base::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
std::unique_ptr<DnsProbeRunner> runner =
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
runner->Start(false /* network_change */);
// The first probe starts without any delay, but doesn't finish.
RunUntilIdle();
EXPECT_FALSE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
// Second probe succeeds.
FastForwardBy(runner->GetDelayUntilNextProbeForTest(0));
EXPECT_TRUE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
// Probe runner self-cancels on next cycle.
FastForwardBy(runner->GetDelayUntilNextProbeForTest(0));
EXPECT_EQ(runner->GetDelayUntilNextProbeForTest(0), base::TimeDelta());
// Expect no effect when the hung probe wakes up and fails.
sequenced_socket_data->Resume();
RunUntilIdle();
EXPECT_TRUE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
EXPECT_EQ(runner->GetDelayUntilNextProbeForTest(0), base::TimeDelta());
}
TEST_F(DnsTransactionTestWithMockTime, ProbeMultipleServers) {
ConfigureDohServers(true /* use_post */, 2 /* num_doh_servers */,
false /* make_available */);
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
base::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndErrorResponse(0 /* id */, kT4HostName, kT4Qtype,
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
base::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
ASSERT_FALSE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
ASSERT_FALSE(resolve_context_->GetDohServerAvailability(
1u /* doh_server_index */, session_.get()));
std::unique_ptr<DnsProbeRunner> runner =
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
runner->Start(true /* network_change */);
// The first probes happens without any delay and succeeds for only one server
RunUntilIdle();
EXPECT_TRUE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
EXPECT_FALSE(resolve_context_->GetDohServerAvailability(
1u /* doh_server_index */, session_.get()));
// On second round of probing, probes for first server should self-cancel and
// second server should become available.
FastForwardBy(
runner->GetDelayUntilNextProbeForTest(0u /* doh_server_index */));
EXPECT_EQ(runner->GetDelayUntilNextProbeForTest(0u /* doh_server_index */),
base::TimeDelta());
FastForwardBy(
runner->GetDelayUntilNextProbeForTest(1u /* doh_server_index */));
EXPECT_TRUE(resolve_context_->GetDohServerAvailability(
1u /* doh_server_index */, session_.get()));
// Expect server 2 probes to self-cancel on next cycle.
FastForwardBy(runner->GetDelayUntilNextProbeForTest(1u));
EXPECT_EQ(runner->GetDelayUntilNextProbeForTest(1u), base::TimeDelta());
}
TEST_F(DnsTransactionTestWithMockTime, MultipleProbeRunners) {
ConfigureDohServers(true /* use_post */, 1 /* num_doh_servers */,
false /* make_available */);
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
base::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
base::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
std::unique_ptr<DnsProbeRunner> runner1 =
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
std::unique_ptr<DnsProbeRunner> runner2 =
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
runner1->Start(true /* network_change */);
runner2->Start(true /* network_change */);
// The first two probes (one for each runner) happen without any delay
// and mark the first server good.
RunUntilIdle();
{
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
}
// Both probes expected to self-cancel on next scheduled run.
FastForwardBy(runner1->GetDelayUntilNextProbeForTest(0));
FastForwardBy(runner2->GetDelayUntilNextProbeForTest(0));
EXPECT_EQ(runner1->GetDelayUntilNextProbeForTest(0), base::TimeDelta());
EXPECT_EQ(runner2->GetDelayUntilNextProbeForTest(0), base::TimeDelta());
}
TEST_F(DnsTransactionTestWithMockTime, MultipleProbeRunners_SeparateContexts) {
// Each RequestContext uses its own transient IsolationInfo. Since there's
// typically only one RequestContext per URLRequestContext, there's no
// advantage in using the same IsolationInfo across RequestContexts.
set_expect_multiple_isolation_infos(true);
ConfigureDohServers(true /* use_post */, 1 /* num_doh_servers */,
false /* make_available */);
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
base::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndErrorResponse(0 /* id */, kT4HostName, kT4Qtype,
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
base::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
TestURLRequestContext request_context2;
ResolveContext context2(&request_context2, false /* enable_caching */);
context2.InvalidateCachesAndPerSessionData(session_.get(),
false /* network_change */);
std::unique_ptr<DnsProbeRunner> runner1 =
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
std::unique_ptr<DnsProbeRunner> runner2 =
transaction_factory_->CreateDohProbeRunner(&context2);
runner1->Start(false /* network_change */);
runner2->Start(false /* network_change */);
// The first two probes (one for each runner) happen without any delay.
// Probe for first context succeeds and second fails.
RunUntilIdle();
{
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
}
{
std::unique_ptr<DnsServerIterator> doh_itr2 = context2.GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
EXPECT_FALSE(doh_itr2->AttemptAvailable());
}
// First probe runner expected to be compete and self-cancel on next run.
FastForwardBy(runner1->GetDelayUntilNextProbeForTest(0));
EXPECT_EQ(runner1->GetDelayUntilNextProbeForTest(0), base::TimeDelta());
// Expect second runner to succeed on its second probe.
FastForwardBy(runner2->GetDelayUntilNextProbeForTest(0));
{
std::unique_ptr<DnsServerIterator> doh_itr2 = context2.GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
ASSERT_TRUE(doh_itr2->AttemptAvailable());
EXPECT_EQ(doh_itr2->GetNextAttemptIndex(), 0u);
}
FastForwardBy(runner2->GetDelayUntilNextProbeForTest(0));
EXPECT_EQ(runner2->GetDelayUntilNextProbeForTest(0), base::TimeDelta());
}
TEST_F(DnsTransactionTestWithMockTime, CancelDohProbe) {
ConfigureDohServers(true /* use_post */, 1 /* num_doh_servers */,
false /* make_available */);
AddQueryAndErrorResponse(0 /* id */, kT4HostName, kT4Qtype,
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndErrorResponse(0 /* id */, kT4HostName, kT4Qtype,
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
std::unique_ptr<DnsProbeRunner> runner =
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
runner->Start(false /* network_change */);
// The first probe happens without any delay.
RunUntilIdle();
std::unique_ptr<DnsServerIterator> doh_itr = resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
EXPECT_FALSE(doh_itr->AttemptAvailable());
// Expect the server to still be unavailable after the second probe.
FastForwardBy(runner->GetDelayUntilNextProbeForTest(0));
EXPECT_FALSE(doh_itr->AttemptAvailable());
base::TimeDelta next_delay = runner->GetDelayUntilNextProbeForTest(0);
runner.reset();
// Server stays unavailable because probe canceled before (non-existent)
// success. No success result is added, so this FastForward will cause a
// failure if probes attempt to run.
FastForwardBy(next_delay);
EXPECT_FALSE(doh_itr->AttemptAvailable());
}
TEST_F(DnsTransactionTestWithMockTime, CancelOneOfMultipleProbeRunners) {
ConfigureDohServers(true /* use_post */, 1 /* num_doh_servers */,
false /* make_available */);
AddQueryAndErrorResponse(0 /* id */, kT4HostName, kT4Qtype,
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndErrorResponse(0 /* id */, kT4HostName, kT4Qtype,
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
base::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
std::unique_ptr<DnsProbeRunner> runner1 =
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
std::unique_ptr<DnsProbeRunner> runner2 =
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
runner1->Start(true /* network_change */);
runner2->Start(true /* network_change */);
// The first two probes (one for each runner) happen without any delay.
RunUntilIdle();
std::unique_ptr<DnsServerIterator> doh_itr = resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
EXPECT_FALSE(doh_itr->AttemptAvailable());
EXPECT_GT(runner1->GetDelayUntilNextProbeForTest(0), base::TimeDelta());
EXPECT_GT(runner2->GetDelayUntilNextProbeForTest(0), base::TimeDelta());
// Cancel only one probe runner.
runner1.reset();
// Expect the server to be available after the successful third probe.
FastForwardBy(runner2->GetDelayUntilNextProbeForTest(0));
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
FastForwardBy(runner2->GetDelayUntilNextProbeForTest(0));
EXPECT_EQ(runner2->GetDelayUntilNextProbeForTest(0), base::TimeDelta());
}
TEST_F(DnsTransactionTestWithMockTime, CancelAllOfMultipleProbeRunners) {
ConfigureDohServers(true /* use_post */, 1 /* num_doh_servers */,
false /* make_available */);
AddQueryAndErrorResponse(0 /* id */, kT4HostName, kT4Qtype,
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndErrorResponse(0 /* id */, kT4HostName, kT4Qtype,
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
std::unique_ptr<DnsProbeRunner> runner1 =
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
std::unique_ptr<DnsProbeRunner> runner2 =
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
runner1->Start(false /* network_change */);
runner2->Start(false /* network_change */);
// The first two probes (one for each runner) happen without any delay.
RunUntilIdle();
std::unique_ptr<DnsServerIterator> doh_itr = resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
EXPECT_FALSE(doh_itr->AttemptAvailable());
EXPECT_GT(runner1->GetDelayUntilNextProbeForTest(0), base::TimeDelta());
EXPECT_GT(runner2->GetDelayUntilNextProbeForTest(0), base::TimeDelta());
base::TimeDelta next_delay = runner1->GetDelayUntilNextProbeForTest(0);
runner1.reset();
runner2.reset();
// Server stays unavailable because probe canceled before (non-existent)
// success. No success result is added, so this FastForward will cause a
// failure if probes attempt to run.
FastForwardBy(next_delay);
EXPECT_FALSE(doh_itr->AttemptAvailable());
}
TEST_F(DnsTransactionTestWithMockTime, CancelDohProbe_AfterSuccess) {
ConfigureDohServers(true /* use_post */, 1 /* num_doh_servers */,
false /* make_available */);
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
base::size(kT4ResponseDatagram), SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
std::unique_ptr<DnsProbeRunner> runner =
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
runner->Start(true /* network_change */);
// The first probe happens without any delay, and immediately succeeds.
RunUntilIdle();
{
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
}
runner.reset();
// No change expected after cancellation.
RunUntilIdle();
{
std::unique_ptr<DnsServerIterator> doh_itr =
resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
ASSERT_TRUE(doh_itr->AttemptAvailable());
EXPECT_EQ(doh_itr->GetNextAttemptIndex(), 0u);
}
}
TEST_F(DnsTransactionTestWithMockTime, DestroyFactoryAfterStartingDohProbe) {
ConfigureDohServers(true /* use_post */, 1 /* num_doh_servers */,
false /* make_available */);
AddQueryAndErrorResponse(0 /* id */, kT4HostName, kT4Qtype,
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
std::unique_ptr<DnsProbeRunner> runner =
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
runner->Start(false /* network_change */);
// The first probe happens without any delay.
RunUntilIdle();
std::unique_ptr<DnsServerIterator> doh_itr = resolve_context_->GetDohIterator(
session_->config(), SecureDnsMode::kAutomatic, session_.get());
EXPECT_FALSE(doh_itr->AttemptAvailable());
// Destroy factory and session.
transaction_factory_.reset();
ASSERT_TRUE(session_->HasOneRef());
session_.reset();
// Probe should not encounter issues and should stop running.
FastForwardBy(runner->GetDelayUntilNextProbeForTest(0));
EXPECT_EQ(runner->GetDelayUntilNextProbeForTest(0), base::TimeDelta());
}
TEST_F(DnsTransactionTestWithMockTime, StartWhileRunning) {
ConfigureDohServers(true /* use_post */, 1 /* num_doh_servers */,
false /* make_available */);
AddQueryAndErrorResponse(0 /* id */, kT4HostName, kT4Qtype,
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
Transport::HTTPS, nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
base::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
std::unique_ptr<DnsProbeRunner> runner =
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
runner->Start(false /* network_change */);
// The first probe happens without any delay.
RunUntilIdle();
EXPECT_FALSE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
// Extra Start() call should have no effect because runner is already running.
runner->Start(true /* network_change */);
RunUntilIdle();
EXPECT_FALSE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
// Expect the server to be available after the successful second probe.
FastForwardBy(runner->GetDelayUntilNextProbeForTest(0));
EXPECT_TRUE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
}
TEST_F(DnsTransactionTestWithMockTime, RestartFinishedProbe) {
ConfigureDohServers(true /* use_post */, 1 /* num_doh_servers */,
false /* make_available */);
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
base::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
base::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
std::unique_ptr<DnsProbeRunner> runner =
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
runner->Start(true /* network_change */);
// The first probe happens without any delay and succeeds.
RunUntilIdle();
EXPECT_TRUE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
// Expect runner to self-cancel on next cycle.
FastForwardBy(runner->GetDelayUntilNextProbeForTest(0u));
EXPECT_EQ(runner->GetDelayUntilNextProbeForTest(0u), base::TimeDelta());
// Mark server unavailabe and restart runner.
for (int i = 0; i < ResolveContext::kAutomaticModeFailureLimit; ++i) {
resolve_context_->RecordServerFailure(0u /* server_index */,
true /* is_doh_server */, ERR_FAILED,
session_.get());
}
ASSERT_FALSE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
runner->Start(false /* network_change */);
// Expect the server to be available again after a successful immediately-run
// probe.
RunUntilIdle();
EXPECT_TRUE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
// Expect self-cancel again.
FastForwardBy(runner->GetDelayUntilNextProbeForTest(0u));
EXPECT_EQ(runner->GetDelayUntilNextProbeForTest(0u), base::TimeDelta());
}
// Test that a probe runner keeps running on the same schedule if it completes
// but the server is marked unavailable again before the next scheduled probe.
TEST_F(DnsTransactionTestWithMockTime, FastProbeRestart) {
ConfigureDohServers(true /* use_post */, 1 /* num_doh_servers */,
false /* make_available */);
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
base::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
base::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
nullptr /* opt_rdata */,
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
false /* enqueue_transaction_id */);
std::unique_ptr<DnsProbeRunner> runner =
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
runner->Start(true /* network_change */);
// The first probe happens without any delay and succeeds.
RunUntilIdle();
EXPECT_TRUE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
base::TimeDelta scheduled_delay = runner->GetDelayUntilNextProbeForTest(0);
EXPECT_GT(scheduled_delay, base::TimeDelta());
// Mark server unavailabe and restart runner. Note that restarting the runner
// is unnecessary, but a Start() call should always happen on a server
// becoming unavailable and might as well replecate real behavior for the
// test.
for (int i = 0; i < ResolveContext::kAutomaticModeFailureLimit; ++i) {
resolve_context_->RecordServerFailure(0u /* server_index */,
true /* is_doh_server */, ERR_FAILED,
session_.get());
}
ASSERT_FALSE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
runner->Start(false /* network_change */);
// Probe should not run until scheduled delay.
RunUntilIdle();
EXPECT_FALSE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
// Expect the probe to run again and succeed after scheduled delay.
FastForwardBy(scheduled_delay);
EXPECT_TRUE(resolve_context_->GetDohServerAvailability(
0u /* doh_server_index */, session_.get()));
}
} // namespace
} // namespace net