4167 lines
175 KiB
C++
4167 lines
175 KiB
C++
// Copyright 2012 The Chromium Authors
|
|
// 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 <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "base/base64url.h"
|
|
#include "base/containers/circular_deque.h"
|
|
#include "base/functional/bind.h"
|
|
#include "base/memory/raw_ptr.h"
|
|
#include "base/numerics/safe_math.h"
|
|
#include "base/rand_util.h"
|
|
#include "base/ranges/algorithm.h"
|
|
#include "base/run_loop.h"
|
|
#include "base/strings/string_util.h"
|
|
#include "base/strings/stringprintf.h"
|
|
#include "base/sys_byteorder.h"
|
|
#include "base/task/single_thread_task_runner.h"
|
|
#include "base/test/bind.h"
|
|
#include "base/test/metrics/histogram_tester.h"
|
|
#include "base/time/time.h"
|
|
#include "base/values.h"
|
|
#include "net/base/idempotency.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_names_util.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_test_util.h"
|
|
#include "net/dns/public/dns_over_https_config.h"
|
|
#include "net/dns/public/dns_over_https_server_config.h"
|
|
#include "net/dns/public/dns_protocol.h"
|
|
#include "net/dns/public/secure_dns_policy.h"
|
|
#include "net/dns/resolve_context.h"
|
|
#include "net/http/http_util.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/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_context.h"
|
|
#include "net/url_request/url_request_context_builder.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"
|
|
#include "third_party/abseil-cpp/absl/types/optional.h"
|
|
|
|
using net::test::IsOk;
|
|
|
|
namespace net {
|
|
|
|
namespace {
|
|
|
|
base::TimeDelta kFallbackPeriod = base::Seconds(1);
|
|
|
|
const char kMockHostname[] = "mock.http";
|
|
|
|
std::vector<uint8_t> DomainFromDot(base::StringPiece dotted_name) {
|
|
absl::optional<std::vector<uint8_t>> dns_name =
|
|
dns_names_util::DottedNameToNetwork(dotted_name);
|
|
CHECK(dns_name.has_value());
|
|
return dns_name.value();
|
|
}
|
|
|
|
enum class Transport { UDP, TCP, HTTPS };
|
|
|
|
class NetLogCountingObserver : public net::NetLog::ThreadSafeObserver {
|
|
public:
|
|
NetLogCountingObserver() = default;
|
|
|
|
~NetLogCountingObserver() override {
|
|
if (net_log())
|
|
net_log()->RemoveObserver(this);
|
|
}
|
|
|
|
void OnAddEntry(const NetLogEntry& entry) override {
|
|
++count_;
|
|
if (!entry.params.empty()) {
|
|
dict_count_++;
|
|
}
|
|
}
|
|
|
|
int count() const { return count_; }
|
|
|
|
int dict_count() const { return dict_count_; }
|
|
|
|
private:
|
|
int count_ = 0;
|
|
int dict_count_ = 0;
|
|
};
|
|
|
|
// 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_(std::make_unique<DnsQuery>(id,
|
|
DomainFromDot(dotted_name),
|
|
qtype,
|
|
opt_rdata,
|
|
padding_strategy)),
|
|
transport_(transport) {
|
|
if (Transport::TCP == transport_) {
|
|
auto length = std::make_unique<uint16_t>();
|
|
*length = base::HostToNet16(query_->io_buffer()->size());
|
|
writes_.emplace_back(mode, reinterpret_cast<const char*>(length.get()),
|
|
sizeof(uint16_t), num_reads_and_writes());
|
|
lengths_.push_back(std::move(length));
|
|
}
|
|
writes_.emplace_back(mode, query_->io_buffer()->data(),
|
|
query_->io_buffer()->size(), num_reads_and_writes());
|
|
}
|
|
|
|
DnsSocketData(const DnsSocketData&) = delete;
|
|
DnsSocketData& operator=(const DnsSocketData&) = delete;
|
|
|
|
~DnsSocketData() = default;
|
|
|
|
void ClearWrites() { writes_.clear(); }
|
|
// 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_) {
|
|
auto length = std::make_unique<uint16_t>();
|
|
*length = base::HostToNet16(tcp_length);
|
|
reads_.emplace_back(mode, reinterpret_cast<const char*>(length.get()),
|
|
sizeof(uint16_t), num_reads_and_writes());
|
|
lengths_.push_back(std::move(length));
|
|
}
|
|
reads_.emplace_back(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) {
|
|
auto response = std::make_unique<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_.emplace_back(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_.emplace_back(SYNCHRONOUS, ERR_IO_PENDING,
|
|
writes_.size() + reads_.size());
|
|
}
|
|
provider_ = std::make_unique<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_;
|
|
};
|
|
|
|
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(const FailingUDPClientSocket&) = delete;
|
|
FailingUDPClientSocket& operator=(const FailingUDPClientSocket&) = delete;
|
|
|
|
~FailingUDPClientSocket() override = default;
|
|
int Connect(const IPEndPoint& endpoint) override {
|
|
return ERR_CONNECTION_REFUSED;
|
|
}
|
|
};
|
|
|
|
// 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(const TestUDPClientSocket&) = delete;
|
|
TestUDPClientSocket& operator=(const TestUDPClientSocket&) = delete;
|
|
|
|
~TestUDPClientSocket() override = default;
|
|
int Connect(const IPEndPoint& endpoint) override;
|
|
int ConnectAsync(const IPEndPoint& address,
|
|
CompletionOnceCallback callback) override;
|
|
|
|
private:
|
|
raw_ptr<TestSocketFactory> factory_;
|
|
};
|
|
|
|
// 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::make_unique<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) {}
|
|
|
|
absl::optional<IPEndPoint> insecure_nameserver;
|
|
absl::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);
|
|
}
|
|
|
|
int TestUDPClientSocket::ConnectAsync(const IPEndPoint& address,
|
|
CompletionOnceCallback callback) {
|
|
factory_->OnConnect(address);
|
|
return MockUDPClientSocket::ConnectAsync(address, std::move(callback));
|
|
}
|
|
|
|
// 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,
|
|
NetLogWithSource::Make(net::NetLog::Get(), 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(base::BindOnce(
|
|
&TransactionHelper::OnTransactionComplete, base::Unretained(this)));
|
|
}
|
|
|
|
void Cancel() {
|
|
ASSERT_TRUE(transaction_.get() != nullptr);
|
|
transaction_.reset(nullptr);
|
|
}
|
|
|
|
void OnTransactionComplete(int rv, const DnsResponse* response) {
|
|
EXPECT_FALSE(completed_);
|
|
|
|
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->GetSingleQType());
|
|
|
|
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_; }
|
|
|
|
// 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_;
|
|
raw_ptr<const DnsResponse> response_ = nullptr;
|
|
int expected_answer_count_;
|
|
bool cancel_in_callback_ = false;
|
|
base::RunLoop transaction_complete_run_loop_;
|
|
bool completed_ = false;
|
|
};
|
|
|
|
// Callback that allows a test to modify HttpResponseinfo
|
|
// before the response is sent to the requester. This allows
|
|
// response headers to be changed.
|
|
using ResponseModifierCallback =
|
|
base::RepeatingCallback<void(URLRequest* request, HttpResponseInfo* info)>;
|
|
|
|
// Callback that allows the test to substitute its own implementation
|
|
// of URLRequestJob to handle the request.
|
|
using DohJobMakerCallback = base::RepeatingCallback<std::unique_ptr<
|
|
URLRequestJob>(URLRequest* request, SocketDataProvider* data_provider)>;
|
|
|
|
// Callback to notify that URLRequestJob::Start has been called.
|
|
using UrlRequestStartedCallback = base::RepeatingCallback<void()>;
|
|
|
|
// 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(),
|
|
UrlRequestStartedCallback on_start = UrlRequestStartedCallback())
|
|
: URLRequestJob(request),
|
|
data_provider_(data_provider),
|
|
response_modifier_(response_modifier),
|
|
on_start_(on_start) {
|
|
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") {
|
|
EXPECT_EQ(IDEMPOTENT, request->GetIdempotency());
|
|
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 {
|
|
if (on_start_)
|
|
on_start_.Run();
|
|
// Start reading asynchronously so that all error reporting and data
|
|
// callbacks happen as they would for network requests.
|
|
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
|
FROM_HERE, base::BindOnce(&URLRequestMockDohJob::StartAsync,
|
|
weak_factory_.GetWeakPtr()));
|
|
}
|
|
|
|
URLRequestMockDohJob(const URLRequestMockDohJob&) = delete;
|
|
URLRequestMockDohJob& operator=(const URLRequestMockDohJob&) = delete;
|
|
|
|
~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_ = 0;
|
|
const char* leftover_data_;
|
|
int leftover_data_len_ = 0;
|
|
raw_ptr<SocketDataProvider> data_provider_;
|
|
const ResponseModifierCallback response_modifier_;
|
|
const UrlRequestStartedCallback on_start_;
|
|
raw_ptr<IOBuffer> pending_buf_;
|
|
int pending_buf_size_;
|
|
|
|
base::WeakPtrFactory<URLRequestMockDohJob> weak_factory_{this};
|
|
};
|
|
|
|
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.emplace_back(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);
|
|
std::vector<string> templates;
|
|
templates.reserve(num_doh_servers);
|
|
for (size_t i = 0; i < num_doh_servers; ++i) {
|
|
templates.push_back(URLRequestMockDohJob::GetMockHttpsUrl(
|
|
base::StringPrintf("doh_test_%zu", i)) +
|
|
(use_post ? "" : "{?dns}"));
|
|
}
|
|
config_.doh_config =
|
|
*DnsOverHttpsConfig::FromTemplatesForTesting(std::move(templates));
|
|
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() {
|
|
session_ = base::MakeRefCounted<DnsSession>(
|
|
config_,
|
|
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));
|
|
}
|
|
|
|
void AddQueryAndResponseNoWrite(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) {
|
|
CHECK(socket_factory_.get());
|
|
auto data = std::make_unique<DnsSocketData>(
|
|
id, dotted_name, qtype, mode, transport, opt_rdata, padding_strategy);
|
|
data->ClearWrites();
|
|
AddSocketData(std::move(data), true);
|
|
}
|
|
|
|
// 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());
|
|
auto data = std::make_unique<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());
|
|
auto data = std::make_unique<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) {
|
|
auto data = std::make_unique<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);
|
|
auto data = std::make_unique<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()
|
|
.doh_config.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_.doh_config.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 = base::ranges::mismatch(prefix, request->url().spec());
|
|
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_EQ(SecureDnsPolicy::kBootstrap, request->secure_dns_policy());
|
|
|
|
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_, on_start_);
|
|
}
|
|
|
|
class DohJobInterceptor : public URLRequestInterceptor {
|
|
public:
|
|
explicit DohJobInterceptor(DnsTransactionTestBase* test) : test_(test) {}
|
|
|
|
DohJobInterceptor(const DohJobInterceptor&) = delete;
|
|
DohJobInterceptor& operator=(const DohJobInterceptor&) = delete;
|
|
|
|
~DohJobInterceptor() override = default;
|
|
|
|
// URLRequestInterceptor implementation:
|
|
std::unique_ptr<URLRequestJob> MaybeInterceptRequest(
|
|
URLRequest* request) const override {
|
|
return test_->MaybeInterceptRequest(request);
|
|
}
|
|
|
|
private:
|
|
raw_ptr<DnsTransactionTestBase> test_;
|
|
};
|
|
|
|
void SetResponseModifierCallback(ResponseModifierCallback response_modifier) {
|
|
response_modifier_ = response_modifier;
|
|
}
|
|
|
|
void SetDohJobMakerCallback(DohJobMakerCallback doh_job_maker) {
|
|
doh_job_maker_ = doh_job_maker;
|
|
}
|
|
|
|
void SetUrlRequestStartedCallback(UrlRequestStartedCallback on_start) {
|
|
on_start_ = on_start;
|
|
}
|
|
|
|
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;
|
|
auto context_builder = CreateTestURLRequestContextBuilder();
|
|
socket_factory_ = std::make_unique<TestSocketFactory>();
|
|
context_builder->set_client_socket_factory_for_testing(
|
|
socket_factory_.get());
|
|
request_context_ = context_builder->Build();
|
|
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<URLRequestContext> request_context_;
|
|
std::unique_ptr<ResolveContext> resolve_context_;
|
|
scoped_refptr<DnsSession> session_;
|
|
std::unique_ptr<DnsTransactionFactory> transaction_factory_;
|
|
ResponseModifierCallback response_modifier_;
|
|
UrlRequestStartedCallback on_start_;
|
|
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, std::size(kT0ResponseDatagram));
|
|
|
|
TransactionHelper helper0(kT0RecordCount);
|
|
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
|
|
false /* secure */, resolve_context_.get());
|
|
helper0.RunUntilComplete();
|
|
}
|
|
|
|
TEST_F(DnsTransactionTest, LookupWithLog) {
|
|
AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
|
|
kT0ResponseDatagram, std::size(kT0ResponseDatagram));
|
|
|
|
TransactionHelper helper0(kT0RecordCount);
|
|
NetLogCountingObserver observer;
|
|
NetLog::Get()->AddObserver(&observer, NetLogCaptureMode::kEverything);
|
|
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
|
|
false /* secure */, resolve_context_.get());
|
|
helper0.RunUntilComplete();
|
|
EXPECT_EQ(observer.count(), 6);
|
|
EXPECT_EQ(observer.dict_count(), 4);
|
|
}
|
|
|
|
TEST_F(DnsTransactionTest, LookupWithEDNSOption) {
|
|
OptRecordRdata expected_opt_rdata;
|
|
|
|
transaction_factory_->AddEDNSOption(
|
|
OptRecordRdata::UnknownOpt::CreateForTesting(123, "\xbe\xef"));
|
|
expected_opt_rdata.AddOpt(
|
|
OptRecordRdata::UnknownOpt::CreateForTesting(123, "\xbe\xef"));
|
|
|
|
AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
|
|
kT0ResponseDatagram, std::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;
|
|
|
|
std::vector<std::pair<uint16_t, std::string>> params = {
|
|
// Two options with the same code, to check that both are included.
|
|
std::pair<uint16_t, std::string>(1, "\xde\xad"),
|
|
std::pair<uint16_t, std::string>(1, "\xbe\xef"),
|
|
// Try a different code and different length of data.
|
|
std::pair<uint16_t, std::string>(2, "\xff")};
|
|
|
|
for (auto& param : params) {
|
|
transaction_factory_->AddEDNSOption(
|
|
OptRecordRdata::UnknownOpt::CreateForTesting(param.first,
|
|
param.second));
|
|
expected_opt_rdata.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
|
|
param.first, param.second));
|
|
}
|
|
|
|
AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
|
|
kT0ResponseDatagram, std::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, std::size(kT0ResponseDatagram));
|
|
AddAsyncQueryAndResponse(1 /* id */, kT1HostName, kT1Qtype,
|
|
kT1ResponseDatagram, std::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) {
|
|
AddQueryAndResponseNoWrite(0 /* id */, kT0HostName, kT0Qtype, ASYNC,
|
|
Transport::UDP, nullptr);
|
|
|
|
AddAsyncQueryAndResponse(1 /* id */, kT1HostName, kT1Qtype,
|
|
kT1ResponseDatagram, std::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, std::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, std::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.
|
|
auto data = std::make_unique<DnsSocketData>(0 /* id */, kT0HostName, kT0Qtype,
|
|
SYNCHRONOUS, Transport::UDP);
|
|
data->AddResponseData(kT1ResponseDatagram, std::size(kT1ResponseDatagram),
|
|
SYNCHRONOUS);
|
|
AddSocketData(std::move(data));
|
|
|
|
// Second attempt receives valid response synchronously.
|
|
auto data1 = std::make_unique<DnsSocketData>(
|
|
0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::UDP);
|
|
data1->AddResponseData(kT0ResponseDatagram, std::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.
|
|
auto data0 = std::make_unique<DnsSocketData>(0 /* id */, kT0HostName,
|
|
kT0Qtype, ASYNC, Transport::UDP);
|
|
data0->AddResponseData(kT1ResponseDatagram, std::size(kT1ResponseDatagram),
|
|
ASYNC);
|
|
AddSocketData(std::move(data0));
|
|
|
|
// Second attempt receives valid response asynchronously.
|
|
auto data1 = std::make_unique<DnsSocketData>(0 /* id */, kT0HostName,
|
|
kT0Qtype, ASYNC, Transport::UDP);
|
|
data1->AddResponseData(kT0ResponseDatagram, std::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 that responses are not accepted when only the response ID mismatches.
|
|
// Tests against incorrect transaction ID validation, which is anti-pattern #1
|
|
// from the "NAME:WRECK" report:
|
|
// https://www.forescout.com/company/resources/namewreck-breaking-and-fixing-dns-implementations/
|
|
TEST_F(DnsTransactionTest, MismatchedResponseFail) {
|
|
ConfigureFactory();
|
|
|
|
// Attempt receives mismatched response and fails because only one attempt is
|
|
// allowed.
|
|
AddAsyncQueryAndResponse(1 /* id */, kT0HostName, kT0Qtype,
|
|
kT0ResponseDatagram, std::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.
|
|
auto data = std::make_unique<DnsSocketData>(0 /* id */, kT0HostName, kT0Qtype,
|
|
SYNCHRONOUS, Transport::UDP);
|
|
data->AddResponseData(kT1ResponseDatagram, std::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();
|
|
}
|
|
|
|
// This is a regression test for https://crbug.com/1410442.
|
|
TEST_F(DnsTransactionTest, ZeroSizeResponseAsync) {
|
|
config_.attempts = 2;
|
|
ConfigureFactory();
|
|
|
|
// First attempt receives zero size response asynchronously.
|
|
auto data0 = std::make_unique<DnsSocketData>(/*id=*/0, kT0HostName, kT0Qtype,
|
|
ASYNC, Transport::UDP);
|
|
data0->AddReadError(0, ASYNC);
|
|
AddSocketData(std::move(data0));
|
|
|
|
// Second attempt receives valid response asynchronously.
|
|
auto data1 = std::make_unique<DnsSocketData>(/*id=*/0, kT0HostName, kT0Qtype,
|
|
ASYNC, Transport::UDP);
|
|
data1->AddResponseData(kT0ResponseDatagram, std::size(kT0ResponseDatagram),
|
|
ASYNC);
|
|
AddSocketData(std::move(data1));
|
|
|
|
TransactionHelper helper0(kT0RecordCount);
|
|
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
|
|
/*secure=*/false, 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, 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, std::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, std::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, std::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, std::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, std::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, std::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, std::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,
|
|
std::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,
|
|
std::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,
|
|
std::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,
|
|
std::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,
|
|
std::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,
|
|
std::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,
|
|
std::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 */);
|
|
auto data = std::make_unique<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,
|
|
std::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 */);
|
|
auto data = std::make_unique<DnsSocketData>(
|
|
0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS,
|
|
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128);
|
|
data->AddResponseData(kT0ResponseDatagram, 20, SYNCHRONOUS);
|
|
data->AddResponseData(kT0ResponseDatagram + 20,
|
|
std::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 */);
|
|
auto data = std::make_unique<DnsSocketData>(
|
|
0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS,
|
|
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128);
|
|
data->AddResponseData(kT0ResponseDatagram, 20, ASYNC);
|
|
data->AddResponseData(kT0ResponseDatagram + 20,
|
|
std::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 */);
|
|
auto data = std::make_unique<DnsSocketData>(
|
|
0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS,
|
|
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128);
|
|
data->AddResponseData(kT0ResponseDatagram, std::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 */);
|
|
auto data = std::make_unique<DnsSocketData>(
|
|
0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS,
|
|
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128);
|
|
data->AddResponseData(kT0ResponseDatagram, std::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 */);
|
|
auto data = std::make_unique<DnsSocketData>(
|
|
0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS,
|
|
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128);
|
|
data->AddResponseData(kT0ResponseDatagram, 20, ASYNC);
|
|
data->AddResponseData(kT0ResponseDatagram + 20,
|
|
std::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 */);
|
|
auto data = std::make_unique<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 */);
|
|
auto data = std::make_unique<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 */);
|
|
auto data = std::make_unique<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 */);
|
|
auto data = std::make_unique<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,
|
|
std::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,
|
|
std::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, std::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, std::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,
|
|
std::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, std::size(kOrder0));
|
|
}
|
|
|
|
TEST_F(DnsTransactionTest, HttpsPostFailTwice) {
|
|
config_.attempts = 3;
|
|
ConfigureDohServers(true /* use_post */, 2);
|
|
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
|
|
std::size(kT0ResponseDatagram), SYNCHRONOUS,
|
|
Transport::HTTPS, nullptr /* opt_rdata */,
|
|
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
|
|
false /* enqueue_transaction_id */);
|
|
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
|
|
std::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, std::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,
|
|
std::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, std::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, std::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, std::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,
|
|
std::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.
|
|
auto data = std::make_unique<DnsSocketData>(
|
|
0, kT0HostName, kT0Qtype, ASYNC, Transport::HTTPS,
|
|
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128);
|
|
data->AddReadError(ERR_IO_PENDING, ASYNC);
|
|
data->AddResponseData(kT0ResponseDatagram, std::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() : loop_to_quit_(std::make_unique<base::RunLoop>()) {}
|
|
|
|
void SetCookieCallback(CookieAccessResult result) {
|
|
result_ = result.status.IsInclude();
|
|
loop_to_quit_->Quit();
|
|
}
|
|
|
|
CookieCallback(const CookieCallback&) = delete;
|
|
CookieCallback& operator=(const CookieCallback&) = delete;
|
|
|
|
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_ = std::make_unique<base::RunLoop>(); }
|
|
|
|
void WaitUntilDone() { loop_to_quit_->Run(); }
|
|
|
|
size_t cookie_list_size() { return list_.size(); }
|
|
|
|
private:
|
|
net::CookieList list_;
|
|
bool result_ = false;
|
|
std::unique_ptr<base::RunLoop> loop_to_quit_;
|
|
};
|
|
|
|
TEST_F(DnsTransactionTest, HttpsPostTestNoCookies) {
|
|
ConfigureDohServers(true /* use_post */);
|
|
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
|
|
std::size(kT0ResponseDatagram), SYNCHRONOUS,
|
|
Transport::HTTPS, nullptr /* opt_rdata */,
|
|
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
|
|
false /* enqueue_transaction_id */);
|
|
AddQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram,
|
|
std::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_.doh_config.servers()[0].server_template())),
|
|
CookieOptions::MakeAllInclusive(), CookiePartitionKeyCollection(),
|
|
base::BindOnce(&CookieCallback::GetCookieListCallback,
|
|
base::Unretained(&callback)));
|
|
callback.WaitUntilDone();
|
|
EXPECT_EQ(0u, callback.cookie_list_size());
|
|
callback.Reset();
|
|
GURL cookie_url(GetURLFromTemplateWithoutParameters(
|
|
config_.doh_config.servers()[0].server_template()));
|
|
auto cookie = CanonicalCookie::Create(
|
|
cookie_url, "test-cookie=you-still-fail", base::Time::Now(),
|
|
absl::nullopt /* server_time */,
|
|
absl::nullopt /* cookie_partition_key */);
|
|
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,
|
|
std::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,
|
|
std::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,
|
|
std::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,
|
|
std::size(kT0ResponseDatagram), SYNCHRONOUS,
|
|
Transport::HTTPS, nullptr /* opt_rdata */,
|
|
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
|
|
false /* enqueue_transaction_id */);
|
|
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
|
|
std::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,
|
|
std::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();
|
|
}
|
|
|
|
TEST_F(DnsTransactionTest, HttpsPostLookupWithLog) {
|
|
ConfigureDohServers(true /* use_post */);
|
|
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
|
|
std::size(kT0ResponseDatagram), SYNCHRONOUS,
|
|
Transport::HTTPS, nullptr /* opt_rdata */,
|
|
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
|
|
false /* enqueue_transaction_id */);
|
|
TransactionHelper helper0(kT0RecordCount);
|
|
NetLogCountingObserver observer;
|
|
NetLog::Get()->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(), 18);
|
|
EXPECT_EQ(observer.dict_count(), 9);
|
|
}
|
|
|
|
// 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, std::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, 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, 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, std::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, 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, 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::Milliseconds(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, 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::Milliseconds(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, 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, std::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, 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, 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::Milliseconds(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, 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, 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, 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,
|
|
std::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_UdpRetry_WithLog) {
|
|
AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
|
|
dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
|
|
AddQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram,
|
|
std::size(kT0ResponseDatagram), ASYNC, Transport::TCP);
|
|
|
|
TransactionHelper helper0(kT0RecordCount);
|
|
NetLogCountingObserver observer;
|
|
NetLog::Get()->AddObserver(&observer, NetLogCaptureMode::kEverything);
|
|
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
|
|
false /* secure */, resolve_context_.get());
|
|
helper0.RunUntilComplete();
|
|
EXPECT_EQ(observer.count(), 8);
|
|
EXPECT_EQ(observer.dict_count(), 6);
|
|
}
|
|
|
|
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,
|
|
std::size(kT0ResponseDatagram), ASYNC, Transport::UDP);
|
|
}
|
|
|
|
AddQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram,
|
|
std::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);
|
|
auto data = std::make_unique<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),
|
|
std::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,
|
|
std::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);
|
|
auto data = std::make_unique<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),
|
|
std::size(kT0ResponseDatagram) - 1, 0),
|
|
ASYNC, static_cast<uint16_t>(std::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);
|
|
auto data = std::make_unique<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),
|
|
std::size(kT0ResponseDatagram) - 1, 0),
|
|
SYNCHRONOUS, static_cast<uint16_t>(std::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);
|
|
auto data = std::make_unique<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);
|
|
auto data = std::make_unique<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();
|
|
auto data = std::make_unique<DnsSocketData>(0 /* id */, kT0HostName, kT0Qtype,
|
|
SYNCHRONOUS, Transport::UDP);
|
|
// First attempt gets a mismatched response.
|
|
data->AddResponseData(kT1ResponseDatagram, std::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();
|
|
auto data = std::make_unique<DnsSocketData>(0 /* id */, kT0HostName, kT0Qtype,
|
|
SYNCHRONOUS, Transport::UDP);
|
|
// First attempt gets a mismatched response.
|
|
data->AddResponseData(kT1ResponseDatagram, std::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, std::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.
|
|
auto data = std::make_unique<DnsSocketData>(0 /* id */, kT0HostName, kT0Qtype,
|
|
SYNCHRONOUS, Transport::UDP);
|
|
data->AddResponseData(kT1ResponseDatagram, std::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(DnsTransactionTest, CheckAsync) {
|
|
ConfigureDohServers(false /* use_post */);
|
|
AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram,
|
|
std::size(kT0ResponseDatagram), SYNCHRONOUS,
|
|
Transport::HTTPS, nullptr /* opt_rdata */,
|
|
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
|
|
false /* enqueue_transaction_id */);
|
|
TransactionHelper helper0(kT0RecordCount);
|
|
bool started = false;
|
|
SetUrlRequestStartedCallback(
|
|
base::BindLambdaForTesting([&] { started = true; }));
|
|
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
|
|
true /* secure */, resolve_context_.get());
|
|
EXPECT_FALSE(started);
|
|
EXPECT_FALSE(helper0.has_completed());
|
|
helper0.RunUntilComplete();
|
|
EXPECT_TRUE(started);
|
|
}
|
|
|
|
TEST_F(DnsTransactionTest, EarlyCancel) {
|
|
ConfigureDohServers(false /* use_post */);
|
|
TransactionHelper helper0(0);
|
|
SetUrlRequestStartedCallback(base::BindRepeating([] { FAIL(); }));
|
|
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
|
|
true /* secure */, resolve_context_.get());
|
|
EXPECT_FALSE(helper0.has_completed());
|
|
helper0.Cancel();
|
|
base::RunLoop().RunUntilIdle();
|
|
}
|
|
|
|
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,
|
|
std::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, std::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,
|
|
std::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,
|
|
std::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,
|
|
std::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,
|
|
std::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
|
|
nullptr /* opt_rdata */,
|
|
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
|
|
false /* enqueue_transaction_id */);
|
|
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
|
|
std::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,
|
|
std::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,
|
|
std::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
|
|
nullptr /* opt_rdata */,
|
|
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
|
|
false /* enqueue_transaction_id */);
|
|
|
|
auto request_context2 = CreateTestURLRequestContextBuilder()->Build();
|
|
ResolveContext context2(request_context2.get(), 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, CancelDohProbeOnDestruction) {
|
|
ConfigureDohServers(/*use_post=*/true, /*num_doh_servers=*/1,
|
|
/*make_available=*/false);
|
|
AddQueryAndErrorResponse(/*id=*/0, kT4HostName, kT4Qtype,
|
|
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
|
|
Transport::HTTPS, /*opt_rdata=*/nullptr,
|
|
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
|
|
/*enqueue_transaction_id=*/false);
|
|
AddQueryAndErrorResponse(/*id=*/0, kT4HostName, kT4Qtype,
|
|
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
|
|
Transport::HTTPS, /*opt_rdata=*/nullptr,
|
|
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
|
|
/* enqueue_transaction_id=*/false);
|
|
|
|
std::unique_ptr<DnsProbeRunner> runner =
|
|
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
|
|
runner->Start(/*network_change=*/false);
|
|
|
|
// 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, CancelDohProbeOnContextDestruction) {
|
|
ConfigureDohServers(/*use_post=*/true, /*num_doh_servers=*/1,
|
|
/*make_available=*/false);
|
|
AddQueryAndErrorResponse(/*id=*/0, kT4HostName, kT4Qtype,
|
|
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
|
|
Transport::HTTPS, /*opt_rdata=*/nullptr,
|
|
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
|
|
/*enqueue_transaction_id=*/false);
|
|
AddQueryAndErrorResponse(/*id=*/0, kT4HostName, kT4Qtype,
|
|
ERR_CONNECTION_REFUSED, SYNCHRONOUS,
|
|
Transport::HTTPS, /*opt_rdata=*/nullptr,
|
|
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
|
|
/* enqueue_transaction_id=*/false);
|
|
|
|
std::unique_ptr<DnsProbeRunner> runner =
|
|
transaction_factory_->CreateDohProbeRunner(resolve_context_.get());
|
|
runner->Start(/*network_change=*/false);
|
|
|
|
// 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);
|
|
resolve_context_.reset();
|
|
|
|
// The probe detects that the context no longer exists and stops running.
|
|
FastForwardBy(next_delay);
|
|
|
|
// There are no more probes to run.
|
|
EXPECT_EQ(base::TimeDelta(), runner->GetDelayUntilNextProbeForTest(0));
|
|
}
|
|
|
|
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,
|
|
std::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,
|
|
std::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,
|
|
std::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,
|
|
std::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
|
|
nullptr /* opt_rdata */,
|
|
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
|
|
false /* enqueue_transaction_id */);
|
|
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
|
|
std::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,
|
|
std::size(kT4ResponseDatagram), ASYNC, Transport::HTTPS,
|
|
nullptr /* opt_rdata */,
|
|
DnsQuery::PaddingStrategy::BLOCK_LENGTH_128,
|
|
false /* enqueue_transaction_id */);
|
|
AddQueryAndResponse(0 /* id */, kT4HostName, kT4Qtype, kT4ResponseDatagram,
|
|
std::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()));
|
|
}
|
|
|
|
// Test that queries cannot be sent when they contain a too-long name.
|
|
// Tests against incorrect name length validation, which is anti-pattern #3 from
|
|
// the "NAME:WRECK" report:
|
|
// https://www.forescout.com/company/resources/namewreck-breaking-and-fixing-dns-implementations/
|
|
TEST_F(DnsTransactionTestWithMockTime, RejectsQueryingLongNames) {
|
|
std::string long_dotted_name;
|
|
while (long_dotted_name.size() <= dns_protocol::kMaxNameLength) {
|
|
long_dotted_name.append("naaaaaamelabel.");
|
|
}
|
|
long_dotted_name.append("test");
|
|
|
|
TransactionHelper helper0(ERR_INVALID_ARGUMENT);
|
|
helper0.StartTransaction(transaction_factory_.get(), long_dotted_name.c_str(),
|
|
dns_protocol::kTypeA, false /* secure */,
|
|
resolve_context_.get());
|
|
helper0.RunUntilComplete();
|
|
}
|
|
|
|
// Test that ERR_CONNECTION_REFUSED error after fallback of DnsTCPAttempt
|
|
// should not cause DCHECK failure (https://crbug.com/1334250).
|
|
TEST_F(DnsTransactionTestWithMockTime, TcpConnectionRefusedAfterFallback) {
|
|
ConfigureNumServers(2);
|
|
ConfigureFactory();
|
|
socket_factory_->diverse_source_ports_ = false;
|
|
|
|
// Data for UDP attempts to set `low_entropy` flag.
|
|
for (int i = 0; i <= DnsUdpTracker::kPortReuseThreshold; ++i) {
|
|
AddQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram,
|
|
std::size(kT0ResponseDatagram), ASYNC, Transport::UDP);
|
|
}
|
|
|
|
// Data for TCP attempt.
|
|
auto data1 = std::make_unique<DnsSocketData>(0 /* id */, kT0HostName,
|
|
kT0Qtype, ASYNC, Transport::TCP);
|
|
data1->AddReadError(ERR_IO_PENDING, ASYNC);
|
|
data1->AddReadError(ERR_CONNECTION_REFUSED, ASYNC);
|
|
SequencedSocketData* sequenced_socket_data1 = data1->GetProvider();
|
|
AddSocketData(std::move(data1));
|
|
|
|
auto data2 = std::make_unique<DnsSocketData>(0 /* id */, kT0HostName,
|
|
kT0Qtype, ASYNC, Transport::TCP);
|
|
data2->AddReadError(ERR_IO_PENDING, ASYNC);
|
|
data2->AddResponseData(kT0ResponseDatagram, std::size(kT0ResponseDatagram),
|
|
ASYNC);
|
|
SequencedSocketData* sequenced_socket_data2 = data2->GetProvider();
|
|
AddSocketData(std::move(data2));
|
|
|
|
// DNS transactions for UDP attempts to set `low_entropy` flag.
|
|
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());
|
|
|
|
// DNS transactions for TCP attempt.
|
|
TransactionHelper helper0(kT0RecordCount);
|
|
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
|
|
false /* secure */, resolve_context_.get());
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(helper0.has_completed());
|
|
|
|
base::TimeDelta timeout = resolve_context_->NextClassicFallbackPeriod(
|
|
0 /* classic_server_index */, 0 /* attempt */, session_.get());
|
|
FastForwardBy(timeout);
|
|
|
|
// Resume the first query.
|
|
sequenced_socket_data1->Resume();
|
|
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(helper0.has_completed());
|
|
|
|
// Resume the second query.
|
|
sequenced_socket_data2->Resume();
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_TRUE(helper0.has_completed());
|
|
}
|
|
|
|
// Test that ERR_CONNECTION_REFUSED error after fallback of DnsHTTPAttempt
|
|
// should not cause DCHECK failure (https://crbug.com/1334250).
|
|
TEST_F(DnsTransactionTestWithMockTime, HttpsConnectionRefusedAfterFallback) {
|
|
ConfigureDohServers(false /* use_post */, 2 /* num_doh_servers */,
|
|
true /* make_available */);
|
|
|
|
auto data1 = std::make_unique<DnsSocketData>(
|
|
0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::HTTPS,
|
|
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128);
|
|
data1->AddReadError(ERR_IO_PENDING, ASYNC);
|
|
data1->AddReadError(ERR_CONNECTION_REFUSED, ASYNC);
|
|
SequencedSocketData* sequenced_socket_data1 = data1->GetProvider();
|
|
AddSocketData(std::move(data1), false /* enqueue_transaction_id */);
|
|
|
|
auto data2 = std::make_unique<DnsSocketData>(
|
|
0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::HTTPS,
|
|
nullptr /* opt_rdata */, DnsQuery::PaddingStrategy::BLOCK_LENGTH_128);
|
|
data2->AddReadError(ERR_IO_PENDING, ASYNC);
|
|
data2->AddResponseData(kT0ResponseDatagram, std::size(kT0ResponseDatagram),
|
|
ASYNC);
|
|
SequencedSocketData* sequenced_socket_data2 = data2->GetProvider();
|
|
AddSocketData(std::move(data2), false /* enqueue_transaction_id */);
|
|
|
|
TransactionHelper helper0(kT0RecordCount);
|
|
helper0.StartTransaction(transaction_factory_.get(), kT0HostName, kT0Qtype,
|
|
true /* secure */, resolve_context_.get());
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(helper0.has_completed());
|
|
|
|
base::TimeDelta timeout = resolve_context_->NextDohFallbackPeriod(
|
|
0 /* doh_server_index */, session_.get());
|
|
FastForwardBy(timeout);
|
|
|
|
// Resume the first query.
|
|
sequenced_socket_data1->Resume();
|
|
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(helper0.has_completed());
|
|
|
|
// Resume the second query.
|
|
sequenced_socket_data2->Resume();
|
|
|
|
EXPECT_TRUE(helper0.has_completed());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
} // namespace net
|