// 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.PassAs<DatagramClientSocket>(); } 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