Index: net/dns/dns_transaction.cc |
diff --git a/net/dns/dns_transaction.cc b/net/dns/dns_transaction.cc |
index a94bba8490e59bc2ebcaa5ffe9be17e955f39def..545cce22a8b7ab610151cc820e90305ae0c6e715 100644 |
--- a/net/dns/dns_transaction.cc |
+++ b/net/dns/dns_transaction.cc |
@@ -21,6 +21,7 @@ |
#include "base/threading/non_thread_safe.h" |
#include "base/timer.h" |
#include "base/values.h" |
+#include "net/base/big_endian.h" |
#include "net/base/completion_callback.h" |
#include "net/base/dns_util.h" |
#include "net/base/io_buffer.h" |
@@ -31,6 +32,7 @@ |
#include "net/dns/dns_query.h" |
#include "net/dns/dns_response.h" |
#include "net/dns/dns_session.h" |
+#include "net/socket/stream_socket.h" |
#include "net/udp/datagram_client_socket.h" |
namespace net { |
@@ -66,56 +68,77 @@ Value* NetLogStartCallback(const std::string* hostname, |
// ---------------------------------------------------------------------------- |
-// A single asynchronous DNS exchange over UDP, which consists of sending out a |
+// A single asynchronous DNS exchange, which consists of sending out a |
// DNS query, waiting for a response, and returning the response that it |
// matches. Logging is done in the socket and in the outer DnsTransaction. |
-class DnsUDPAttempt { |
+class DnsAttempt { |
+ public: |
+ virtual ~DnsAttempt() {} |
+ // Starts the attempt. Returns ERR_IO_PENDING if cannot complete synchronously |
+ // and calls |callback| upon completion. |
+ virtual int Start(const CompletionCallback& callback) = 0; |
+ |
+ // Returns the query of this attempt. |
+ virtual const DnsQuery* GetQuery() const = 0; |
+ |
+ // Returns the response or NULL if has not received a matching response from |
+ // the server. |
+ virtual const DnsResponse* GetResponse() const = 0; |
+ |
+ // Returns the net log bound to the source of the socket. |
+ virtual const BoundNetLog& GetSocketNetLog() const = 0; |
+ |
+ // Returns the index of the destination server within DnsConfig::nameservers. |
+ virtual unsigned GetServerIndex() const = 0; |
+ |
+ // Returns a Value representing the received response, along with a reference |
+ // to the NetLog source source of the UDP socket used. The request must have |
+ // completed before this is called. |
+ Value* NetLogResponseCallback(NetLog::LogLevel log_level) const { |
+ DCHECK(GetResponse()->IsValid()); |
+ |
+ DictionaryValue* dict = new DictionaryValue(); |
+ dict->SetInteger("rcode", GetResponse()->rcode()); |
+ dict->SetInteger("answer_count", GetResponse()->answer_count()); |
+ GetSocketNetLog().source().AddToEventParameters(dict); |
+ return dict; |
+ } |
+}; |
+ |
+class DnsUDPAttempt : public DnsAttempt { |
public: |
DnsUDPAttempt(scoped_ptr<DnsSession::SocketLease> socket_lease, |
- scoped_ptr<DnsQuery> query, |
- const CompletionCallback& callback) |
+ scoped_ptr<DnsQuery> query) |
: next_state_(STATE_NONE), |
received_malformed_response_(false), |
socket_lease_(socket_lease.Pass()), |
- query_(query.Pass()), |
- callback_(callback) { |
+ query_(query.Pass()) { |
} |
- // Starts the attempt. Returns ERR_IO_PENDING if cannot complete synchronously |
- // and calls |callback| upon completion. |
- int Start() { |
+ // DnsAttempt: |
+ virtual int Start(const CompletionCallback& callback) OVERRIDE { |
DCHECK_EQ(STATE_NONE, next_state_); |
+ callback_ = callback; |
start_time_ = base::TimeTicks::Now(); |
next_state_ = STATE_SEND_QUERY; |
return DoLoop(OK); |
} |
- const DnsQuery* query() const { |
+ virtual const DnsQuery* GetQuery() const OVERRIDE { |
return query_.get(); |
} |
- const BoundNetLog& socket_net_log() const { |
- return socket_lease_->socket()->NetLog(); |
- } |
- |
- // Returns the response or NULL if has not received a matching response from |
- // the server. |
- const DnsResponse* response() const { |
+ virtual const DnsResponse* GetResponse() const OVERRIDE { |
const DnsResponse* resp = response_.get(); |
return (resp != NULL && resp->IsValid()) ? resp : NULL; |
} |
- // Returns a Value representing the received response, along with a reference |
- // to the NetLog source source of the UDP socket used. The request must have |
- // completed before this is called. |
- Value* NetLogResponseCallback(NetLog::LogLevel /* log_level */) const { |
- DCHECK(response_->IsValid()); |
+ virtual const BoundNetLog& GetSocketNetLog() const OVERRIDE { |
+ return socket_lease_->socket()->NetLog(); |
+ } |
- DictionaryValue* dict = new DictionaryValue(); |
- dict->SetInteger("rcode", response_->rcode()); |
- dict->SetInteger("answer_count", response_->answer_count()); |
- socket_net_log().source().AddToEventParameters(dict); |
- return dict; |
+ virtual unsigned GetServerIndex() const OVERRIDE { |
+ return socket_lease_->server_index(); |
} |
private: |
@@ -160,6 +183,7 @@ class DnsUDPAttempt { |
if (rv == ERR_IO_PENDING && received_malformed_response_) |
return ERR_DNS_MALFORMED_RESPONSE; |
if (rv == OK) { |
+ DCHECK_EQ(STATE_NONE, next_state_); |
DNS_HISTOGRAM("AsyncDNS.UDPAttemptSuccess", |
base::TimeTicks::Now() - start_time_); |
} else if (rv != ERR_IO_PENDING) { |
@@ -223,7 +247,6 @@ class DnsUDPAttempt { |
if (response_->rcode() != dns_protocol::kRcodeNOERROR) |
return ERR_DNS_SERVER_FAILED; |
- CHECK(response()); |
return OK; |
} |
@@ -247,6 +270,216 @@ class DnsUDPAttempt { |
DISALLOW_COPY_AND_ASSIGN(DnsUDPAttempt); |
}; |
+class DnsTCPAttempt : public DnsAttempt { |
+ public: |
+ DnsTCPAttempt(scoped_ptr<StreamSocket> socket, |
+ scoped_ptr<DnsQuery> query) |
+ : next_state_(STATE_NONE), |
+ socket_(socket.Pass()), |
+ query_(query.Pass()), |
+ length_buffer_(new IOBufferWithSize(sizeof(uint16))), |
+ response_length_(0) { |
+ } |
+ |
+ // DnsAttempt: |
+ virtual int Start(const CompletionCallback& callback) OVERRIDE { |
+ DCHECK_EQ(STATE_NONE, next_state_); |
+ callback_ = callback; |
+ start_time_ = base::TimeTicks::Now(); |
+ next_state_ = STATE_CONNECT_COMPLETE; |
+ int rv = socket_->Connect(base::Bind(&DnsTCPAttempt::OnIOComplete, |
+ base::Unretained(this))); |
+ if (rv == ERR_IO_PENDING) |
+ return rv; |
+ return DoLoop(rv); |
+ } |
+ |
+ virtual const DnsQuery* GetQuery() const OVERRIDE { |
+ return query_.get(); |
+ } |
+ |
+ virtual const DnsResponse* GetResponse() const OVERRIDE { |
+ const DnsResponse* resp = response_.get(); |
+ return (resp != NULL && resp->IsValid()) ? resp : NULL; |
+ } |
+ |
+ virtual const BoundNetLog& GetSocketNetLog() const OVERRIDE { |
+ return socket_->NetLog(); |
+ } |
+ |
+ virtual unsigned GetServerIndex() const OVERRIDE { |
+ NOTREACHED(); |
+ return 0; |
+ } |
+ |
+ private: |
+ enum State { |
+ STATE_CONNECT_COMPLETE, |
+ STATE_SEND_LENGTH, |
+ STATE_SEND_QUERY, |
+ STATE_READ_LENGTH, |
+ STATE_READ_RESPONSE, |
+ STATE_NONE, |
+ }; |
+ |
+ int DoLoop(int result) { |
+ CHECK_NE(STATE_NONE, next_state_); |
+ int rv = result; |
+ do { |
+ State state = next_state_; |
+ next_state_ = STATE_NONE; |
+ switch (state) { |
+ case STATE_CONNECT_COMPLETE: |
+ rv = DoConnectComplete(rv); |
+ break; |
+ case STATE_SEND_LENGTH: |
+ rv = DoSendLength(rv); |
+ break; |
+ case STATE_SEND_QUERY: |
+ rv = DoSendQuery(rv); |
+ break; |
+ case STATE_READ_LENGTH: |
+ rv = DoReadLength(rv); |
+ break; |
+ case STATE_READ_RESPONSE: |
+ rv = DoReadResponse(rv); |
+ break; |
+ default: |
+ NOTREACHED(); |
+ break; |
+ } |
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); |
+ if (rv == OK) { |
+ DCHECK_EQ(STATE_NONE, next_state_); |
+ DNS_HISTOGRAM("AsyncDNS.TCPAttemptSuccess", |
+ base::TimeTicks::Now() - start_time_); |
+ } else if (rv != ERR_IO_PENDING) { |
+ DNS_HISTOGRAM("AsyncDNS.TCPAttemptFail", |
+ base::TimeTicks::Now() - start_time_); |
+ } |
+ return rv; |
+ } |
+ |
+ int DoConnectComplete(int rv) { |
+ DCHECK_NE(ERR_IO_PENDING, rv); |
+ if (rv < 0) |
+ return rv; |
+ |
+ WriteBigEndian<uint16>(length_buffer_->data(), query_->io_buffer()->size()); |
+ buffer_ = new DrainableIOBuffer(length_buffer_, length_buffer_->size()); |
+ next_state_ = STATE_SEND_LENGTH; |
+ return OK; |
+ } |
+ |
+ int DoSendLength(int rv) { |
+ DCHECK_NE(ERR_IO_PENDING, rv); |
+ if (rv < 0) |
+ return rv; |
+ |
+ buffer_->DidConsume(rv); |
+ if (buffer_->BytesRemaining() > 0) { |
+ next_state_ = STATE_SEND_LENGTH; |
+ return socket_->Write(buffer_, |
+ buffer_->BytesRemaining(), |
+ base::Bind(&DnsTCPAttempt::OnIOComplete, |
+ base::Unretained(this))); |
+ } |
+ buffer_ = new DrainableIOBuffer(query_->io_buffer(), |
+ query_->io_buffer()->size()); |
+ next_state_ = STATE_SEND_QUERY; |
+ return OK; |
+ } |
+ |
+ int DoSendQuery(int rv) { |
+ DCHECK_NE(ERR_IO_PENDING, rv); |
+ if (rv < 0) |
+ return rv; |
+ |
+ buffer_->DidConsume(rv); |
+ if (buffer_->BytesRemaining() > 0) { |
+ next_state_ = STATE_SEND_QUERY; |
+ return socket_->Write(buffer_, |
+ buffer_->BytesRemaining(), |
+ base::Bind(&DnsTCPAttempt::OnIOComplete, |
+ base::Unretained(this))); |
+ } |
+ buffer_ = new DrainableIOBuffer(length_buffer_, length_buffer_->size()); |
+ next_state_ = STATE_READ_LENGTH; |
+ return OK; |
+ } |
+ |
+ int DoReadLength(int rv) { |
+ DCHECK_NE(ERR_IO_PENDING, rv); |
+ if (rv < 0) |
+ return rv; |
+ |
+ buffer_->DidConsume(rv); |
+ if (buffer_->BytesRemaining() > 0) { |
+ next_state_ = STATE_READ_LENGTH; |
+ return socket_->Read(buffer_, |
+ buffer_->BytesRemaining(), |
+ base::Bind(&DnsTCPAttempt::OnIOComplete, |
+ base::Unretained(this))); |
+ } |
+ ReadBigEndian<uint16>(length_buffer_->data(), &response_length_); |
+ // Check if advertised response is too short. (Optimization only.) |
+ if (response_length_ < query_->io_buffer()->size()) |
+ return ERR_DNS_MALFORMED_RESPONSE; |
+ // Allocate more space so that DnsResponse::InitParse sanity check passes. |
+ response_.reset(new DnsResponse(response_length_ + 1)); |
+ buffer_ = new DrainableIOBuffer(response_->io_buffer(), response_length_); |
+ next_state_ = STATE_READ_RESPONSE; |
+ return OK; |
+ } |
+ |
+ int DoReadResponse(int rv) { |
+ DCHECK_NE(ERR_IO_PENDING, rv); |
+ if (rv < 0) |
+ return rv; |
+ |
+ buffer_->DidConsume(rv); |
+ if (buffer_->BytesRemaining() > 0) { |
+ next_state_ = STATE_READ_RESPONSE; |
+ return socket_->Read(buffer_, |
+ buffer_->BytesRemaining(), |
+ base::Bind(&DnsTCPAttempt::OnIOComplete, |
+ base::Unretained(this))); |
+ } |
+ if (!response_->InitParse(buffer_->BytesConsumed(), *query_)) |
+ return ERR_DNS_MALFORMED_RESPONSE; |
+ if (response_->flags() & dns_protocol::kFlagTC) |
+ return ERR_UNEXPECTED; |
+ // TODO(szym): Frankly, none of these are expected. |
+ if (response_->rcode() == dns_protocol::kRcodeNXDOMAIN) |
+ return ERR_NAME_NOT_RESOLVED; |
+ if (response_->rcode() != dns_protocol::kRcodeNOERROR) |
+ return ERR_DNS_SERVER_FAILED; |
+ |
+ return OK; |
+ } |
+ |
+ void OnIOComplete(int rv) { |
+ rv = DoLoop(rv); |
+ if (rv != ERR_IO_PENDING) |
+ callback_.Run(rv); |
+ } |
+ |
+ State next_state_; |
+ base::TimeTicks start_time_; |
+ |
+ scoped_ptr<StreamSocket> socket_; |
+ scoped_ptr<DnsQuery> query_; |
+ scoped_refptr<IOBufferWithSize> length_buffer_; |
+ scoped_refptr<DrainableIOBuffer> buffer_; |
+ |
+ uint16 response_length_; |
+ scoped_ptr<DnsResponse> response_; |
+ |
+ CompletionCallback callback_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(DnsTCPAttempt); |
+}; |
+ |
// ---------------------------------------------------------------------------- |
// Implements DnsTransaction. Configuration is supplied by DnsSession. |
@@ -269,6 +502,7 @@ class DnsTransactionImpl : public DnsTransaction, |
qtype_(qtype), |
callback_(callback), |
net_log_(net_log), |
+ had_tcp_attempt_(false), |
first_server_index_(0) { |
DCHECK(session_); |
DCHECK(!hostname_.empty()); |
@@ -321,11 +555,11 @@ class DnsTransactionImpl : public DnsTransaction, |
private: |
// Wrapper for the result of a DnsUDPAttempt. |
struct AttemptResult { |
- AttemptResult(int rv, const DnsUDPAttempt* attempt) |
+ AttemptResult(int rv, const DnsAttempt* attempt) |
: rv(rv), attempt(attempt) {} |
int rv; |
- const DnsUDPAttempt* attempt; |
+ const DnsAttempt* attempt; |
}; |
// Prepares |qnames_| according to the DnsConfig. |
@@ -380,7 +614,7 @@ class DnsTransactionImpl : public DnsTransaction, |
DCHECK(!callback_.is_null()); |
DCHECK_NE(ERR_IO_PENDING, result.rv); |
const DnsResponse* response = result.attempt ? |
- result.attempt->response() : NULL; |
+ result.attempt->GetResponse() : NULL; |
CHECK(result.rv != OK || response != NULL); |
timer_.Stop(); |
@@ -402,7 +636,7 @@ class DnsTransactionImpl : public DnsTransaction, |
if (attempts_.empty()) { |
query.reset(new DnsQuery(id, qnames_.front(), qtype_)); |
} else { |
- query.reset(attempts_[0]->query()->CloneWithNewId(id)); |
+ query.reset(attempts_[0]->GetQuery()->CloneWithNewId(id)); |
} |
const DnsConfig& config = session_->config(); |
@@ -415,12 +649,7 @@ class DnsTransactionImpl : public DnsTransaction, |
bool got_socket = !!lease.get(); |
- DnsUDPAttempt* attempt = new DnsUDPAttempt( |
- lease.Pass(), |
- query.Pass(), |
- base::Bind(&DnsTransactionImpl::OnAttemptComplete, |
- base::Unretained(this), |
- attempt_number)); |
+ DnsUDPAttempt* attempt = new DnsUDPAttempt(lease.Pass(), query.Pass()); |
attempts_.push_back(attempt); |
@@ -429,17 +658,56 @@ class DnsTransactionImpl : public DnsTransaction, |
net_log_.AddEvent( |
NetLog::TYPE_DNS_TRANSACTION_ATTEMPT, |
- attempt->socket_net_log().source().ToEventParametersCallback()); |
+ attempt->GetSocketNetLog().source().ToEventParametersCallback()); |
- int rv = attempt->Start(); |
+ int rv = attempt->Start(base::Bind(&DnsTransactionImpl::OnAttemptComplete, |
+ base::Unretained(this), |
+ attempt_number)); |
if (rv == ERR_IO_PENDING) { |
- timer_.Stop(); |
base::TimeDelta timeout = session_->NextTimeout(attempt_number); |
timer_.Start(FROM_HERE, timeout, this, &DnsTransactionImpl::OnTimeout); |
} |
return AttemptResult(rv, attempt); |
} |
+ AttemptResult MakeTCPAttempt(const DnsAttempt* previous_attempt) { |
+ DCHECK(previous_attempt); |
+ DCHECK(!had_tcp_attempt_); |
+ |
+ scoped_ptr<StreamSocket> socket( |
+ session_->CreateTCPSocket(previous_attempt->GetServerIndex(), |
+ net_log_.source())); |
+ |
+ // TODO(szym): Reuse the same id to help the server? |
+ uint16 id = session_->NextQueryId(); |
+ scoped_ptr<DnsQuery> query( |
+ previous_attempt->GetQuery()->CloneWithNewId(id)); |
+ |
+ // Cancel all other attempts, no point waiting on them. |
+ attempts_.clear(); |
+ |
+ unsigned attempt_number = attempts_.size(); |
+ |
+ DnsTCPAttempt* attempt = new DnsTCPAttempt(socket.Pass(), query.Pass()); |
+ |
+ attempts_.push_back(attempt); |
+ had_tcp_attempt_ = true; |
+ |
+ net_log_.AddEvent( |
+ NetLog::TYPE_DNS_TRANSACTION_TCP_ATTEMPT, |
+ attempt->GetSocketNetLog().source().ToEventParametersCallback()); |
+ |
+ int rv = attempt->Start(base::Bind(&DnsTransactionImpl::OnAttemptComplete, |
+ base::Unretained(this), |
+ attempt_number)); |
+ if (rv == ERR_IO_PENDING) { |
+ // Custom timeout for TCP attempt. |
+ base::TimeDelta timeout = timer_.GetCurrentDelay() * 2; |
+ timer_.Start(FROM_HERE, timeout, this, &DnsTransactionImpl::OnTimeout); |
+ } |
+ return AttemptResult(rv, attempt); |
+ } |
+ |
// Begins query for the current name. Makes the first attempt. |
AttemptResult StartQuery() { |
std::string dotted_qname = DNSDomainToString(qnames_.front()); |
@@ -449,6 +717,7 @@ class DnsTransactionImpl : public DnsTransaction, |
first_server_index_ = session_->NextFirstServerIndex(); |
attempts_.clear(); |
+ had_tcp_attempt_ = false; |
return MakeAttempt(); |
} |
@@ -456,27 +725,29 @@ class DnsTransactionImpl : public DnsTransaction, |
if (callback_.is_null()) |
return; |
DCHECK_LT(attempt_number, attempts_.size()); |
- const DnsUDPAttempt* attempt = attempts_[attempt_number]; |
+ const DnsAttempt* attempt = attempts_[attempt_number]; |
AttemptResult result = ProcessAttemptResult(AttemptResult(rv, attempt)); |
if (result.rv != ERR_IO_PENDING) |
DoCallback(result); |
} |
- void LogResponse(const DnsUDPAttempt* attempt) { |
- if (attempt && attempt->response()) { |
+ void LogResponse(const DnsAttempt* attempt) { |
+ if (attempt && attempt->GetResponse()) { |
net_log_.AddEvent( |
NetLog::TYPE_DNS_TRANSACTION_RESPONSE, |
- base::Bind(&DnsUDPAttempt::NetLogResponseCallback, |
+ base::Bind(&DnsAttempt::NetLogResponseCallback, |
base::Unretained(attempt))); |
} |
} |
bool MoreAttemptsAllowed() const { |
+ if (had_tcp_attempt_) |
+ return false; |
const DnsConfig& config = session_->config(); |
return attempts_.size() < config.attempts * config.nameservers.size(); |
} |
- // Resolves the result of a DnsUDPAttempt until a terminal result is reached |
+ // Resolves the result of a DnsAttempt until a terminal result is reached |
// or it will complete asynchronously (ERR_IO_PENDING). |
AttemptResult ProcessAttemptResult(AttemptResult result) { |
while (result.rv != ERR_IO_PENDING) { |
@@ -487,7 +758,7 @@ class DnsTransactionImpl : public DnsTransaction, |
net_log_.EndEventWithNetErrorCode( |
NetLog::TYPE_DNS_TRANSACTION_QUERY, result.rv); |
DCHECK(result.attempt); |
- DCHECK(result.attempt->response()); |
+ DCHECK(result.attempt->GetResponse()); |
return result; |
case ERR_NAME_NOT_RESOLVED: |
net_log_.EndEventWithNetErrorCode( |
@@ -508,6 +779,9 @@ class DnsTransactionImpl : public DnsTransaction, |
return result; |
} |
break; |
+ case ERR_DNS_SERVER_REQUIRES_TCP: |
+ result = MakeTCPAttempt(result.attempt); |
+ break; |
default: |
// Server failure. |
DCHECK(result.attempt); |
@@ -517,8 +791,10 @@ class DnsTransactionImpl : public DnsTransaction, |
} |
if (MoreAttemptsAllowed()) { |
result = MakeAttempt(); |
- } else if (result.rv == ERR_DNS_MALFORMED_RESPONSE) { |
- // Wait until the last attempt times out. |
+ } else if (result.rv == ERR_DNS_MALFORMED_RESPONSE && |
+ !had_tcp_attempt_) { |
+ // For UDP only, ignore the response and wait until the last attempt |
+ // times out. |
return AttemptResult(ERR_IO_PENDING, NULL); |
} else { |
return AttemptResult(result.rv, NULL); |
@@ -550,7 +826,8 @@ class DnsTransactionImpl : public DnsTransaction, |
std::deque<std::string> qnames_; |
// List of attempts for the current name. |
- ScopedVector<DnsUDPAttempt> attempts_; |
+ ScopedVector<DnsAttempt> attempts_; |
+ bool had_tcp_attempt_; |
// Index of the first server to try on each search query. |
int first_server_index_; |