| // 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 "base/bind.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/memory/scoped_vector.h" |
| #include "base/rand_util.h" |
| #include "base/sys_byteorder.h" |
| #include "base/test/test_timeouts.h" |
| #include "net/base/dns_util.h" |
| #include "net/base/net_log.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/socket/socket_test_util.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| std::string DomainFromDot(const base::StringPiece& dotted) { |
| std::string out; |
| EXPECT_TRUE(DNSDomainFromDot(dotted, &out)); |
| return out; |
| } |
| |
| // A SocketDataProvider builder. |
| class DnsSocketData { |
| public: |
| // The ctor takes parameters for the DnsQuery. |
| DnsSocketData(uint16 id, |
| const char* dotted_name, |
| uint16 qtype, |
| IoMode mode, |
| bool use_tcp) |
| : query_(new DnsQuery(id, DomainFromDot(dotted_name), qtype)), |
| use_tcp_(use_tcp) { |
| if (use_tcp_) { |
| scoped_ptr<uint16> length(new uint16); |
| *length = base::HostToNet16(query_->io_buffer()->size()); |
| writes_.push_back(MockWrite(mode, |
| reinterpret_cast<const char*>(length.get()), |
| sizeof(uint16))); |
| lengths_.push_back(length.release()); |
| } |
| writes_.push_back(MockWrite(mode, |
| query_->io_buffer()->data(), |
| query_->io_buffer()->size())); |
| } |
| ~DnsSocketData() {} |
| |
| // All responses must be added before GetProvider. |
| |
| // Adds pre-built DnsResponse. |tcp_length| will be used in TCP mode only. |
| void AddResponseWithLength(scoped_ptr<DnsResponse> response, IoMode mode, |
| uint16 tcp_length) { |
| CHECK(!provider_.get()); |
| if (use_tcp_) { |
| scoped_ptr<uint16> length(new uint16); |
| *length = base::HostToNet16(tcp_length); |
| reads_.push_back(MockRead(mode, |
| reinterpret_cast<const char*>(length.get()), |
| sizeof(uint16))); |
| lengths_.push_back(length.release()); |
| } |
| reads_.push_back(MockRead(mode, |
| response->io_buffer()->data(), |
| response->io_buffer()->size())); |
| responses_.push_back(response.release()); |
| } |
| |
| // Adds pre-built DnsResponse. |
| void AddResponse(scoped_ptr<DnsResponse> response, IoMode mode) { |
| uint16 tcp_length = response->io_buffer()->size(); |
| AddResponseWithLength(response.Pass(), mode, tcp_length); |
| } |
| |
| // Adds pre-built response from |data| buffer. |
| void AddResponseData(const uint8* data, size_t length, IoMode mode) { |
| CHECK(!provider_.get()); |
| AddResponse(make_scoped_ptr( |
| new DnsResponse(reinterpret_cast<const char*>(data), length, 0)), mode); |
| } |
| |
| // Add no-answer (RCODE only) response matching the query. |
| void AddRcode(int rcode, IoMode mode) { |
| scoped_ptr<DnsResponse> response( |
| new DnsResponse(query_->io_buffer()->data(), |
| query_->io_buffer()->size(), |
| 0)); |
| dns_protocol::Header* header = |
| reinterpret_cast<dns_protocol::Header*>(response->io_buffer()->data()); |
| header->flags |= base::HostToNet16(dns_protocol::kFlagResponse | rcode); |
| AddResponse(response.Pass(), mode); |
| } |
| |
| // Add error response. |
| void AddReadError(int error, IoMode mode) { |
| reads_.push_back(MockRead(mode, error)); |
| } |
| |
| // Build, if needed, and return the SocketDataProvider. No new responses |
| // should be added afterwards. |
| SocketDataProvider* GetProvider() { |
| if (provider_.get()) |
| return provider_.get(); |
| // Terminate the reads with ERR_IO_PENDING to prevent overrun and default to |
| // timeout. |
| reads_.push_back(MockRead(ASYNC, ERR_IO_PENDING)); |
| provider_.reset(new DelayedSocketData(1, &reads_[0], reads_.size(), |
| &writes_[0], writes_.size())); |
| if (use_tcp_) { |
| provider_->set_connect_data(MockConnect(reads_[0].mode, OK)); |
| } |
| return provider_.get(); |
| } |
| |
| uint16 query_id() const { |
| return query_->id(); |
| } |
| |
| // Returns true if the expected query was written to the socket. |
| bool was_written() const { |
| CHECK(provider_.get()); |
| return provider_->write_index() > 0; |
| } |
| |
| private: |
| scoped_ptr<DnsQuery> query_; |
| bool use_tcp_; |
| ScopedVector<uint16> lengths_; |
| ScopedVector<DnsResponse> responses_; |
| std::vector<MockWrite> writes_; |
| std::vector<MockRead> reads_; |
| scoped_ptr<DelayedSocketData> 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) { |
| } |
| virtual ~FailingUDPClientSocket() {} |
| virtual 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) { |
| } |
| virtual ~TestUDPClientSocket() {} |
| virtual 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) {} |
| virtual ~TestSocketFactory() {} |
| |
| virtual scoped_ptr<DatagramClientSocket> CreateDatagramClientSocket( |
| DatagramSocket::BindType bind_type, |
| const RandIntCallback& rand_int_cb, |
| net::NetLog* net_log, |
| const net::NetLog::Source& source) override { |
| if (fail_next_socket_) { |
| fail_next_socket_ = false; |
| return scoped_ptr<DatagramClientSocket>( |
| new FailingUDPClientSocket(&empty_data_, net_log)); |
| } |
| SocketDataProvider* data_provider = mock_data().GetNext(); |
| scoped_ptr<TestUDPClientSocket> socket( |
| new TestUDPClientSocket(this, data_provider, net_log)); |
| data_provider->set_socket(socket.get()); |
| return socket.Pass(); |
| } |
| |
| void OnConnect(const IPEndPoint& endpoint) { |
| remote_endpoints_.push_back(endpoint); |
| } |
| |
| std::vector<IPEndPoint> 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 qtype, |
| int expected_answer_count) |
| : hostname_(hostname), |
| qtype_(qtype), |
| expected_answer_count_(expected_answer_count), |
| cancel_in_callback_(false), |
| quit_in_callback_(false), |
| completed_(false) { |
| } |
| |
| // Mark that the transaction shall be destroyed immediately upon callback. |
| void set_cancel_in_callback() { |
| cancel_in_callback_ = true; |
| } |
| |
| // Mark to call MessageLoop::Quit() upon callback. |
| void set_quit_in_callback() { |
| quit_in_callback_ = true; |
| } |
| |
| void StartTransaction(DnsTransactionFactory* factory) { |
| EXPECT_EQ(NULL, transaction_.get()); |
| transaction_ = factory->CreateTransaction( |
| hostname_, |
| qtype_, |
| base::Bind(&TransactionHelper::OnTransactionComplete, |
| base::Unretained(this)), |
| BoundNetLog()); |
| 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 (cancel_in_callback_) { |
| Cancel(); |
| return; |
| } |
| |
| // Tell MessageLoop to quit now, in case any ASSERT_* fails. |
| if (quit_in_callback_) |
| base::MessageLoop::current()->Quit(); |
| |
| if (expected_answer_count_ >= 0) { |
| ASSERT_EQ(OK, rv); |
| ASSERT_TRUE(response != NULL); |
| EXPECT_EQ(static_cast<unsigned>(expected_answer_count_), |
| response->answer_count()); |
| EXPECT_EQ(qtype_, response->qtype()); |
| |
| DnsRecordParser parser = response->Parser(); |
| DnsResourceRecord record; |
| for (int i = 0; i < expected_answer_count_; ++i) { |
| EXPECT_TRUE(parser.ReadRecord(&record)); |
| } |
| } else { |
| EXPECT_EQ(expected_answer_count_, rv); |
| } |
| } |
| |
| bool has_completed() const { |
| return completed_; |
| } |
| |
| // Shorthands for commonly used commands. |
| |
| bool Run(DnsTransactionFactory* factory) { |
| StartTransaction(factory); |
| base::MessageLoop::current()->RunUntilIdle(); |
| return has_completed(); |
| } |
| |
| // Use when some of the responses are timeouts. |
| bool RunUntilDone(DnsTransactionFactory* factory) { |
| set_quit_in_callback(); |
| StartTransaction(factory); |
| base::MessageLoop::current()->Run(); |
| return has_completed(); |
| } |
| |
| private: |
| std::string hostname_; |
| uint16 qtype_; |
| scoped_ptr<DnsTransaction> transaction_; |
| int expected_answer_count_; |
| bool cancel_in_callback_; |
| bool quit_in_callback_; |
| |
| bool completed_; |
| }; |
| |
| class DnsTransactionTest : public testing::Test { |
| public: |
| DnsTransactionTest() {} |
| |
| // Generates |nameservers| for DnsConfig. |
| void ConfigureNumServers(unsigned num_servers) { |
| CHECK_LE(num_servers, 255u); |
| config_.nameservers.clear(); |
| IPAddressNumber dns_ip; |
| { |
| bool rv = ParseIPLiteralToNumber("192.168.1.0", &dns_ip); |
| EXPECT_TRUE(rv); |
| } |
| for (unsigned i = 0; i < num_servers; ++i) { |
| dns_ip[3] = i; |
| config_.nameservers.push_back(IPEndPoint(dns_ip, |
| dns_protocol::kDefaultPort)); |
| } |
| } |
| |
| // Called after fully configuring |config|. |
| void ConfigureFactory() { |
| socket_factory_.reset(new TestSocketFactory()); |
| session_ = new DnsSession( |
| config_, |
| DnsSocketPool::CreateNull(socket_factory_.get()), |
| base::Bind(&DnsTransactionTest::GetNextId, base::Unretained(this)), |
| NULL /* NetLog */); |
| transaction_factory_ = DnsTransactionFactory::CreateFactory(session_.get()); |
| } |
| |
| void AddSocketData(scoped_ptr<DnsSocketData> data) { |
| CHECK(socket_factory_.get()); |
| transaction_ids_.push_back(data->query_id()); |
| socket_factory_->AddSocketDataProvider(data->GetProvider()); |
| socket_data_.push_back(data.release()); |
| } |
| |
| // 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 id, |
| const char* dotted_name, |
| uint16 qtype, |
| const uint8* response_data, |
| size_t response_length, |
| IoMode mode, |
| bool use_tcp) { |
| CHECK(socket_factory_.get()); |
| scoped_ptr<DnsSocketData> data( |
| new DnsSocketData(id, dotted_name, qtype, mode, use_tcp)); |
| data->AddResponseData(response_data, response_length, mode); |
| AddSocketData(data.Pass()); |
| } |
| |
| void AddAsyncQueryAndResponse(uint16 id, |
| const char* dotted_name, |
| uint16 qtype, |
| const uint8* data, |
| size_t data_length) { |
| AddQueryAndResponse(id, dotted_name, qtype, data, data_length, ASYNC, |
| false); |
| } |
| |
| void AddSyncQueryAndResponse(uint16 id, |
| const char* dotted_name, |
| uint16 qtype, |
| const uint8* data, |
| size_t data_length) { |
| AddQueryAndResponse(id, dotted_name, qtype, data, data_length, SYNCHRONOUS, |
| false); |
| } |
| |
| // Add expected query of |dotted_name| and |qtype| and no response. |
| void AddQueryAndTimeout(const char* dotted_name, uint16 qtype) { |
| uint16 id = base::RandInt(0, kuint16max); |
| scoped_ptr<DnsSocketData> data( |
| new DnsSocketData(id, dotted_name, qtype, ASYNC, false)); |
| AddSocketData(data.Pass()); |
| } |
| |
| // 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 qtype, |
| int rcode, |
| IoMode mode, |
| bool use_tcp) { |
| CHECK_NE(dns_protocol::kRcodeNOERROR, rcode); |
| uint16 id = base::RandInt(0, kuint16max); |
| scoped_ptr<DnsSocketData> data( |
| new DnsSocketData(id, dotted_name, qtype, mode, use_tcp)); |
| data->AddRcode(rcode, mode); |
| AddSocketData(data.Pass()); |
| } |
| |
| void AddAsyncQueryAndRcode(const char* dotted_name, uint16 qtype, int rcode) { |
| AddQueryAndRcode(dotted_name, qtype, rcode, ASYNC, false); |
| } |
| |
| void AddSyncQueryAndRcode(const char* dotted_name, uint16 qtype, int rcode) { |
| AddQueryAndRcode(dotted_name, qtype, rcode, SYNCHRONOUS, false); |
| } |
| |
| // 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]]); |
| } |
| } |
| |
| virtual void SetUp() override { |
| // By default set one server, |
| ConfigureNumServers(1); |
| // and no retransmissions, |
| config_.attempts = 1; |
| // but long enough timeout for memory tests. |
| config_.timeout = TestTimeouts::action_timeout(); |
| ConfigureFactory(); |
| } |
| |
| virtual 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]->was_written()) << i; |
| } |
| } |
| |
| 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_; |
| |
| ScopedVector<DnsSocketData> socket_data_; |
| |
| std::deque<int> transaction_ids_; |
| scoped_ptr<TestSocketFactory> socket_factory_; |
| scoped_refptr<DnsSession> session_; |
| scoped_ptr<DnsTransactionFactory> transaction_factory_; |
| }; |
| |
| TEST_F(DnsTransactionTest, Lookup) { |
| AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, |
| kT0ResponseDatagram, arraysize(kT0ResponseDatagram)); |
| |
| 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::MessageLoop::current()->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::MessageLoop::current()->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::MessageLoop::current()->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; |
| config_.timeout = TestTimeouts::tiny_timeout(); |
| ConfigureFactory(); |
| |
| // Attempt receives mismatched response followed by valid response. |
| scoped_ptr<DnsSocketData> data( |
| new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, false)); |
| data->AddResponseData(kT1ResponseDatagram, |
| arraysize(kT1ResponseDatagram), SYNCHRONOUS); |
| data->AddResponseData(kT0ResponseDatagram, |
| arraysize(kT0ResponseDatagram), SYNCHRONOUS); |
| AddSocketData(data.Pass()); |
| |
| TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); |
| EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); |
| } |
| |
| TEST_F(DnsTransactionTest, MismatchedResponseAsync) { |
| config_.attempts = 2; |
| config_.timeout = TestTimeouts::tiny_timeout(); |
| ConfigureFactory(); |
| |
| // First attempt receives mismatched response followed by valid response. |
| // Second attempt times out. |
| scoped_ptr<DnsSocketData> data( |
| new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, false)); |
| data->AddResponseData(kT1ResponseDatagram, |
| arraysize(kT1ResponseDatagram), ASYNC); |
| data->AddResponseData(kT0ResponseDatagram, |
| arraysize(kT0ResponseDatagram), ASYNC); |
| AddSocketData(data.Pass()); |
| AddQueryAndTimeout(kT0HostName, kT0Qtype); |
| |
| TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount); |
| EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); |
| } |
| |
| TEST_F(DnsTransactionTest, MismatchedResponseFail) { |
| config_.timeout = TestTimeouts::tiny_timeout(); |
| 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_TRUE(helper0.RunUntilDone(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; |
| // Use short timeout to speed up the test. |
| config_.timeout = TestTimeouts::tiny_timeout(); |
| ConfigureFactory(); |
| |
| AddQueryAndTimeout(kT0HostName, kT0Qtype); |
| AddQueryAndTimeout(kT0HostName, kT0Qtype); |
| AddQueryAndTimeout(kT0HostName, kT0Qtype); |
| |
| TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_TIMED_OUT); |
| EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); |
| EXPECT_TRUE(base::MessageLoop::current()->IsIdleForTesting()); |
| } |
| |
| 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); |
| // Use short timeout to speed up the test. |
| config_.timeout = TestTimeouts::tiny_timeout(); |
| 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_TRUE(helper0.RunUntilDone(transaction_factory_.get())); |
| 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 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, TCPLookup) { |
| AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, |
| dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC); |
| AddQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype, |
| kT0ResponseDatagram, arraysize(kT0ResponseDatagram), |
| ASYNC, true /* use_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, true /* use_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); |
| scoped_ptr<DnsSocketData> data( |
| new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, true)); |
| // 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( |
| make_scoped_ptr( |
| new DnsResponse(reinterpret_cast<const char*>(kT0ResponseDatagram), |
| arraysize(kT0ResponseDatagram), 0)), |
| ASYNC, |
| static_cast<uint16>(kT0QuerySize - 1)); |
| AddSocketData(data.Pass()); |
| |
| TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_MALFORMED_RESPONSE); |
| EXPECT_TRUE(helper0.Run(transaction_factory_.get())); |
| } |
| |
| TEST_F(DnsTransactionTest, TCPTimeout) { |
| config_.timeout = TestTimeouts::tiny_timeout(); |
| ConfigureFactory(); |
| AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, |
| dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC); |
| AddSocketData(make_scoped_ptr( |
| new DnsSocketData(1 /* id */, kT0HostName, kT0Qtype, ASYNC, true))); |
| |
| TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_TIMED_OUT); |
| EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get())); |
| } |
| |
| TEST_F(DnsTransactionTest, TCPReadReturnsZeroAsync) { |
| AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, |
| dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC); |
| scoped_ptr<DnsSocketData> data( |
| new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, true)); |
| // Return all but the last byte of the response. |
| data->AddResponseWithLength( |
| make_scoped_ptr( |
| new DnsResponse(reinterpret_cast<const char*>(kT0ResponseDatagram), |
| arraysize(kT0ResponseDatagram) - 1, 0)), |
| ASYNC, |
| static_cast<uint16>(arraysize(kT0ResponseDatagram))); |
| // Then return a 0-length read. |
| data->AddReadError(0, ASYNC); |
| AddSocketData(data.Pass()); |
| |
| 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); |
| scoped_ptr<DnsSocketData> data( |
| new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, true)); |
| // Return all but the last byte of the response. |
| data->AddResponseWithLength( |
| make_scoped_ptr( |
| new DnsResponse(reinterpret_cast<const char*>(kT0ResponseDatagram), |
| arraysize(kT0ResponseDatagram) - 1, 0)), |
| SYNCHRONOUS, |
| static_cast<uint16>(arraysize(kT0ResponseDatagram))); |
| // Then return a 0-length read. |
| data->AddReadError(0, SYNCHRONOUS); |
| AddSocketData(data.Pass()); |
| |
| 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); |
| scoped_ptr<DnsSocketData> data( |
| new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, true)); |
| data->AddReadError(ERR_CONNECTION_CLOSED, ASYNC); |
| AddSocketData(data.Pass()); |
| |
| 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); |
| scoped_ptr<DnsSocketData> data( |
| new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, true)); |
| data->AddReadError(ERR_CONNECTION_CLOSED, SYNCHRONOUS); |
| AddSocketData(data.Pass()); |
| |
| TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_CONNECTION_CLOSED); |
| EXPECT_TRUE(helper0.Run(transaction_factory_.get())); |
| } |
| |
| TEST_F(DnsTransactionTest, InvalidQuery) { |
| config_.timeout = TestTimeouts::tiny_timeout(); |
| ConfigureFactory(); |
| |
| TransactionHelper helper0(".", dns_protocol::kTypeA, ERR_INVALID_ARGUMENT); |
| EXPECT_TRUE(helper0.Run(transaction_factory_.get())); |
| } |
| |
| } // namespace |
| |
| } // namespace net |