| // 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/bind.h" |
| #include "base/containers/circular_deque.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.is_none() && entry.params.is_dict()) |
| 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(); |
| } |
| |
| 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(), 14); |
| EXPECT_EQ(observer.dict_count(), 6); |
| } |
| |
| // 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 |