summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoragayev@chromium.org <agayev@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-06-30 18:01:29 +0000
committeragayev@chromium.org <agayev@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-06-30 18:01:29 +0000
commitdf8996560ffbb5ed27b34f0722bf4af09de7905e (patch)
tree0d6e61b5c5189168d8b99183c6a08d3c3b69fc65
parentd8a3f7aeed0672875ee0fb93e88b93603dcfd251 (diff)
downloadchromium_src-df8996560ffbb5ed27b34f0722bf4af09de7905e.zip
chromium_src-df8996560ffbb5ed27b34f0722bf4af09de7905e.tar.gz
chromium_src-df8996560ffbb5ed27b34f0722bf4af09de7905e.tar.bz2
Implements DnsTransaction class.
BUG=60149,86937 TEST=net_unittests Review URL: http://codereview.chromium.org/7205009 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@91158 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--net/base/dns_transaction.cc199
-rw-r--r--net/base/dns_transaction.h110
-rw-r--r--net/base/dns_transaction_unittest.cc363
-rw-r--r--net/base/net_error_list.h44
-rw-r--r--net/net.gyp3
-rw-r--r--net/udp/udp_socket_unittest.cc1
6 files changed, 701 insertions, 19 deletions
diff --git a/net/base/dns_transaction.cc b/net/base/dns_transaction.cc
new file mode 100644
index 0000000..7710b96
--- /dev/null
+++ b/net/base/dns_transaction.cc
@@ -0,0 +1,199 @@
+// Copyright (c) 2011 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/base/dns_transaction.h"
+
+#include "base/bind.h"
+#include "base/rand_util.h"
+#include "net/base/completion_callback.h"
+#include "net/base/dns_query.h"
+#include "net/base/dns_response.h"
+#include "net/base/rand_callback.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/udp/datagram_client_socket.h"
+
+namespace net {
+
+namespace {
+
+// Retry timeouts.
+const int kTimeoutsMs[] = {3000, 5000, 11000};
+const int kMaxAttempts = arraysize(kTimeoutsMs);
+
+}
+
+DnsTransaction::DnsTransaction(const IPEndPoint& dns_server,
+ const std::string& dns_name,
+ uint16 query_type,
+ std::vector<IPAddressNumber>* results,
+ const RandIntCallback& rand_int,
+ ClientSocketFactory* socket_factory)
+ : dns_server_(dns_server),
+ results_(results),
+ user_callback_(NULL),
+ query_(new DnsQuery(dns_name, query_type, rand_int)),
+ attempts_(0),
+ next_state_(STATE_NONE),
+ socket_factory_(socket_factory ? socket_factory :
+ ClientSocketFactory::GetDefaultFactory()),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ io_callback_(this, &DnsTransaction::OnIOComplete)) {
+ DCHECK(results);
+ DCHECK(!rand_int.is_null());
+ for (size_t i = 0; i < arraysize(kTimeoutsMs); ++i)
+ timeouts_ms_.push_back(base::TimeDelta::FromMilliseconds(kTimeoutsMs[i]));
+}
+
+DnsTransaction::~DnsTransaction() {
+}
+
+int DnsTransaction::Start(CompletionCallback* callback) {
+ DCHECK(callback);
+ DCHECK_EQ(STATE_NONE, next_state_);
+ next_state_ = STATE_CONNECT;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+int DnsTransaction::DoLoop(int result) {
+ DCHECK_NE(STATE_NONE, next_state_);
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_CONNECT:
+ rv = DoConnect();
+ break;
+ case STATE_CONNECT_COMPLETE:
+ rv = DoConnectComplete(rv);
+ break;
+ case STATE_SEND_QUERY:
+ rv = DoSendQuery();
+ break;
+ case STATE_SEND_QUERY_COMPLETE:
+ rv = DoSendQueryComplete(rv);
+ break;
+ case STATE_READ_RESPONSE:
+ rv = DoReadResponse();
+ break;
+ case STATE_READ_RESPONSE_COMPLETE:
+ rv = DoReadResponseComplete(rv);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ return rv;
+}
+
+void DnsTransaction::DoCallback(int result) {
+ DCHECK_NE(result, ERR_IO_PENDING);
+ DCHECK(user_callback_);
+ CompletionCallback* callback = user_callback_;
+ user_callback_ = NULL;
+ callback->Run(result);
+}
+
+void DnsTransaction::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoCallback(rv);
+}
+
+int DnsTransaction::DoConnect() {
+ next_state_ = STATE_CONNECT_COMPLETE;
+
+ DCHECK_LE(attempts_, timeouts_ms_.size());
+ if (attempts_ == timeouts_ms_.size())
+ return ERR_DNS_TIMED_OUT;
+
+ StartTimer(timeouts_ms_[attempts_]);
+ attempts_++;
+
+ // TODO(agayev): keep all sockets around in case the server responds
+ // after its timeout; state machine will need to change to handle that.
+ socket_.reset(socket_factory_->CreateDatagramClientSocket(
+ DatagramSocket::RANDOM_BIND,
+ base::Bind(&base::RandInt),
+ NULL,
+ NetLog::Source()));
+ return socket_->Connect(dns_server_);
+}
+
+int DnsTransaction::DoConnectComplete(int rv) {
+ if (rv < 0)
+ return rv;
+ next_state_ = STATE_SEND_QUERY;
+ return OK;
+}
+
+int DnsTransaction::DoSendQuery() {
+ next_state_ = STATE_SEND_QUERY_COMPLETE;
+ return socket_->Write(query_->io_buffer(),
+ query_->io_buffer()->size(),
+ &io_callback_);
+}
+
+int DnsTransaction::DoSendQueryComplete(int rv) {
+ if (rv < 0)
+ return rv;
+
+ // Writing to UDP should not result in a partial datagram.
+ if (rv != query_->io_buffer()->size())
+ return ERR_NAME_NOT_RESOLVED;
+
+ next_state_ = STATE_READ_RESPONSE;
+ return OK;
+}
+
+int DnsTransaction::DoReadResponse() {
+ next_state_ = STATE_READ_RESPONSE_COMPLETE;
+ response_.reset(new DnsResponse(query_.get()));
+ return socket_->Read(response_->io_buffer(),
+ response_->io_buffer()->size(),
+ &io_callback_);
+}
+
+int DnsTransaction::DoReadResponseComplete(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ RevokeTimer();
+ if (rv < 0)
+ return rv;
+
+ DCHECK(rv);
+ // TODO(agayev): when supporting EDNS0 we may need to do multiple reads
+ // to read the whole response.
+ return response_->Parse(rv, results_);
+}
+
+void DnsTransaction::StartTimer(base::TimeDelta delay) {
+ timer_.Start(delay, this, &DnsTransaction::OnTimeout);
+}
+
+void DnsTransaction::RevokeTimer() {
+ timer_.Stop();
+}
+
+void DnsTransaction::OnTimeout() {
+ DCHECK(next_state_ == STATE_SEND_QUERY_COMPLETE ||
+ next_state_ == STATE_READ_RESPONSE_COMPLETE);
+ next_state_ = STATE_CONNECT;
+ query_.reset(query_->CloneWithNewId());
+ int rv = DoLoop(OK);
+ if (rv != ERR_IO_PENDING)
+ DoCallback(rv);
+}
+
+void DnsTransaction::set_timeouts_ms(
+ const std::vector<base::TimeDelta>& timeouts_ms) {
+ DCHECK_EQ(0u, attempts_);
+ timeouts_ms_ = timeouts_ms;
+}
+
+} // namespace net
diff --git a/net/base/dns_transaction.h b/net/base/dns_transaction.h
new file mode 100644
index 0000000..920d7ad
--- /dev/null
+++ b/net/base/dns_transaction.h
@@ -0,0 +1,110 @@
+// Copyright (c) 2011 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.
+
+#ifndef NET_BASE_DNS_TRANSACTION_H_
+#define NET_BASE_DNS_TRANSACTION_H_
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "base/scoped_ptr.h"
+#include "base/time.h"
+#include "base/timer.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/completion_callback.h"
+#include "net/base/dns_query.h"
+#include "net/base/dns_response.h"
+#include "net/base/dns_transaction.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+class ClientSocketFactory;
+class DatagramClientSocket;
+
+// Performs (with fixed retries) a single asynchronous DNS transaction,
+// which consists of sending out a DNS query, waiting for response, and
+// parsing and returning the IP addresses that it matches.
+class DnsTransaction : public base::NonThreadSafe {
+ public:
+ // |dns_server| is the address of the DNS server, |dns_name| is the
+ // hostname (in DNS format) to be resolved, |query_type| is the type of
+ // the query, either kDNS_A or kDNS_AAAA, |results| is where the IPs in
+ // the response are stored, |rand_int| is the PRNG used for generating
+ // DNS query IDs.
+ DnsTransaction(const IPEndPoint& dns_server,
+ const std::string& dns_name,
+ uint16 query_type,
+ std::vector<IPAddressNumber>* results,
+ const RandIntCallback& rand_int,
+ ClientSocketFactory* socket_factory);
+ ~DnsTransaction();
+
+ // Starts the resolution process. Will return ERR_IO_PENDING and will
+ // notify the caller via |callback| once the resolution is complete.
+ // Should only be called once.
+ int Start(CompletionCallback* callback);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(DnsTransactionTest, FirstTimeoutTest);
+ FRIEND_TEST_ALL_PREFIXES(DnsTransactionTest, SecondTimeoutTest);
+ FRIEND_TEST_ALL_PREFIXES(DnsTransactionTest, ThirdTimeoutTest);
+
+ enum State {
+ STATE_CONNECT,
+ STATE_CONNECT_COMPLETE,
+ STATE_SEND_QUERY,
+ STATE_SEND_QUERY_COMPLETE,
+ STATE_READ_RESPONSE,
+ STATE_READ_RESPONSE_COMPLETE,
+ STATE_NONE,
+ };
+
+ int DoLoop(int result);
+ void DoCallback(int result);
+ void OnIOComplete(int result);
+
+ int DoConnect();
+ int DoConnectComplete(int result);
+ int DoSendQuery();
+ int DoSendQueryComplete(int result);
+ int DoReadResponse();
+ int DoReadResponseComplete(int result);
+
+ // Fixed number of attempts are made to send a query and read a response,
+ // and at the start of each, a timer is started with increasing delays.
+ void StartTimer(base::TimeDelta delay);
+ void RevokeTimer();
+ void OnTimeout();
+
+ // This is to be used by unit tests only.
+ void set_timeouts_ms(const std::vector<base::TimeDelta>& timeouts_ms);
+
+ const IPEndPoint dns_server_;
+ std::vector<IPAddressNumber>* results_;
+ CompletionCallback* user_callback_;
+
+ scoped_ptr<DnsQuery> query_;
+ scoped_ptr<DnsResponse> response_;
+ scoped_ptr<DatagramClientSocket> socket_;
+
+ // Number of retry attempts so far.
+ size_t attempts_;
+
+ // Timeouts in milliseconds.
+ std::vector<base::TimeDelta> timeouts_ms_;
+
+ State next_state_;
+ ClientSocketFactory* socket_factory_;
+ base::OneShotTimer<DnsTransaction> timer_;
+ CompletionCallbackImpl<DnsTransaction> io_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsTransaction);
+};
+
+} // namespace net
+
+#endif // NET_BASE_DNS_TRANSACTION_H_
diff --git a/net/base/dns_transaction_unittest.cc b/net/base/dns_transaction_unittest.cc
new file mode 100644
index 0000000..f51b1ca
--- /dev/null
+++ b/net/base/dns_transaction_unittest.cc
@@ -0,0 +1,363 @@
+// Copyright (c) 2011 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 <vector>
+
+#include "base/bind.h"
+#include "base/rand_util.h"
+#include "net/base/dns_transaction.h"
+#include "net/base/dns_query.h"
+#include "net/base/dns_util.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_util.h"
+#include "net/base/test_completion_callback.h"
+#include "net/socket/socket_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+namespace {
+
+int TestRng1(int /* min */, int /* max */) { return 1; }
+
+std::vector<IPAddressNumber> CStringsToIPAddressList(
+ const char* const ip_strings[], size_t size) {
+ std::vector<IPAddressNumber> ip_addresses;
+ for (size_t i = 0; i < size; ++i) {
+ IPAddressNumber ip;
+ EXPECT_TRUE(ParseIPLiteralToNumber(ip_strings[i], &ip));
+ ip_addresses.push_back(ip);
+ }
+ return ip_addresses;
+}
+
+IPEndPoint CreateDnsAddress(const char* ip_string, uint16 port) {
+ IPAddressNumber ip_address;
+ DCHECK(ParseIPLiteralToNumber(ip_string, &ip_address));
+ return IPEndPoint(ip_address, port);
+}
+
+static const char* kDnsIp = "192.168.1.1";
+static const uint16 kDnsPort = 53;
+static const base::TimeDelta kTimeoutsMs[] = {
+ base::TimeDelta::FromMilliseconds(20),
+ base::TimeDelta::FromMilliseconds(20),
+ base::TimeDelta::FromMilliseconds(20),
+};
+
+//-----------------------------------------------------------------------------
+// Query/response set for www.google.com, ID is fixed to 1.
+
+static const uint16 kT1Qtype = kDNS_A;
+
+static const char kT1DnsName[] = {
+ 0x03, 'w', 'w', 'w',
+ 0x06, 'g', 'o', 'o', 'g', 'l', 'e',
+ 0x03, 'c', 'o', 'm',
+ 0x00
+};
+
+static const uint8 kT1QueryDatagram[] = {
+ // query for www.google.com, type A.
+ 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77,
+ 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
+ 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01
+};
+
+static const uint8 kT1ResponseDatagram[] = {
+ // response contains one CNAME for www.l.google.com and the following
+ // IP addresses: 74.125.226.{179,180,176,177,178}
+ 0x00, 0x01, 0x81, 0x80, 0x00, 0x01, 0x00, 0x06,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77,
+ 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
+ 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x01,
+ 0x4d, 0x13, 0x00, 0x08, 0x03, 0x77, 0x77, 0x77,
+ 0x01, 0x6c, 0xc0, 0x10, 0xc0, 0x2c, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x04,
+ 0x4a, 0x7d, 0xe2, 0xb3, 0xc0, 0x2c, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x04,
+ 0x4a, 0x7d, 0xe2, 0xb4, 0xc0, 0x2c, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x04,
+ 0x4a, 0x7d, 0xe2, 0xb0, 0xc0, 0x2c, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x04,
+ 0x4a, 0x7d, 0xe2, 0xb1, 0xc0, 0x2c, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x04,
+ 0x4a, 0x7d, 0xe2, 0xb2
+};
+
+static const char* const kT1IpAddresses[] = {
+ "74.125.226.179", "74.125.226.180", "74.125.226.176",
+ "74.125.226.177", "74.125.226.178"
+};
+
+//-----------------------------------------------------------------------------
+// Query/response set for codereview.chromium.org, ID is fixed to 2.
+static const uint16 kT2Qtype = kDNS_A;
+
+static const char kT2DnsName[] = {
+ 0x12, 'c', 'o', 'd', 'e', 'r', 'e', 'v', 'i', 'e', 'w',
+ 0x10, 'c', 'h', 'r', 'o', 'm', 'i', 'u', 'm',
+ 0x03, 'o', 'r', 'g',
+ 0x00
+};
+
+static const uint8 kT2QueryDatagram[] = {
+ // query for codereview.chromium.org, type A.
+ 0x00, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, 0x64,
+ 0x65, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x08,
+ 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x69, 0x75, 0x6d,
+ 0x03, 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00,
+ 0x01
+};
+
+static const uint8 kT2ResponseDatagram[] = {
+ // response contains one CNAME for ghs.l.google.com and the following
+ // IP address: 64.233.169.121
+ 0x00, 0x02, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, 0x64,
+ 0x65, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x08,
+ 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x69, 0x75, 0x6d,
+ 0x03, 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00,
+ 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00,
+ 0x01, 0x41, 0x75, 0x00, 0x12, 0x03, 0x67, 0x68,
+ 0x73, 0x01, 0x6c, 0x06, 0x67, 0x6f, 0x6f, 0x67,
+ 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0xc0,
+ 0x35, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x0b, 0x00, 0x04, 0x40, 0xe9, 0xa9, 0x79
+};
+
+//-----------------------------------------------------------------------------
+
+} // namespace
+
+TEST(DnsTransactionTest, NormalQueryResponseTest1) {
+ MockWrite writes1[] = {
+ MockWrite(true, reinterpret_cast<const char*>(kT1QueryDatagram),
+ arraysize(kT1QueryDatagram))
+ };
+
+ MockRead reads1[] = {
+ MockRead(true, reinterpret_cast<const char*>(kT1ResponseDatagram),
+ arraysize(kT1ResponseDatagram))
+ };
+
+ StaticSocketDataProvider data(reads1, arraysize(reads1),
+ writes1, arraysize(writes1));
+ MockClientSocketFactory factory;
+ factory.AddSocketDataProvider(&data);
+
+ std::vector<IPAddressNumber> actual_ip_addresses;
+ std::vector<IPAddressNumber> expected_ip_addresses =
+ CStringsToIPAddressList(kT1IpAddresses, arraysize(kT1IpAddresses));
+
+ DnsTransaction t(CreateDnsAddress(kDnsIp, kDnsPort),
+ std::string(kT1DnsName, arraysize(kT1DnsName)),
+ kT1Qtype,
+ &actual_ip_addresses,
+ base::Bind(&TestRng1),
+ &factory);
+ TestCompletionCallback callback;
+ int rv = t.Start(&callback);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_TRUE(data.at_read_eof());
+ EXPECT_TRUE(data.at_write_eof());
+
+ EXPECT_TRUE(actual_ip_addresses == expected_ip_addresses);
+}
+
+TEST(DnsTransactionTest, MismatchedQueryResponseTest) {
+ MockWrite writes1[] = {
+ MockWrite(true, reinterpret_cast<const char*>(kT1QueryDatagram),
+ arraysize(kT1QueryDatagram))
+ };
+
+ MockRead reads2[] = {
+ MockRead(true, reinterpret_cast<const char*>(kT2ResponseDatagram),
+ arraysize(kT2ResponseDatagram))
+ };
+
+ StaticSocketDataProvider data(reads2, arraysize(reads2),
+ writes1, arraysize(writes1));
+ MockClientSocketFactory factory;
+ factory.AddSocketDataProvider(&data);
+
+ std::vector<IPAddressNumber> ip_addresses;
+ DnsTransaction t(CreateDnsAddress(kDnsIp, kDnsPort),
+ std::string(kT1DnsName, arraysize(kT1DnsName)),
+ kT1Qtype,
+ &ip_addresses,
+ base::Bind(&TestRng1),
+ &factory);
+ TestCompletionCallback callback;
+ int rv = t.Start(&callback);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_TRUE(data.at_read_eof());
+ EXPECT_TRUE(data.at_write_eof());
+
+ EXPECT_EQ(ERR_DNS_MALFORMED_RESPONSE, rv);
+}
+
+// Test that after the first timeout we do a fresh connection and if we get
+// a response on the new connection, we return it.
+TEST(DnsTransactionTest, FirstTimeoutTest) {
+ MockWrite writes1[] = {
+ MockWrite(true, reinterpret_cast<const char*>(kT1QueryDatagram),
+ arraysize(kT1QueryDatagram))
+ };
+
+ MockRead reads1[] = {
+ MockRead(true, reinterpret_cast<const char*>(kT1ResponseDatagram),
+ arraysize(kT1ResponseDatagram))
+ };
+
+ scoped_refptr<DelayedSocketData> socket1_data(
+ new DelayedSocketData(2, NULL, 0, writes1, arraysize(writes1)));
+ scoped_refptr<DelayedSocketData> socket2_data(
+ new DelayedSocketData(0, reads1, arraysize(reads1),
+ writes1, arraysize(writes1)));
+ MockClientSocketFactory factory;
+ factory.AddSocketDataProvider(socket1_data.get());
+ factory.AddSocketDataProvider(socket2_data.get());
+
+ std::vector<IPAddressNumber> expected_ip_addresses =
+ CStringsToIPAddressList(kT1IpAddresses, arraysize(kT1IpAddresses));
+
+ std::vector<IPAddressNumber> actual_ip_addresses;
+ DnsTransaction transaction(CreateDnsAddress(kDnsIp, kDnsPort),
+ std::string(kT1DnsName, arraysize(kT1DnsName)),
+ kT1Qtype,
+ &actual_ip_addresses,
+ base::Bind(&TestRng1),
+ &factory);
+ transaction.set_timeouts_ms(
+ std::vector<base::TimeDelta>(kTimeoutsMs,
+ kTimeoutsMs + arraysize(kTimeoutsMs)));
+
+ TestCompletionCallback callback;
+ int rv = transaction.Start(&callback);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+
+ EXPECT_TRUE(socket1_data->at_read_eof());
+ EXPECT_TRUE(socket1_data->at_write_eof());
+ EXPECT_TRUE(socket2_data->at_read_eof());
+ EXPECT_TRUE(socket2_data->at_write_eof());
+ EXPECT_EQ(2u, factory.udp_client_sockets().size());
+
+ EXPECT_TRUE(actual_ip_addresses == expected_ip_addresses);
+}
+
+// Test that after the first timeout we do a fresh connection, and after
+// the second timeout we do another fresh connection, and if we get a
+// response on the second connection, we return it.
+TEST(DnsTransactionTest, SecondTimeoutTest) {
+ MockWrite writes1[] = {
+ MockWrite(true, reinterpret_cast<const char*>(kT1QueryDatagram),
+ arraysize(kT1QueryDatagram))
+ };
+
+ MockRead reads1[] = {
+ MockRead(true, reinterpret_cast<const char*>(kT1ResponseDatagram),
+ arraysize(kT1ResponseDatagram))
+ };
+
+ scoped_refptr<DelayedSocketData> socket1_data(
+ new DelayedSocketData(2, NULL, 0, writes1, arraysize(writes1)));
+ scoped_refptr<DelayedSocketData> socket2_data(
+ new DelayedSocketData(2, NULL, 0, writes1, arraysize(writes1)));
+ scoped_refptr<DelayedSocketData> socket3_data(
+ new DelayedSocketData(0, reads1, arraysize(reads1),
+ writes1, arraysize(writes1)));
+ MockClientSocketFactory factory;
+ factory.AddSocketDataProvider(socket1_data.get());
+ factory.AddSocketDataProvider(socket2_data.get());
+ factory.AddSocketDataProvider(socket3_data.get());
+
+ std::vector<IPAddressNumber> expected_ip_addresses =
+ CStringsToIPAddressList(kT1IpAddresses, arraysize(kT1IpAddresses));
+
+ std::vector<IPAddressNumber> actual_ip_addresses;
+ DnsTransaction transaction(CreateDnsAddress(kDnsIp, kDnsPort),
+ std::string(kT1DnsName, arraysize(kT1DnsName)),
+ kT1Qtype,
+ &actual_ip_addresses,
+ base::Bind(&TestRng1),
+ &factory);
+ transaction.set_timeouts_ms(
+ std::vector<base::TimeDelta>(kTimeoutsMs,
+ kTimeoutsMs + arraysize(kTimeoutsMs)));
+
+ TestCompletionCallback callback;
+ int rv = transaction.Start(&callback);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+
+ EXPECT_TRUE(socket1_data->at_read_eof());
+ EXPECT_TRUE(socket1_data->at_write_eof());
+ EXPECT_TRUE(socket2_data->at_read_eof());
+ EXPECT_TRUE(socket2_data->at_write_eof());
+ EXPECT_TRUE(socket3_data->at_read_eof());
+ EXPECT_TRUE(socket3_data->at_write_eof());
+ EXPECT_EQ(3u, factory.udp_client_sockets().size());
+
+ EXPECT_TRUE(actual_ip_addresses == expected_ip_addresses);
+}
+
+// Test that after the first timeout we do a fresh connection, and after
+// the second timeout we do another fresh connection and after the third
+// timeout we give up and return a timeout error.
+TEST(DnsTransactionTest, ThirdTimeoutTest) {
+ MockWrite writes1[] = {
+ MockWrite(true, reinterpret_cast<const char*>(kT1QueryDatagram),
+ arraysize(kT1QueryDatagram))
+ };
+
+ scoped_refptr<DelayedSocketData> socket1_data(
+ new DelayedSocketData(2, NULL, 0, writes1, arraysize(writes1)));
+ scoped_refptr<DelayedSocketData> socket2_data(
+ new DelayedSocketData(2, NULL, 0, writes1, arraysize(writes1)));
+ scoped_refptr<DelayedSocketData> socket3_data(
+ new DelayedSocketData(2, NULL, 0, writes1, arraysize(writes1)));
+ MockClientSocketFactory factory;
+ factory.AddSocketDataProvider(socket1_data.get());
+ factory.AddSocketDataProvider(socket2_data.get());
+ factory.AddSocketDataProvider(socket3_data.get());
+
+ std::vector<IPAddressNumber> expected_ip_addresses =
+ CStringsToIPAddressList(kT1IpAddresses, arraysize(kT1IpAddresses));
+
+ std::vector<IPAddressNumber> actual_ip_addresses;
+ DnsTransaction transaction(CreateDnsAddress(kDnsIp, kDnsPort),
+ std::string(kT1DnsName, arraysize(kT1DnsName)),
+ kT1Qtype,
+ &actual_ip_addresses,
+ base::Bind(&TestRng1),
+ &factory);
+ transaction.set_timeouts_ms(
+ std::vector<base::TimeDelta>(kTimeoutsMs,
+ kTimeoutsMs + arraysize(kTimeoutsMs)));
+
+ TestCompletionCallback callback;
+ int rv = transaction.Start(&callback);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+
+ EXPECT_TRUE(socket1_data->at_read_eof());
+ EXPECT_TRUE(socket1_data->at_write_eof());
+ EXPECT_TRUE(socket2_data->at_read_eof());
+ EXPECT_TRUE(socket2_data->at_write_eof());
+ EXPECT_TRUE(socket3_data->at_read_eof());
+ EXPECT_TRUE(socket3_data->at_write_eof());
+ EXPECT_EQ(3u, factory.udp_client_sockets().size());
+
+ EXPECT_EQ(ERR_DNS_TIMED_OUT, rv);
+}
+
+} // namespace net
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index b6aaa5b..ef44b46 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -17,7 +17,7 @@
// 500-599 ?
// 600-699 FTP errors
// 700-799 Certificate manager errors
-//
+// 800-899 DNS resolver errors
// An asynchronous IO operation is not yet complete. This usually does not
// indicate a fatal error. Typically this error will be generated as a
@@ -231,23 +231,6 @@ NET_ERROR(SSL_CLIENT_AUTH_SIGNATURE_FAILED, -141)
// which exceeds size threshold).
NET_ERROR(MSG_TOO_BIG, -142)
-// DNS resolver received a malformed response.
-NET_ERROR(DNS_MALFORMED_RESPONSE, -143)
-
-// DNS server requires TCP
-NET_ERROR(DNS_SERVER_REQUIRES_TCP, -144)
-
-// DNS server failed. This error is returned for all of the following
-// error conditions:
-// 1 - Format error - The name server was unable to interpret the query.
-// 2 - Server failure - The name server was unable to process this query
-// due to a problem with the name server.
-// 4 - Not Implemented - The name server does not support the requested
-// kind of query.
-// 5 - Refused - The name server refuses to perform the specified
-// operation for policy reasons.
-NET_ERROR(DNS_SERVER_FAILED, -145)
-
// Connection was aborted for switching to another ptotocol.
// WebSocket abort SocketStream connection when alternate protocol is found.
NET_ERROR(PROTOCOL_SWITCHED, -146)
@@ -255,6 +238,9 @@ NET_ERROR(PROTOCOL_SWITCHED, -146)
// Returned when attempting to bind an address that is already in use.
NET_ERROR(ADDRESS_IN_USE, -147)
+// NOTE: error codes 143-145 are available, please use those before adding
+// 148.
+
// Certificate error codes
//
// The values of certificate error codes must be consecutive.
@@ -560,3 +546,25 @@ NET_ERROR(IMPORT_CA_CERT_FAILED, -705)
// Server certificate import failed due to some internal error.
NET_ERROR(IMPORT_SERVER_CERT_FAILED, -706)
+
+// DNS error codes.
+
+// DNS resolver received a malformed response.
+NET_ERROR(DNS_MALFORMED_RESPONSE, -800)
+
+// DNS server requires TCP
+NET_ERROR(DNS_SERVER_REQUIRES_TCP, -801)
+
+// DNS server failed. This error is returned for all of the following
+// error conditions:
+// 1 - Format error - The name server was unable to interpret the query.
+// 2 - Server failure - The name server was unable to process this query
+// due to a problem with the name server.
+// 4 - Not Implemented - The name server does not support the requested
+// kind of query.
+// 5 - Refused - The name server refuses to perform the specified
+// operation for policy reasons.
+NET_ERROR(DNS_SERVER_FAILED, -802)
+
+// DNS transaction timed out.
+NET_ERROR(DNS_TIMED_OUT, -803)
diff --git a/net/net.gyp b/net/net.gyp
index 9134b72..97386b9 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -72,6 +72,8 @@
'base/dns_query.cc',
'base/dns_query.h',
'base/dns_response.cc',
+ 'base/dns_transaction.h',
+ 'base/dns_transaction.cc',
'base/dns_response.h',
'base/dns_reload_timer.cc',
'base/dns_reload_timer.h',
@@ -858,6 +860,7 @@
'base/directory_lister_unittest.cc',
'base/dns_query_unittest.cc',
'base/dns_response_unittest.cc',
+ 'base/dns_transaction_unittest.cc',
'base/dnssec_unittest.cc',
'base/dns_util_unittest.cc',
'base/dnsrr_resolver_unittest.cc',
diff --git a/net/udp/udp_socket_unittest.cc b/net/udp/udp_socket_unittest.cc
index 9fe870f..9df38f6 100644
--- a/net/udp/udp_socket_unittest.cc
+++ b/net/udp/udp_socket_unittest.cc
@@ -7,7 +7,6 @@
#include "base/basictypes.h"
#include "base/bind.h"
-#include "base/callback.h"
#include "base/metrics/histogram.h"
#include "base/stl_util-inl.h"
#include "net/base/io_buffer.h"