Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(673)

Unified Diff: net/dns/dns_client.cc

Issue 9190031: DnsClient refactoring + features (timeout, suffix search, server rotation). (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Responded to review. Renamed DnsClient -> DnsTransactionFactory. Completed logging. Created 8 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: net/dns/dns_client.cc
diff --git a/net/dns/dns_client.cc b/net/dns/dns_client.cc
index de60cc33fee98c30b3c7844035b1bbb3443bacf0..fa52745c97b0b6535bdf3c7b1d0fe46d02cc16e0 100644
--- a/net/dns/dns_client.cc
+++ b/net/dns/dns_client.cc
@@ -4,87 +4,522 @@
#include "net/dns/dns_client.h"
+#include <deque>
+#include <string>
+#include <vector>
+
#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop.h"
+#include "base/rand_util.h"
+#include "base/stl_util.h"
#include "base/string_piece.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/timer.h"
+#include "net/base/completion_callback.h"
+#include "net/base/dns_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.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_transaction.h"
#include "net/socket/client_socket_factory.h"
+#include "net/udp/datagram_client_socket.h"
namespace net {
-DnsClient::Request::Request(const base::StringPiece& qname,
- uint16 qtype,
- const RequestCallback& callback)
- : qname_(qname.data(), qname.size()),
- qtype_(qtype),
- callback_(callback) {
+namespace {
+
+// Count labels in the fully-qualified name in DNS format.
+int CountLabels(const std::string& name) {
+ size_t count = 0;
+ for (size_t i = 0; i < name.size() && name[i]; i += name[i] + 1)
+ ++count;
+ return count;
+}
+
+bool IsIPLiteral(const std::string& hostname) {
+ IPAddressNumber ip;
+ return ParseIPLiteralToNumber(hostname, &ip);
}
-DnsClient::Request::~Request() {}
+class StartParameters : public NetLog::EventParameters {
+ public:
+ StartParameters(const std::string& hostname,
+ uint16 qtype,
+ const NetLog::Source& source)
+ : hostname_(hostname), qtype_(qtype), source_(source) {}
+
+ virtual Value* ToValue() const OVERRIDE {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetString("hostname", hostname_);
+ dict->SetInteger("query_type", qtype_);
+ dict->Set("source_dependency", source_.ToValue());
+ return dict;
+ }
+
+ private:
+ const std::string hostname_;
+ const uint16 qtype_;
+ const NetLog::Source source_;
+};
-// Implementation of DnsClient that uses DnsTransaction to serve requests.
-class DnsClientImpl : public DnsClient {
+class ResponseParameters : public NetLog::EventParameters {
public:
- class RequestImpl : public Request {
- public:
- RequestImpl(const base::StringPiece& qname,
- uint16 qtype,
- const RequestCallback& callback,
- DnsSession* session,
- const BoundNetLog& net_log)
- : Request(qname, qtype, callback),
- session_(session),
- net_log_(net_log) {
+ ResponseParameters(int rcode, int answer_count, const NetLog::Source& source)
+ : rcode_(rcode), answer_count_(answer_count), source_(source) {}
+
+ virtual Value* ToValue() const OVERRIDE {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetInteger("rcode", rcode_);
+ dict->SetInteger("answer_count", answer_count_);
+ dict->Set("socket_source", source_.ToValue());
+ return dict;
+ }
+
+ private:
+ const int rcode_;
+ const int answer_count_;
+ const NetLog::Source source_;
+};
+
+// ----------------------------------------------------------------------------
+
+// A single asynchronous DNS exchange over UDP, 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 {
+ public:
+ DnsUDPAttempt(scoped_ptr<DatagramClientSocket> socket,
+ const IPEndPoint& server,
+ scoped_ptr<DnsQuery> query,
+ const CompletionCallback& callback)
+ : next_state_(STATE_NONE),
+ socket_(socket.Pass()),
+ server_(server),
+ query_(query.Pass()),
+ callback_(callback) {
+ }
+
+ // Starts the attempt. Returns ERR_IO_PENDING if cannot complete synchronously
+ // and calls |callback| upon completion.
+ int Start() {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ next_state_ = STATE_CONNECT;
+ return DoLoop(OK);
+ }
+
+ const DnsQuery* query() const {
+ return query_.get();
+ }
+
+ const DatagramClientSocket* socket() const {
+ return socket_.get();
+ }
+
+ // Returns the response or NULL if has not received a matching response from
+ // the server.
+ const DnsResponse* response() const {
+ const DnsResponse* resp = response_.get();
+ return (resp != NULL && resp->is_valid()) ? resp : NULL;
+ }
+
+ private:
+ enum State {
+ STATE_CONNECT,
+ STATE_SEND_QUERY,
+ STATE_SEND_QUERY_COMPLETE,
+ STATE_READ_RESPONSE,
+ STATE_READ_RESPONSE_COMPLETE,
+ STATE_NONE,
+ };
+
+ int 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_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;
+ }
+
+ int DoConnect() {
+ next_state_ = STATE_SEND_QUERY;
+ return socket_->Connect(server_);
+ }
+
+ int DoSendQuery() {
+ next_state_ = STATE_SEND_QUERY_COMPLETE;
+ return socket_->Write(query_->io_buffer(),
+ query_->io_buffer()->size(),
+ base::Bind(&DnsUDPAttempt::OnIOComplete,
+ base::Unretained(this)));
+ }
+
+ int 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_MSG_TOO_BIG;
+
+ next_state_ = STATE_READ_RESPONSE;
+ return OK;
+ }
+
+ int DoReadResponse() {
+ next_state_ = STATE_READ_RESPONSE_COMPLETE;
+ response_.reset(new DnsResponse());
+ return socket_->Read(response_->io_buffer(),
+ response_->io_buffer()->size(),
+ base::Bind(&DnsUDPAttempt::OnIOComplete,
+ base::Unretained(this)));
+ }
+
+ int DoReadResponseComplete(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ if (rv < 0)
+ return rv;
+
+ DCHECK(rv);
+ if (!response_->InitParse(rv, *query_))
+ return ERR_DNS_MALFORMED_RESPONSE;
+ if (response_->flags() & dns_protocol::kFlagTC)
+ return ERR_DNS_SERVER_REQUIRES_TCP;
+ if (response_->rcode() != dns_protocol::kRcodeNOERROR &&
+ response_->rcode() != dns_protocol::kRcodeNXDOMAIN) {
+ return ERR_DNS_SERVER_FAILED;
}
+ if (response_->answer_count() == 0)
+ return ERR_NAME_NOT_RESOLVED;
+
+ return OK;
+ }
+
+ void OnIOComplete(int rv) {
+ rv = DoLoop(rv);
+ if (rv != ERR_IO_PENDING)
+ callback_.Run(rv);
+ }
+
+ State next_state_;
+
+ scoped_ptr<DatagramClientSocket> socket_;
+ IPEndPoint server_;
+ scoped_ptr<DnsQuery> query_;
+
+ scoped_ptr<DnsResponse> response_;
+
+ CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsUDPAttempt);
+};
+
+// ----------------------------------------------------------------------------
+
+// Implements DnsTransaction. Configuration is supplied by DnsSession.
+// The suffix list is built according to the DnsConfig from the session.
+// The timeout for each DnsUDPAttempt is given by DnsSession::NextTimeout.
+// The first server to attempt on each query is given by
+// DnsSession::NextFirstServerIndex, and the order is round-robin afterwards.
+// Each server is attempted DnsConfig::attempts times.
+class DnsTransactionImpl : public DnsTransaction, public base::NonThreadSafe {
+ public:
+ DnsTransactionImpl(DnsSession* session,
+ const std::string& hostname,
+ uint16 qtype,
+ const DnsTransactionFactory::CallbackType& callback,
+ const BoundNetLog& source_net_log)
+ : session_(session),
+ hostname_(hostname),
+ qtype_(qtype),
+ callback_(callback),
+ net_log_(BoundNetLog::Make(session->net_log(),
+ NetLog::SOURCE_DNS_TRANSACTION)),
+ successful_attempt_(NULL),
+ first_server_index_(0) {
+ DCHECK(session_);
+ DCHECK(!hostname_.empty());
+ DCHECK(!callback_.is_null());
+
+ DCHECK(!IsIPLiteral(hostname_));
- virtual int Start() OVERRIDE {
- transaction_.reset(new DnsTransaction(
- session_,
- qname(),
- qtype(),
- base::Bind(&RequestImpl::OnComplete, base::Unretained(this)),
- net_log_));
- return transaction_->Start();
+ net_log_.BeginEvent(NetLog::TYPE_DNS_TRANSACTION, make_scoped_refptr(
+ new StartParameters(hostname_, qtype_, source_net_log.source())));
+ }
+
+ virtual ~DnsTransactionImpl() {
+ STLDeleteElements(&attempts_);
+ if (!callback_.is_null()) {
+ net_log_.AddEvent(NetLog::TYPE_CANCELLED, NULL);
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_DNS_TRANSACTION,
+ ERR_ABORTED);
}
+ }
+
+ virtual const std::string& GetHostname() const OVERRIDE {
+ DCHECK(CalledOnValidThread());
+ return hostname_;
+ }
+
+ virtual uint16 GetType() const OVERRIDE {
+ DCHECK(CalledOnValidThread());
+ return qtype_;
+ }
- void OnComplete(DnsTransaction* transaction, int rv) {
- DCHECK_EQ(transaction_.get(), transaction);
- // TODO(szym):
- // - handle retransmissions here instead of DnsTransaction
- // - handle rcode and flags here instead of DnsTransaction
- // - update RTT in DnsSession
- // - perform suffix search
- // - handle DNS over TCP
- DoCallback(rv, (rv == OK) ? transaction->response() : NULL);
+ virtual int Start() OVERRIDE {
+ int rv = PrepareSearch();
+ if (rv == OK)
+ rv = StartQuery();
+ DCHECK_NE(OK, rv);
+ return rv;
+ }
+
+ private:
+ // Prepares |qnames_| according to the DnsConfig.
+ int PrepareSearch() {
+ const DnsConfig& config = session_->config();
+
+ std::string labelled_hostname;
+ if (!DNSDomainFromDot(hostname_, &labelled_hostname))
+ return ERR_INVALID_ARGUMENT;
+
+ if (hostname_[hostname_.size() - 1] == '.') {
+ // It's a fully-qualified name, no suffix search.
+ qnames_.push_back(labelled_hostname);
+ return OK;
}
- private:
- scoped_refptr<DnsSession> session_;
- BoundNetLog net_log_;
- scoped_ptr<DnsTransaction> transaction_;
- };
+ // Set true when |labelled_hostname| is put on the list.
+ bool had_hostname = false;
+
+ int ndots = CountLabels(labelled_hostname) - 1;
+ if (ndots >= config.ndots) {
+ qnames_.push_back(labelled_hostname);
+ had_hostname = true;
+ }
+
+ std::string qname;
+ for (size_t i = 0; i < config.search.size(); ++i) {
+ // Ignore invalid (too long) combinations.
+ if (!DNSDomainFromDot(hostname_ + "." + config.search[i], &qname))
+ continue;
+ if (qname.size() == labelled_hostname.size()) {
+ if (had_hostname)
+ continue;
+ had_hostname = true;
+ }
+ qnames_.push_back(qname);
+ }
+
+ if (!had_hostname)
+ qnames_.push_back(labelled_hostname);
+
+ return OK;
+ }
+
+ void DoCallback(int rv) {
+ if (callback_.is_null())
+ return;
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ DCHECK(rv != OK || successful_attempt_ != NULL);
+
+ DnsTransactionFactory::CallbackType callback = callback_;
+ callback_.Reset();
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_DNS_TRANSACTION, rv);
+ callback.Run(this,
+ rv,
+ successful_attempt_ ? successful_attempt_->response() : NULL);
+ }
+
+ // Makes another attempt at the current name, |qnames_.front()|, using the
+ // next nameserver.
+ int MakeAttempt() {
+ int attempt_number = attempts_.size();
+
+ scoped_ptr<DatagramClientSocket> socket(
+ session_->socket_factory()->CreateDatagramClientSocket(
+ DatagramSocket::RANDOM_BIND,
+ base::Bind(&base::RandInt),
+ net_log_.net_log(),
+ net_log_.source()));
+
+ uint16 id = session_->NextQueryId();
+ scoped_ptr<DnsQuery> query;
+ if (attempts_.empty()) {
+ query.reset(new DnsQuery(id, qnames_.front(), qtype_));
+ } else {
+ query.reset(attempts_[0]->query()->CloneWithNewId(id));
+ }
+
+ net_log_.AddEvent(NetLog::TYPE_DNS_TRANSACTION_ATTEMPT, make_scoped_refptr(
+ new NetLogSourceParameter("socket_source", socket->NetLog().source())));
+
+ const DnsConfig& config = session_->config();
+
+ int server_index = first_server_index_ +
+ (attempt_number % config.nameservers.size());
- explicit DnsClientImpl(DnsSession* session) {
+ DnsUDPAttempt* attempt = new DnsUDPAttempt(
+ socket.Pass(),
+ config.nameservers[server_index],
+ query.Pass(),
+ base::Bind(&DnsTransactionImpl::OnAttemptComplete,
+ base::Unretained(this),
+ attempt_number));
+
+ base::TimeDelta timeout = session_->NextTimeout(attempt_number);
+ timer_.Start(FROM_HERE, timeout, this, &DnsTransactionImpl::OnTimeout);
+ attempts_.push_back(attempt);
+ return attempt->Start();
+ }
+
+ // Begins query for the current name. Makes the first attempt.
+ int StartQuery() {
+ std::string dotted_qname = DNSDomainToString(qnames_.front());
+ net_log_.BeginEvent(
+ NetLog::TYPE_DNS_TRANSACTION_QUERY,
+ make_scoped_refptr(new NetLogStringParameter("qname", dotted_qname)));
+
+ first_server_index_ = session_->NextFirstServerIndex();
+
+ STLDeleteElements(&attempts_);
+ return MakeAttempt();
+ }
+
+ void OnAttemptComplete(int attempt_number, int rv) {
+ timer_.Stop();
+
+ const DnsUDPAttempt* attempt = attempts_[attempt_number];
+
+ if (attempt->response()) {
+ net_log_.AddEvent(
+ NetLog::TYPE_DNS_TRANSACTION_RESPONSE,
+ make_scoped_refptr(
+ new ResponseParameters(attempt->response()->rcode(),
+ attempt->response()->answer_count(),
+ attempt->socket()->NetLog().source())));
+ }
+
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_DNS_TRANSACTION_QUERY, rv);
+
+ switch (rv) {
+ case ERR_NAME_NOT_RESOLVED:
+ // Try next suffix.
+ qnames_.pop_front();
+ if (qnames_.empty())
+ rv = ERR_NAME_NOT_RESOLVED;
+ else
+ rv = StartQuery();
+ break;
+ case OK:
+ successful_attempt_ = attempt;
+ break;
+ default:
+ // TODO(szym): Some nameservers could fail and we should just ignore
+ // them.
+ break;
+ }
+ if (rv != ERR_IO_PENDING)
+ DoCallback(rv);
+ }
+
+ void OnTimeout() {
+ const DnsConfig& config = session_->config();
+ if (attempts_.size() == config.attempts * config.nameservers.size()) {
+ DoCallback(ERR_DNS_TIMED_OUT);
+ return;
+ }
+ int rv = MakeAttempt();
+ if (rv != ERR_IO_PENDING)
+ DoCallback(rv);
+ }
+
+ scoped_refptr<DnsSession> session_;
+ std::string hostname_;
+ uint16 qtype_;
+ // Set to null once the transaction completes.
+ DnsTransactionFactory::CallbackType callback_;
+
+ BoundNetLog net_log_;
+
+ // Search list of fully-qualified DNS names to query next (in DNS format).
+ std::deque<std::string> qnames_;
+
+ // List of attempts for the current name.
+ std::vector<DnsUDPAttempt*> attempts_;
+ // The member of |attempts_| that succeeded first.
+ const DnsUDPAttempt* successful_attempt_;
+
+ // Index of the first server to try on each search query.
+ int first_server_index_;
+
+ base::OneShotTimer<DnsTransactionImpl> timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsTransactionImpl);
+};
+
+// ----------------------------------------------------------------------------
+
+// Implementation of DnsTransactionFactory that returns instances of
+// DnsTransactionImpl.
+class DnsTransactionFactoryImpl : public DnsTransactionFactory {
+ public:
+ explicit DnsTransactionFactoryImpl(DnsSession* session) {
session_ = session;
}
- virtual Request* CreateRequest(
- const base::StringPiece& qname,
+ virtual scoped_ptr<DnsTransaction> CreateTransaction(
+ const std::string& hostname,
uint16 qtype,
- const RequestCallback& callback,
+ const CallbackType& callback,
const BoundNetLog& source_net_log) OVERRIDE {
- return new RequestImpl(qname, qtype, callback, session_, source_net_log);
+ return scoped_ptr<DnsTransaction>(new DnsTransactionImpl(session_,
+ hostname,
+ qtype,
+ callback,
+ source_net_log));
}
private:
scoped_refptr<DnsSession> session_;
};
+} // namespace
+
// static
-DnsClient* DnsClient::CreateClient(DnsSession* session) {
- return new DnsClientImpl(session);
+scoped_ptr<DnsTransactionFactory> DnsTransactionFactory::CreateFactory(
+ DnsSession* session) {
+ return scoped_ptr<DnsTransactionFactory>(
+ new DnsTransactionFactoryImpl(session));
}
} // namespace net
« net/dns/dns_client.h ('K') | « net/dns/dns_client.h ('k') | net/dns/dns_client_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698