// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/dns/dns_transaction.h" #include #include #include #include "base/base64url.h" #include "base/bind.h" #include "base/containers/circular_deque.h" #include "base/macros.h" #include "base/message_loop/message_loop.h" #include "base/rand_util.h" #include "base/run_loop.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/sys_byteorder.h" #include "base/test/scoped_task_environment.h" #include "base/time/time.h" #include "base/values.h" #include "net/base/ip_address.h" #include "net/base/port_util.h" #include "net/base/upload_bytes_element_reader.h" #include "net/base/url_util.h" #include "net/dns/dns_protocol.h" #include "net/dns/dns_query.h" #include "net/dns/dns_response.h" #include "net/dns/dns_session.h" #include "net/dns/dns_test_util.h" #include "net/dns/dns_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/net_test_suite.h" #include "net/test/url_request/url_request_failed_job.h" #include "net/url_request/url_request_filter.h" #include "net/url_request/url_request_interceptor.h" #include "net/url_request/url_request_test_util.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using net::test::IsOk; namespace net { namespace { base::TimeDelta kTimeout = base::TimeDelta::FromSeconds(1); const char kMockHostname[] = "mock.http"; std::string DomainFromDot(const base::StringPiece& dotted) { std::string out; EXPECT_TRUE(DNSDomainFromDot(dotted, &out)); return out; } enum class Transport { UDP, TCP, HTTPS }; // A SocketDataProvider builder. class DnsSocketData { public: // The ctor takes parameters for the DnsQuery. DnsSocketData(uint16_t id, const char* dotted_name, uint16_t qtype, IoMode mode, Transport transport, const OptRecordRdata* opt_rdata = nullptr) : query_(new DnsQuery(id, DomainFromDot(dotted_name), qtype, opt_rdata)), transport_(transport) { if (Transport::TCP == transport_) { std::unique_ptr length(new uint16_t); *length = base::HostToNet16(query_->io_buffer()->size()); writes_.push_back(MockWrite(mode, reinterpret_cast(length.get()), sizeof(uint16_t), num_reads_and_writes())); lengths_.push_back(std::move(length)); } writes_.push_back(MockWrite(mode, query_->io_buffer()->data(), query_->io_buffer()->size(), num_reads_and_writes())); } ~DnsSocketData() = default; // All responses must be added before GetProvider. // Adds pre-built DnsResponse. |tcp_length| will be used in TCP mode only. void AddResponseWithLength(std::unique_ptr response, IoMode mode, uint16_t tcp_length) { CHECK(!provider_.get()); if (Transport::TCP == transport_) { std::unique_ptr length(new uint16_t); *length = base::HostToNet16(tcp_length); reads_.push_back(MockRead(mode, reinterpret_cast(length.get()), sizeof(uint16_t), num_reads_and_writes())); lengths_.push_back(std::move(length)); } reads_.push_back(MockRead(mode, response->io_buffer()->data(), response->io_buffer_size(), num_reads_and_writes())); responses_.push_back(std::move(response)); } // Adds pre-built DnsResponse. void AddResponse(std::unique_ptr 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( reinterpret_cast(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(reinterpret_cast(data), length - offset, offset), mode); } // Add no-answer (RCODE only) response matching the query. void AddRcode(int rcode, IoMode mode) { std::unique_ptr response(new DnsResponse( query_->io_buffer()->data(), query_->io_buffer()->size(), 0)); dns_protocol::Header* header = reinterpret_cast(response->io_buffer()->data()); header->flags |= base::HostToNet16(dns_protocol::kFlagResponse | rcode); AddResponse(std::move(response), mode); } // Add error response. void AddReadError(int error, IoMode mode) { reads_.push_back(MockRead(mode, error, num_reads_and_writes())); } // Build, if needed, and return the SocketDataProvider. No new responses // should be added afterwards. SequencedSocketData* GetProvider() { if (provider_.get()) return provider_.get(); // Terminate the reads with ERR_IO_PENDING to prevent overrun and default to // timeout. if (transport_ != Transport::HTTPS) { reads_.push_back(MockRead(SYNCHRONOUS, ERR_IO_PENDING, writes_.size() + reads_.size())); } provider_.reset(new SequencedSocketData(&reads_[0], reads_.size(), &writes_[0], writes_.size())); 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 query_; Transport transport_; std::vector> lengths_; std::vector> responses_; std::vector writes_; std::vector reads_; std::unique_ptr provider_; DISALLOW_COPY_AND_ASSIGN(DnsSocketData); }; class TestSocketFactory; // A variant of MockUDPClientSocket which always fails to Connect. class FailingUDPClientSocket : public MockUDPClientSocket { public: FailingUDPClientSocket(SocketDataProvider* data, net::NetLog* net_log) : MockUDPClientSocket(data, net_log) { } ~FailingUDPClientSocket() override = default; int Connect(const IPEndPoint& endpoint) override { return ERR_CONNECTION_REFUSED; } private: DISALLOW_COPY_AND_ASSIGN(FailingUDPClientSocket); }; // A variant of MockUDPClientSocket which notifies the factory OnConnect. class TestUDPClientSocket : public MockUDPClientSocket { public: TestUDPClientSocket(TestSocketFactory* factory, SocketDataProvider* data, net::NetLog* net_log) : MockUDPClientSocket(data, net_log), factory_(factory) { } ~TestUDPClientSocket() override = default; int Connect(const IPEndPoint& endpoint) override; private: TestSocketFactory* factory_; DISALLOW_COPY_AND_ASSIGN(TestUDPClientSocket); }; // Creates TestUDPClientSockets and keeps endpoints reported via OnConnect. class TestSocketFactory : public MockClientSocketFactory { public: TestSocketFactory() : fail_next_socket_(false) {} ~TestSocketFactory() override = default; std::unique_ptr CreateDatagramClientSocket( DatagramSocket::BindType bind_type, NetLog* net_log, const NetLogSource& source) override { if (fail_next_socket_) { fail_next_socket_ = false; return std::unique_ptr( new FailingUDPClientSocket(&empty_data_, net_log)); } SocketDataProvider* data_provider = mock_data().GetNext(); return std::make_unique(this, data_provider, net_log); } void OnConnect(const IPEndPoint& endpoint) { remote_endpoints_.push_back(endpoint); } std::vector remote_endpoints_; bool fail_next_socket_; private: StaticSocketDataProvider empty_data_; DISALLOW_COPY_AND_ASSIGN(TestSocketFactory); }; int TestUDPClientSocket::Connect(const IPEndPoint& endpoint) { factory_->OnConnect(endpoint); return MockUDPClientSocket::Connect(endpoint); } // Helper class that holds a DnsTransaction and handles OnTransactionComplete. class TransactionHelper { public: // If |expected_answer_count| < 0 then it is the expected net error. TransactionHelper(const char* hostname, uint16_t qtype, int expected_answer_count) : hostname_(hostname), qtype_(qtype), expected_answer_count_(expected_answer_count), cancel_in_callback_(false), completed_(false) {} // Mark that the transaction shall be destroyed immediately upon callback. void set_cancel_in_callback() { cancel_in_callback_ = true; } void StartTransaction(DnsTransactionFactory* factory) { EXPECT_EQ(NULL, transaction_.get()); transaction_ = factory->CreateTransaction( hostname_, qtype_, base::Bind(&TransactionHelper::OnTransactionComplete, base::Unretained(this)), NetLogWithSource::Make(&net_log_, net::NetLogSourceType::NONE)); transaction_->SetRequestContext(&request_context_); transaction_->SetRequestPriority(DEFAULT_PRIORITY); EXPECT_EQ(hostname_, transaction_->GetHostname()); EXPECT_EQ(qtype_, transaction_->GetType()); transaction_->Start(); } void Cancel() { ASSERT_TRUE(transaction_.get() != NULL); transaction_.reset(NULL); } void OnTransactionComplete(DnsTransaction* t, int rv, const DnsResponse* response) { EXPECT_FALSE(completed_); EXPECT_EQ(transaction_.get(), t); completed_ = true; if (transaction_complete_run_loop_) transaction_complete_run_loop_->QuitWhenIdle(); if (cancel_in_callback_) { Cancel(); return; } if (expected_answer_count_ >= 0) { ASSERT_THAT(rv, IsOk()); ASSERT_TRUE(response != NULL); EXPECT_EQ(static_cast(expected_answer_count_), response->answer_count()); EXPECT_EQ(qtype_, response->qtype()); DnsRecordParser parser = response->Parser(); DnsResourceRecord record; for (int i = 0; i < expected_answer_count_; ++i) { EXPECT_TRUE(parser.ReadRecord(&record)); } } else { EXPECT_EQ(expected_answer_count_, rv); } } bool has_completed() const { return completed_; } // Shorthands for commonly used commands. bool Run(DnsTransactionFactory* factory) { StartTransaction(factory); base::RunLoop().RunUntilIdle(); return has_completed(); } bool RunUntilDone(DnsTransactionFactory* factory) { DCHECK(!transaction_complete_run_loop_); transaction_complete_run_loop_ = std::make_unique(); StartTransaction(factory); transaction_complete_run_loop_->Run(); transaction_complete_run_loop_.reset(); return has_completed(); } bool FastForwardByTimeout(DnsSession* session, unsigned server_index, int attempt) { NetTestSuite::GetScopedTaskEnvironment()->FastForwardBy( session->NextTimeout(server_index, attempt)); return has_completed(); } bool FastForwardAll() { NetTestSuite::GetScopedTaskEnvironment()->FastForwardUntilNoTasksRemain(); return has_completed(); } TestURLRequestContext* request_context() { return &request_context_; } NetLog* net_log() { return &net_log_; } private: std::string hostname_; uint16_t qtype_; std::unique_ptr transaction_; int expected_answer_count_; bool cancel_in_callback_; TestURLRequestContext request_context_; std::unique_ptr transaction_complete_run_loop_; bool completed_; NetLog net_log_; }; // Callback that allows a test to modify HttpResponseinfo // before the response is sent to the requester. This allows // response headers to be changed. typedef base::RepeatingCallback ResponseModifierCallback; // Callback that allows the test to substiture its own implementation // of URLRequestJob to handle the request. typedef base::RepeatingCallback DohJobMakerCallback; // Subclass of URLRequest 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, NetworkDelegate* network_delegate, SocketDataProvider* data_provider, ResponseModifierCallback response_modifier = ResponseModifierCallback()) : URLRequestJob(request, network_delegate), content_length_(0), leftover_data_len_(0), data_provider_(data_provider), response_modifier_(response_modifier), weak_factory_(this) { 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(), "body", &encoded_query)); EXPECT_GT(encoded_query.size(), 0ul); EXPECT_TRUE(base::Base64UrlDecode( encoded_query, base::Base64UrlDecodePolicy::IGNORE_PADDING, &decoded_query)); } else if (request->method() == "POST") { const UploadDataStream* stream = request->get_upload(); 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 GURL GetMockHttpsUrl(const std::string& path) { return GURL("https://" + (kMockHostname + ("/" + path))); } // URLRequestJob implementation: void Start() override { // Start reading asynchronously so that all error reporting and data // callbacks happen as they would for network requests. base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::Bind(&URLRequestMockDohJob::StartAsync, weak_factory_.GetWeakPtr())); } ~URLRequestMockDohJob() override { if (data_provider_) data_provider_->DetachSocket(); } int ReadRawData(IOBuffer* buf, int buf_size) override { if (!data_provider_) return ERR_FAILED; if (leftover_data_len_ > 0) { int rv = DoBufferCopy(leftover_data_, leftover_data_len_, buf, buf_size); return rv; } if (data_provider_->AllReadDataConsumed()) return 0; MockRead read = data_provider_->OnRead(); if (read.result < ERR_IO_PENDING) return read.result; if (read.result == ERR_IO_PENDING) { pending_buf_ = buf; pending_buf_size_ = buf_size; return ERR_IO_PENDING; } return DoBufferCopy(read.data, read.data_len, buf, buf_size); } void GetResponseInfo(HttpResponseInfo* info) override { // Send back mock headers. std::string raw_headers; raw_headers.append( "HTTP/1.1 200 OK\n" "Content-type: application/dns-udpwireformat\n"); if (content_length_ > 0) { raw_headers.append(base::StringPrintf("Content-Length: %1d\n", static_cast(content_length_))); } info->headers = base::MakeRefCounted(HttpUtil::AssembleRawHeaders( raw_headers.c_str(), static_cast(raw_headers.length()))); if (response_modifier_) response_modifier_.Run(request(), info); } // AsyncSocket implementation: void OnReadComplete(const MockRead& data) override { EXPECT_NE(data.result, ERR_IO_PENDING); if (data.result < 0) return ReadRawDataComplete(data.result); ReadRawDataComplete(DoBufferCopy(data.data, data.data_len, pending_buf_, pending_buf_size_)); } void OnWriteComplete(int rv) override {} void OnConnectComplete(const MockConnect& data) override {} void OnDataProviderDestroyed() override { data_provider_ = nullptr; } private: void StartAsync() { if (!request_) return; if (content_length_) set_expected_content_size(content_length_); NotifyHeadersComplete(); } int DoBufferCopy(const char* data, int data_len, IOBuffer* buf, int buf_size) { if (data_len > buf_size) { memcpy(buf->data(), data, buf_size); leftover_data_ = data + buf_size; leftover_data_len_ = data_len - buf_size; return buf_size; } memcpy(buf->data(), data, data_len); return data_len; } const int content_length_; const char* leftover_data_; int leftover_data_len_; SocketDataProvider* data_provider_; const ResponseModifierCallback response_modifier_; IOBuffer* pending_buf_; int pending_buf_size_; DISALLOW_COPY_AND_ASSIGN(URLRequestMockDohJob); base::WeakPtrFactory weak_factory_; }; class DnsTransactionTest : public testing::Test { public: DnsTransactionTest() = default; // Generates |nameservers| for DnsConfig. void ConfigureNumServers(unsigned num_servers) { CHECK_LE(num_servers, 255u); config_.nameservers.clear(); for (unsigned i = 0; i < num_servers; ++i) { config_.nameservers.push_back( IPEndPoint(IPAddress(192, 168, 1, i), dns_protocol::kDefaultPort)); } } // Generates |nameservers| for DnsConfig. void ConfigureDohServers(unsigned num_servers, bool use_post) { CHECK_LE(num_servers, 255u); for (unsigned i = 0; i < num_servers; ++i) { GURL url(URLRequestMockDohJob::GetMockHttpsUrl( base::StringPrintf("doh_test_%d", i))); config_.dns_over_https_servers.push_back( DnsConfig::DnsOverHttpsServerConfig(url, use_post)); } } // Configures the DnsConfig with one dns over https server, which either // accepts GET or POST requests based on use_post. If |clear_udp| is true, // existing IP name servers are removed from the DnsConfig. If a // ResponseModifierCallback is provided it will be called to contruct the // HTTPResponse. void ConfigDohServers(bool clear_udp, bool use_post, int num_doh_servers = 1) { if (clear_udp) ConfigureNumServers(0); NetTestSuite::SetScopedTaskEnvironment( base::test::ScopedTaskEnvironment::MainThreadType::IO); GURL url(URLRequestMockDohJob::GetMockHttpsUrl("doh_test")); URLRequestFilter* filter = URLRequestFilter::GetInstance(); filter->AddHostnameInterceptor(url.scheme(), url.host(), std::make_unique(this)); ConfigureDohServers(num_doh_servers, use_post); ConfigureFactory(); } URLRequestJob* MaybeInterceptRequest(URLRequest* request, NetworkDelegate* network_delegate) { // If the path indicates a redirct, skip checking the list of // configured servers, because it won't be there and we still want // to handle it. bool server_found = request->url().path() == "/redirect-destination"; for (auto server : config_.dns_over_https_servers) { if (server_found) break; GURL url(request->url()); GURL server_url = server.server; if (url.has_query()) { server_url = GURL(server_url.spec() + "?" + url.query()); } if (server_url == url) { EXPECT_TRUE((server.use_post ? "POST" : "GET") == request->method()); server_found = true; } } EXPECT_TRUE(server_found); HttpRequestHeaders* headers = nullptr; if (request->GetFullRequestHeaders(headers)) { EXPECT_FALSE(headers->HasHeader(HttpRequestHeaders::kCookie)); } EXPECT_FALSE(request->extra_request_headers().HasHeader( HttpRequestHeaders::kCookie)); std::string accept; EXPECT_TRUE(request->extra_request_headers().GetHeader("Accept", &accept)); EXPECT_EQ(accept, "application/dns-udpwireformat"); SocketDataProvider* provider = socket_factory_->mock_data().GetNext(); if (doh_job_maker_) return doh_job_maker_.Run(request, network_delegate, provider); return new URLRequestMockDohJob(request, network_delegate, provider, response_modifier_); } class DohJobInterceptor : public URLRequestInterceptor { public: explicit DohJobInterceptor(DnsTransactionTest* test) : test_(test) {} ~DohJobInterceptor() override {} // URLRequestInterceptor implementation: URLRequestJob* MaybeInterceptRequest( URLRequest* request, NetworkDelegate* network_delegate) const override { return test_->MaybeInterceptRequest(request, network_delegate); } private: DnsTransactionTest* test_; DISALLOW_COPY_AND_ASSIGN(DohJobInterceptor); }; // Called after fully configuring |config|. void ConfigureFactory() { socket_factory_.reset(new TestSocketFactory()); session_ = new DnsSession( config_, DnsSocketPool::CreateNull(socket_factory_.get(), base::Bind(base::RandInt)), base::Bind(&DnsTransactionTest::GetNextId, base::Unretained(this)), NULL /* NetLog */); transaction_factory_ = DnsTransactionFactory::CreateFactory(session_.get()); } void AddSocketData(std::unique_ptr data) { CHECK(socket_factory_.get()); transaction_ids_.push_back(data->query_id()); socket_factory_->AddSocketDataProvider(data->GetProvider()); socket_data_.push_back(std::move(data)); } // Add expected query for |dotted_name| and |qtype| with |id| and response // taken verbatim from |data| of |data_length| bytes. The transaction id in // |data| should equal |id|, unless testing mismatched response. void AddQueryAndResponse(uint16_t id, const char* dotted_name, uint16_t qtype, const uint8_t* response_data, size_t response_length, IoMode mode, Transport transport, const OptRecordRdata* opt_rdata = nullptr) { CHECK(socket_factory_.get()); std::unique_ptr data( new DnsSocketData(id, dotted_name, qtype, mode, transport, opt_rdata)); data->AddResponseData(response_data, response_length, mode); AddSocketData(std::move(data)); } void AddQueryAndErrorResponse(uint16_t id, const char* dotted_name, uint16_t qtype, int error, IoMode mode, Transport transport) { CHECK(socket_factory_.get()); std::unique_ptr data( new DnsSocketData(id, dotted_name, qtype, mode, transport)); data->AddReadError(error, mode); AddSocketData(std::move(data)); } 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 AddQueryAndTimeout(const char* dotted_name, uint16_t qtype) { uint16_t id = base::RandInt(0, std::numeric_limits::max()); std::unique_ptr data( new DnsSocketData(id, dotted_name, qtype, ASYNC, Transport::UDP)); AddSocketData(std::move(data)); } // 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) { CHECK_NE(dns_protocol::kRcodeNOERROR, rcode); uint16_t id = base::RandInt(0, std::numeric_limits::max()); std::unique_ptr data( new DnsSocketData(id, dotted_name, qtype, mode, trans)); data->AddRcode(rcode, mode); AddSocketData(std::move(data)); } 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 unsigned* servers, size_t num_attempts) { ASSERT_EQ(num_attempts, socket_factory_->remote_endpoints_.size()); for (size_t i = 0; i < num_attempts; ++i) { EXPECT_EQ(socket_factory_->remote_endpoints_[i], session_->config().nameservers[servers[i]]); } } void SetUp() override { NetTestSuite::SetScopedTaskEnvironment( base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME); // By default set one server, ConfigureNumServers(1); // and no retransmissions, config_.attempts = 1; // and an arbitrary timeout. config_.timeout = kTimeout; ConfigureFactory(); } void TearDown() override { // Check that all socket data was at least written to. if (base::MessageLoopForIO::IsCurrent()) { URLRequestFilter* filter = URLRequestFilter::GetInstance(); filter->ClearHandlers(); } for (size_t i = 0; i < socket_data_.size(); ++i) { EXPECT_TRUE(socket_data_[i]->GetProvider()->AllWriteDataConsumed()) << i; } NetTestSuite::ResetScopedTaskEnvironment(); } void SetResponseModifierCallback(ResponseModifierCallback response_modifier) { response_modifier_ = response_modifier; } void SetDohJobMakerCallback(DohJobMakerCallback doh_job_maker) { doh_job_maker_ = doh_job_maker; } 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> socket_data_; base::circular_deque transaction_ids_; std::unique_ptr socket_factory_; scoped_refptr session_; std::unique_ptr transaction_factory_; ResponseModifierCallback response_modifier_; DohJobMakerCallback doh_job_maker_; }; TEST_F(DnsTransactionTest, Lookup) { AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram)); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, LookupWithEDNSOption) { OptRecordRdata expected_opt_rdata; const OptRecordRdata::Opt ednsOpt(123, "\xbe\xef"); transaction_factory_->AddEDNSOption(ednsOpt); expected_opt_rdata.AddOpt(ednsOpt); AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), &expected_opt_rdata); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, LookupWithMultipleEDNSOptions) { OptRecordRdata expected_opt_rdata; for (const auto& ednsOpt : { // Two options with the same code, to check that both are included. OptRecordRdata::Opt(1, "\xde\xad"), OptRecordRdata::Opt(1, "\xbe\xef"), // Try a different code and different length of data. OptRecordRdata::Opt(2, "\xff"), }) { transaction_factory_->AddEDNSOption(ednsOpt); expected_opt_rdata.AddOpt(ednsOpt); } AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), &expected_opt_rdata); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } // Concurrent lookup tests assume that DnsTransaction::Start immediately // consumes a socket from ClientSocketFactory. TEST_F(DnsTransactionTest, ConcurrentLookup) { AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram)); AddAsyncQueryAndResponse(1 /* id */, kT1HostName, kT1Qtype, kT1ResponseDatagram, arraysize(kT1ResponseDatagram)); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); helper0.StartTransaction(transaction_factory_.get()); TransactionHelper helper1(kT1HostName, kT1Qtype, kT1RecordCount); helper1.StartTransaction(transaction_factory_.get()); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(helper0.has_completed()); EXPECT_TRUE(helper1.has_completed()); } TEST_F(DnsTransactionTest, CancelLookup) { AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram)); AddAsyncQueryAndResponse(1 /* id */, kT1HostName, kT1Qtype, kT1ResponseDatagram, arraysize(kT1ResponseDatagram)); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); helper0.StartTransaction(transaction_factory_.get()); TransactionHelper helper1(kT1HostName, kT1Qtype, kT1RecordCount); helper1.StartTransaction(transaction_factory_.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, arraysize(kT0ResponseDatagram)); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); helper0.StartTransaction(transaction_factory_.get()); // Destroying the client does not affect running requests. transaction_factory_.reset(NULL); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(helper0.has_completed()); } TEST_F(DnsTransactionTest, CancelFromCallback) { AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram)); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); helper0.set_cancel_in_callback(); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, MismatchedResponseSync) { config_.attempts = 2; ConfigureFactory(); // Attempt receives mismatched response followed by valid response. std::unique_ptr data(new DnsSocketData( 0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::UDP)); data->AddResponseData(kT1ResponseDatagram, arraysize(kT1ResponseDatagram), SYNCHRONOUS); data->AddResponseData(kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, MismatchedResponseAsync) { config_.attempts = 2; ConfigureFactory(); // First attempt receives mismatched response followed by valid response. // Second attempt times out. std::unique_ptr data(new DnsSocketData( 0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::UDP)); data->AddResponseData(kT1ResponseDatagram, arraysize(kT1ResponseDatagram), ASYNC); data->AddResponseData(kT0ResponseDatagram, arraysize(kT0ResponseDatagram), ASYNC); AddSocketData(std::move(data)); AddQueryAndTimeout(kT0HostName, kT0Qtype); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, MismatchedResponseFail) { ConfigureFactory(); // Attempt receives mismatched response but times out because only one attempt // is allowed. AddAsyncQueryAndResponse(1 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram)); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_TIMED_OUT); EXPECT_FALSE(helper0.Run(transaction_factory_.get())); EXPECT_TRUE(helper0.FastForwardByTimeout(session_.get(), 0, 0)); } TEST_F(DnsTransactionTest, MismatchedResponseNxdomain) { config_.attempts = 2; ConfigureFactory(); // First attempt receives mismatched response followed by valid NXDOMAIN // response. // Second attempt receives valid NXDOMAIN response. std::unique_ptr data(new DnsSocketData( 0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::UDP)); data->AddResponseData(kT1ResponseDatagram, arraysize(kT1ResponseDatagram), SYNCHRONOUS); data->AddRcode(dns_protocol::kRcodeNXDOMAIN, ASYNC); AddSocketData(std::move(data)); AddSyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNXDOMAIN); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_NAME_NOT_RESOLVED); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, ServerFail) { AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_SERVER_FAILED); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, NoDomain) { AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNXDOMAIN); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_NAME_NOT_RESOLVED); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, Timeout) { config_.attempts = 3; ConfigureFactory(); AddQueryAndTimeout(kT0HostName, kT0Qtype); AddQueryAndTimeout(kT0HostName, kT0Qtype); AddQueryAndTimeout(kT0HostName, kT0Qtype); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_TIMED_OUT); // Finish when the third attempt times out. EXPECT_FALSE(helper0.Run(transaction_factory_.get())); EXPECT_FALSE(helper0.FastForwardByTimeout(session_.get(), 0, 0)); EXPECT_FALSE(helper0.FastForwardByTimeout(session_.get(), 0, 1)); EXPECT_TRUE(helper0.FastForwardByTimeout(session_.get(), 0, 2)); } TEST_F(DnsTransactionTest, ServerFallbackAndRotate) { // Test that we fallback on both server failure and timeout. config_.attempts = 2; // The next request should start from the next server. config_.rotate = true; ConfigureNumServers(3); ConfigureFactory(); // Responses for first request. AddQueryAndTimeout(kT0HostName, kT0Qtype); AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL); AddQueryAndTimeout(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(kT0HostName, kT0Qtype, ERR_NAME_NOT_RESOLVED); TransactionHelper helper1(kT1HostName, kT1Qtype, ERR_NAME_NOT_RESOLVED); EXPECT_FALSE(helper0.Run(transaction_factory_.get())); EXPECT_TRUE(helper0.FastForwardAll()); EXPECT_TRUE(helper1.Run(transaction_factory_.get())); unsigned kOrder[] = { 0, 1, 2, 0, 1, // The first transaction. 1, 2, 0, // The second transaction starts from the next server. }; CheckServerOrder(kOrder, arraysize(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("x.y.z", dns_protocol::kTypeA, ERR_NAME_NOT_RESOLVED); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); // Also check if suffix search causes server rotation. unsigned kOrder0[] = { 0, 1, 0, 1 }; CheckServerOrder(kOrder0, arraysize(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("x.y", dns_protocol::kTypeA, ERR_NAME_NOT_RESOLVED); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); // A single-label name. TransactionHelper helper1("x", dns_protocol::kTypeA, ERR_NAME_NOT_RESOLVED); EXPECT_TRUE(helper1.Run(transaction_factory_.get())); // A fully-qualified name. TransactionHelper helper2("x.", dns_protocol::kTypeAAAA, ERR_NAME_NOT_RESOLVED); EXPECT_TRUE(helper2.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, EmptySuffixSearch) { // Responses for first transaction. AddAsyncQueryAndRcode("x", dns_protocol::kTypeA, dns_protocol::kRcodeNXDOMAIN); // A fully-qualified name. TransactionHelper helper0("x.", dns_protocol::kTypeA, ERR_NAME_NOT_RESOLVED); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); // A single label name is not even attempted. TransactionHelper helper1("singlelabel", dns_protocol::kTypeA, ERR_DNS_SEARCH_EMPTY); helper1.Run(transaction_factory_.get()); EXPECT_TRUE(helper1.has_completed()); } 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("x.y.z", dns_protocol::kTypeA, ERR_NAME_NOT_RESOLVED); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); TransactionHelper helper1("x.y", dns_protocol::kTypeA, ERR_NAME_NOT_RESOLVED); EXPECT_TRUE(helper1.Run(transaction_factory_.get())); TransactionHelper helper2("x", dns_protocol::kTypeA, ERR_NAME_NOT_RESOLVED); EXPECT_TRUE(helper2.Run(transaction_factory_.get())); } 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, arraysize(kResponseNoData)); TransactionHelper helper0("x.y.z", dns_protocol::kTypeA, 0 /* answers */); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } 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, arraysize(kT0ResponseDatagram)); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } 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, arraysize(kT2ResponseDatagram)); TransactionHelper helper0("www", kT2Qtype, kT2RecordCount); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } 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, arraysize(kT2ResponseDatagram)); TransactionHelper helper0("www", kT2Qtype, kT2RecordCount); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, ConnectFailure) { socket_factory_->fail_next_socket_ = true; transaction_ids_.push_back(0); // Needed to make a DnsUDPAttempt. TransactionHelper helper0("www.chromium.org", dns_protocol::kTypeA, ERR_CONNECTION_REFUSED); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } 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, arraysize(kT0ResponseDatagram)); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsGetLookup) { ConfigDohServers(true /* clear_udp */, false /* use_post */); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsGetFailure) { ConfigDohServers(true /* clear_udp */, false /* use_post */); AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL, SYNCHRONOUS, Transport::HTTPS); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_SERVER_FAILED); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsGetMalformed) { ConfigDohServers(true /* clear_udp */, false /* use_post */); std::unique_ptr data(new DnsSocketData( 0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS)); std::unique_ptr response = std::make_unique( reinterpret_cast(kT0ResponseDatagram), arraysize(kT0ResponseDatagram), 0); // Change the id of the header to make the response malformed. response->io_buffer()->data()[0]++; data->AddResponse(std::move(response), SYNCHRONOUS); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_MALFORMED_RESPONSE); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsPostLookup) { ConfigDohServers(true /* clear_udp */, true /* use_post */); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsPostFailure) { ConfigDohServers(true /* clear_udp */, true /* use_post */); AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL, SYNCHRONOUS, Transport::HTTPS); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_SERVER_FAILED); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsPostMalformed) { ConfigDohServers(true /* clear_udp */, true /* use_post */); std::unique_ptr data(new DnsSocketData( 0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS)); std::unique_ptr response = std::make_unique( reinterpret_cast(kT0ResponseDatagram), arraysize(kT0ResponseDatagram), 0); // Change the id of the header to make the response malformed. response->io_buffer()->data()[0]++; data->AddResponse(std::move(response), SYNCHRONOUS); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_MALFORMED_RESPONSE); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsPostLookupAsync) { ConfigDohServers(true /* clear_udp */, true /* use_post */); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), ASYNC, Transport::HTTPS); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } URLRequestJob* DohJobMakerCallbackFailStart(URLRequest* request, NetworkDelegate* network_delegate, SocketDataProvider* data) { URLRequestMockDohJob::MatchQueryData(request, data); return new URLRequestFailedJob(request, network_delegate, URLRequestFailedJob::START, ERR_FAILED); } TEST_F(DnsTransactionTest, HttpsPostLookupFailStart) { ConfigDohServers(true /* clear_udp */, true /* use_post */); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_FAILED); SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailStart)); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } URLRequestJob* DohJobMakerCallbackFailSync(URLRequest* request, NetworkDelegate* network_delegate, SocketDataProvider* data) { URLRequestMockDohJob::MatchQueryData(request, data); return new URLRequestFailedJob(request, network_delegate, URLRequestFailedJob::READ_SYNC, ERR_FAILED); } TEST_F(DnsTransactionTest, HttpsPostLookupFailSync) { ConfigDohServers(true /* clear_udp */, true /* use_post */); std::unique_ptr data(new DnsSocketData( 0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS)); data->AddResponseWithLength(std::make_unique(), SYNCHRONOUS, 0); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_MALFORMED_RESPONSE); SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailSync)); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } URLRequestJob* DohJobMakerCallbackFailAsync(URLRequest* request, NetworkDelegate* network_delegate, SocketDataProvider* data) { URLRequestMockDohJob::MatchQueryData(request, data); return new URLRequestFailedJob(request, network_delegate, URLRequestFailedJob::READ_ASYNC, ERR_FAILED); } TEST_F(DnsTransactionTest, HttpsPostLookupFailAsync) { ConfigDohServers(true /* clear_udp */, true /* use_post */); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_MALFORMED_RESPONSE); SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailAsync)); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsPostLookup2Sync) { ConfigDohServers(true /* clear_udp */, true /* use_post */); std::unique_ptr data(new DnsSocketData( 0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS)); data->AddResponseData(kT0ResponseDatagram, 20, SYNCHRONOUS); data->AddResponseData(kT0ResponseDatagram + 20, arraysize(kT0ResponseDatagram) - 20, SYNCHRONOUS); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsPostLookup2Async) { ConfigDohServers(true /* clear_udp */, true /* use_post */); std::unique_ptr data(new DnsSocketData( 0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS)); data->AddResponseData(kT0ResponseDatagram, 20, ASYNC); data->AddResponseData(kT0ResponseDatagram + 20, arraysize(kT0ResponseDatagram) - 20, ASYNC); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsPostLookupAsyncWithAsyncZeroRead) { ConfigDohServers(true /* clear_udp */, true /* use_post */); std::unique_ptr data(new DnsSocketData( 0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS)); data->AddResponseData(kT0ResponseDatagram, arraysize(kT0ResponseDatagram), ASYNC); data->AddResponseData(kT0ResponseDatagram, 0, ASYNC); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsPostLookupSyncWithAsyncZeroRead) { ConfigDohServers(true /* clear_udp */, true /* use_post */); std::unique_ptr data(new DnsSocketData( 0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS)); data->AddResponseData(kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS); data->AddResponseData(kT0ResponseDatagram, 0, ASYNC); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsPostLookupAsyncThenSync) { ConfigDohServers(true /* clear_udp */, true /* use_post */); std::unique_ptr data(new DnsSocketData( 0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS)); data->AddResponseData(kT0ResponseDatagram, 20, ASYNC); data->AddResponseData(kT0ResponseDatagram + 20, arraysize(kT0ResponseDatagram) - 20, SYNCHRONOUS); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsPostLookupAsyncThenSyncError) { ConfigDohServers(true /* clear_udp */, true /* use_post */); std::unique_ptr data(new DnsSocketData( 0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS)); data->AddResponseData(kT0ResponseDatagram, 20, ASYNC); data->AddReadError(ERR_FAILED, SYNCHRONOUS); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_FAILED); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsPostLookupAsyncThenAsyncError) { ConfigDohServers(true /* clear_udp */, true /* use_post */); std::unique_ptr data(new DnsSocketData( 0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS)); data->AddResponseData(kT0ResponseDatagram, 20, ASYNC); data->AddReadError(ERR_FAILED, ASYNC); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_FAILED); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsPostLookupSyncThenAsyncError) { ConfigDohServers(true /* clear_udp */, true /* use_post */); std::unique_ptr data(new DnsSocketData( 0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS)); data->AddResponseData(kT0ResponseDatagram, 20, SYNCHRONOUS); data->AddReadError(ERR_FAILED, ASYNC); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_FAILED); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsPostLookupSyncThenSyncError) { ConfigDohServers(true /* clear_udp */, true /* use_post */); std::unique_ptr data(new DnsSocketData( 0, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::HTTPS)); data->AddResponseData(kT0ResponseDatagram, 20, SYNCHRONOUS); data->AddReadError(ERR_FAILED, SYNCHRONOUS); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_FAILED); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsPostFailThenUDPFallback) { config_.attempts = 2; ConfigDohServers(false /* clear_udp */, true /* use_post */); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), ASYNC, Transport::UDP); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailStart)); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsPostFailThenUDPFailThenUDPFallback) { ConfigureNumServers(3); ConfigDohServers(false /* clear_udp */, true /* use_post */); SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailStart)); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); AddQueryAndTimeout(kT0HostName, kT0Qtype); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), ASYNC, Transport::UDP); transaction_ids_.push_back(0); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); // Servers 3 (HTTP) and 0 (UDP) should be marked as bad. 1 and 2 should be // good. EXPECT_EQ(session_->NextGoodServerIndex(0), 1u); EXPECT_EQ(session_->NextGoodServerIndex(1), 1u); EXPECT_EQ(session_->NextGoodServerIndex(2), 2u); } TEST_F(DnsTransactionTest, HttpsMarkUdpBad) { config_.attempts = 1; ConfigureNumServers(2); ConfigDohServers(false /* clear_udp */, true /* use_post */); AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED, SYNCHRONOUS, Transport::HTTPS); AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED, SYNCHRONOUS, Transport::UDP); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), ASYNC, Transport::UDP); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); // Server 0 (UDP) should be marked bad. Server 1 (UDP) should be good // and since 2 is our only Doh server, it will be good. EXPECT_EQ(session_->NextGoodServerIndex(0), 1u); EXPECT_EQ(session_->NextGoodServerIndex(1), 1u); EXPECT_EQ(session_->NextGoodDnsOverHttpsServerIndex(2), 2u); AddQueryAndErrorResponse(1, kT1HostName, kT1Qtype, ERR_CONNECTION_REFUSED, SYNCHRONOUS, Transport::HTTPS); AddQueryAndErrorResponse(1, kT1HostName, kT1Qtype, ERR_CONNECTION_REFUSED, SYNCHRONOUS, Transport::UDP); AddQueryAndResponse(1, kT1HostName, kT1Qtype, kT1ResponseDatagram, arraysize(kT1ResponseDatagram), ASYNC, Transport::UDP); TransactionHelper helper1(kT1HostName, kT1Qtype, kT1RecordCount); EXPECT_TRUE(helper1.RunUntilDone(transaction_factory_.get())); // Since 0 was bad to start, we started with 1 which will now be the // most recent failure, so Server 1 (UDP) should be marked bad. // Server 0 (UDP) should be good and since 2 is our only Doh server. EXPECT_EQ(session_->NextGoodServerIndex(0), 0u); EXPECT_EQ(session_->NextGoodServerIndex(1), 0u); EXPECT_EQ(session_->NextGoodDnsOverHttpsServerIndex(2), 2u); } TEST_F(DnsTransactionTest, HttpsMarkHttpsBad) { config_.attempts = 1; ConfigDohServers(false /* clear_udp */, true /* use_post */, 3); AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED, SYNCHRONOUS, Transport::HTTPS); AddQueryAndErrorResponse(0, kT0HostName, kT0Qtype, ERR_CONNECTION_REFUSED, SYNCHRONOUS, Transport::HTTPS); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), ASYNC, Transport::HTTPS); AddQueryAndErrorResponse(1, kT1HostName, kT1Qtype, ERR_CONNECTION_REFUSED, SYNCHRONOUS, Transport::HTTPS); AddQueryAndErrorResponse(1, kT1HostName, kT1Qtype, ERR_CONNECTION_REFUSED, SYNCHRONOUS, Transport::HTTPS); AddQueryAndResponse(1, kT1HostName, kT1Qtype, kT1ResponseDatagram, arraysize(kT1ResponseDatagram), ASYNC, Transport::HTTPS); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); TransactionHelper helper1(kT1HostName, kT1Qtype, kT1RecordCount); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); // Server 0 is our only UDP server, so it will be good. HTTPS // servers 1 and 2 failed and will be marked bad. Server 3 succeeded // so will be good. EXPECT_EQ(session_->NextGoodServerIndex(0), 0u); EXPECT_EQ(session_->NextGoodDnsOverHttpsServerIndex(1), 3u); EXPECT_EQ(session_->NextGoodDnsOverHttpsServerIndex(2), 3u); EXPECT_EQ(session_->NextGoodDnsOverHttpsServerIndex(3), 3u); EXPECT_TRUE(helper1.RunUntilDone(transaction_factory_.get())); // Server 0 is still our only UDP server, so will be good by definition. // Server 3 started out as good, so was tried first and failed. Server 1 // then had the oldest failure so would be the next good server and // failed so is marked bad. Next attempt was server 2, which succeded so is // good. EXPECT_EQ(session_->NextGoodServerIndex(0), 0u); EXPECT_EQ(session_->NextGoodDnsOverHttpsServerIndex(1), 2u); EXPECT_EQ(session_->NextGoodDnsOverHttpsServerIndex(2), 2u); EXPECT_EQ(session_->NextGoodDnsOverHttpsServerIndex(3), 2u); } TEST_F(DnsTransactionTest, HttpsPostFailThenHTTPFallback) { ConfigDohServers(true /* clear_udp */, true /* use_post */, 2); AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL, ASYNC, Transport::HTTPS); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsPostFailTwiceThenUDPFallback) { config_.attempts = 3; ConfigDohServers(false /* clear_udp */, true /* use_post */, 2); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), ASYNC, Transport::UDP); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailStart)); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsPostFailTwice) { config_.attempts = 2; ConfigDohServers(true /* clear_udp */, true /* use_post */, 2); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_FAILED); SetDohJobMakerCallback(base::BindRepeating(DohJobMakerCallbackFailStart)); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } void MakeResponseWithCookie(URLRequest* request, HttpResponseInfo* info) { info->headers->AddHeader("Set-Cookie: test-cookie=you-fail"); } class CookieCallback { public: CookieCallback() : result_(false), loop_to_quit_(std::make_unique()) {} void SetCookieCallback(bool result) { result_ = result; loop_to_quit_->Quit(); } void GetAllCookiesCallback(const net::CookieList& list) { list_ = list; loop_to_quit_->Quit(); } void Reset() { loop_to_quit_.reset(new base::RunLoop()); } void WaitUntilDone() { loop_to_quit_->Run(); } size_t cookie_list_size() { return list_.size(); } private: net::CookieList list_; bool result_; std::unique_ptr loop_to_quit_; DISALLOW_COPY_AND_ASSIGN(CookieCallback); }; TEST_F(DnsTransactionTest, HttpsPostTestNoCookies) { ConfigDohServers(true /* clear_udp */, true /* use_post */); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); AddQueryAndResponse(1, kT1HostName, kT1Qtype, kT1ResponseDatagram, arraysize(kT1ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); TransactionHelper helper1(kT1HostName, kT1Qtype, kT1RecordCount); SetResponseModifierCallback(base::BindRepeating(MakeResponseWithCookie)); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); CookieCallback callback; helper0.request_context()->cookie_store()->GetAllCookiesForURLAsync( config_.dns_over_https_servers[0].server, base::Bind(&CookieCallback::GetAllCookiesCallback, base::Unretained(&callback))); callback.WaitUntilDone(); EXPECT_EQ(0u, callback.cookie_list_size()); callback.Reset(); net::CookieOptions options; helper1.request_context()->cookie_store()->SetCookieWithOptionsAsync( config_.dns_over_https_servers[0].server, "test-cookie=you-still-fail", options, base::Bind(&CookieCallback::SetCookieCallback, base::Unretained(&callback))); EXPECT_TRUE(helper1.RunUntilDone(transaction_factory_.get())); } void MakeResponseWithoutLength(URLRequest* request, HttpResponseInfo* info) { info->headers->RemoveHeader("Content-Length"); } TEST_F(DnsTransactionTest, HttpsPostNoContentLength) { ConfigDohServers(true /* clear_udp */, true /* use_post */); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); SetResponseModifierCallback(base::BindRepeating(MakeResponseWithoutLength)); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } void MakeResponseWithBadRequestResponse(URLRequest* request, HttpResponseInfo* info) { info->headers->ReplaceStatusLine("HTTP/1.1 400 Bad Request"); } TEST_F(DnsTransactionTest, HttpsPostWithBadRequestResponse) { ConfigDohServers(true /* clear_udp */, true /* use_post */); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_MALFORMED_RESPONSE); SetResponseModifierCallback( base::BindRepeating(MakeResponseWithBadRequestResponse)); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } void MakeResponseWrongType(URLRequest* request, HttpResponseInfo* info) { info->headers->RemoveHeader("Content-Type"); info->headers->AddHeader("Content-Type: text/html"); } TEST_F(DnsTransactionTest, HttpsPostWithWrongType) { ConfigDohServers(true /* clear_udp */, true /* use_post */); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_MALFORMED_RESPONSE); SetResponseModifierCallback(base::BindRepeating(MakeResponseWrongType)); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } 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) { ConfigDohServers(true /* clear_udp */, false /* use_post */); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); SetResponseModifierCallback(base::BindRepeating(MakeResponseRedirect)); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } void MakeResponseNoType(URLRequest* request, HttpResponseInfo* info) { info->headers->RemoveHeader("Content-Type"); } TEST_F(DnsTransactionTest, HttpsPostWithNoType) { ConfigDohServers(true /* clear_udp */, true /* use_post */); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_MALFORMED_RESPONSE); SetResponseModifierCallback(base::BindRepeating(MakeResponseNoType)); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } TEST_F(DnsTransactionTest, HttpsCantLookupDohServers) { ConfigDohServers(true /* clear_udp */, true /* use_post */, 2); TransactionHelper helper0(kMockHostname, kT0Qtype, ERR_CONNECTION_REFUSED); transaction_ids_.push_back(0); transaction_ids_.push_back(1); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); } class CountingObserver : public net::NetLog::ThreadSafeObserver { public: CountingObserver() : count_(0), dict_count_(0) {} ~CountingObserver() override { if (net_log()) net_log()->RemoveObserver(this); } void OnAddEntry(const NetLogEntry& entry) override { ++count_; std::unique_ptr value = entry.ParametersToValue(); if (value && value->is_dict()) dict_count_++; } int count() const { return count_; } int dict_count() const { return dict_count_; } private: int count_; int dict_count_; }; TEST_F(DnsTransactionTest, HttpsPostLookupWithLog) { ConfigDohServers(true /* clear_udp */, true /* use_post */); AddQueryAndResponse(0, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), SYNCHRONOUS, Transport::HTTPS); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); CountingObserver observer; helper0.net_log()->AddObserver(&observer, NetLogCaptureMode::IncludeSocketBytes()); EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); base::RunLoop().RunUntilIdle(); EXPECT_EQ(observer.count(), 5); EXPECT_EQ(observer.dict_count(), 3); } TEST_F(DnsTransactionTest, TCPLookup) { AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC); AddQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, kT0ResponseDatagram, arraysize(kT0ResponseDatagram), ASYNC, Transport::TCP); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, TCPFailure) { AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC); AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL, ASYNC, Transport::TCP); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_SERVER_FAILED); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, TCPMalformed) { AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC); std::unique_ptr data(new DnsSocketData( 0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP)); // Valid response but length too short. // This must be truncated in the question section. The DnsResponse doesn't // examine the answer section until asked to parse it, so truncating it in // the answer section would result in the DnsTransaction itself succeeding. data->AddResponseWithLength( std::make_unique( reinterpret_cast(kT0ResponseDatagram), arraysize(kT0ResponseDatagram), 0), ASYNC, static_cast(kT0QuerySize - 1)); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_MALFORMED_RESPONSE); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, TCPTimeout) { ConfigureFactory(); AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC); AddSocketData(std::make_unique( 1 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP)); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_TIMED_OUT); EXPECT_FALSE(helper0.Run(transaction_factory_.get())); EXPECT_TRUE(helper0.FastForwardAll()); } TEST_F(DnsTransactionTest, TCPReadReturnsZeroAsync) { AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC); std::unique_ptr data(new DnsSocketData( 0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP)); // Return all but the last byte of the response. data->AddResponseWithLength( std::make_unique( reinterpret_cast(kT0ResponseDatagram), arraysize(kT0ResponseDatagram) - 1, 0), ASYNC, static_cast(arraysize(kT0ResponseDatagram))); // Then return a 0-length read. data->AddReadError(0, ASYNC); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_CONNECTION_CLOSED); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, TCPReadReturnsZeroSynchronous) { AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC); std::unique_ptr data(new DnsSocketData( 0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP)); // Return all but the last byte of the response. data->AddResponseWithLength( std::make_unique( reinterpret_cast(kT0ResponseDatagram), arraysize(kT0ResponseDatagram) - 1, 0), SYNCHRONOUS, static_cast(arraysize(kT0ResponseDatagram))); // Then return a 0-length read. data->AddReadError(0, SYNCHRONOUS); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_CONNECTION_CLOSED); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, TCPConnectionClosedAsync) { AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC); std::unique_ptr data(new DnsSocketData( 0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP)); data->AddReadError(ERR_CONNECTION_CLOSED, ASYNC); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_CONNECTION_CLOSED); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, TCPConnectionClosedSynchronous) { AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC); std::unique_ptr data(new DnsSocketData( 0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP)); data->AddReadError(ERR_CONNECTION_CLOSED, SYNCHRONOUS); AddSocketData(std::move(data)); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_CONNECTION_CLOSED); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, MismatchedThenNxdomainThenTCP) { config_.attempts = 2; ConfigureFactory(); std::unique_ptr data(new DnsSocketData( 0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::UDP)); // First attempt gets a mismatched response. data->AddResponseData(kT1ResponseDatagram, arraysize(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); std::unique_ptr tcp_data(new DnsSocketData( 0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP)); tcp_data->AddReadError(ERR_CONNECTION_CLOSED, SYNCHRONOUS); AddSocketData(std::move(tcp_data)); TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_NAME_NOT_RESOLVED); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, MismatchedThenOkThenTCP) { config_.attempts = 2; ConfigureFactory(); std::unique_ptr data(new DnsSocketData( 0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, Transport::UDP)); // First attempt gets a mismatched response. data->AddResponseData(kT1ResponseDatagram, arraysize(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, arraysize(kT0ResponseDatagram)); std::unique_ptr tcp_data(new DnsSocketData( 0 /* id */, kT0HostName, kT0Qtype, ASYNC, Transport::TCP)); tcp_data->AddReadError(ERR_CONNECTION_CLOSED, SYNCHRONOUS); AddSocketData(std::move(tcp_data)); TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); } TEST_F(DnsTransactionTest, InvalidQuery) { ConfigureFactory(); TransactionHelper helper0(".", dns_protocol::kTypeA, ERR_INVALID_ARGUMENT); EXPECT_TRUE(helper0.Run(transaction_factory_.get())); TransactionHelper helper1("foo,bar.com", dns_protocol::kTypeA, ERR_INVALID_ARGUMENT); EXPECT_TRUE(helper1.Run(transaction_factory_.get())); } } // namespace } // namespace net