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

Side by Side 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: Added comments. Fixed tests. 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include <deque>
6 #include <string>
7 #include <vector>
8
9 #include "base/bind.h"
10 #include "base/memory/ref_counted.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/memory/weak_ptr.h"
13 #include "base/message_loop.h"
14 #include "base/rand_util.h"
15 #include "base/stl_util.h"
16 #include "base/string_piece.h"
17 #include "base/threading/non_thread_safe.h"
18 #include "base/timer.h"
19 #include "net/base/completion_callback.h"
20 #include "net/base/dns_util.h"
21 #include "net/base/io_buffer.h"
22 #include "net/base/ip_endpoint.h"
23 #include "net/base/net_errors.h"
24 #include "net/base/net_log.h"
5 #include "net/dns/dns_client.h" 25 #include "net/dns/dns_client.h"
mmenke 2012/01/13 16:44:37 This should go first, above the other includes, pe
6 26 #include "net/dns/dns_protocol.h"
7 #include "base/bind.h" 27 #include "net/dns/dns_query.h"
8 #include "base/string_piece.h"
9 #include "net/base/net_errors.h"
10 #include "net/dns/dns_response.h" 28 #include "net/dns/dns_response.h"
11 #include "net/dns/dns_session.h" 29 #include "net/dns/dns_session.h"
12 #include "net/dns/dns_transaction.h"
13 #include "net/socket/client_socket_factory.h" 30 #include "net/socket/client_socket_factory.h"
31 #include "net/udp/datagram_client_socket.h"
14 32
15 namespace net { 33 namespace net {
16 34
17 DnsClient::Request::Request(const base::StringPiece& qname, 35 namespace {
18 uint16 qtype, 36
19 const RequestCallback& callback) 37 // Count labels in the fully-qualified name in DNS format.
20 : qname_(qname.data(), qname.size()), 38 int CountLabels(const std::string& name) {
39 size_t count = 0;
40 for (size_t i = 0; i < name.size() && name[i]; i+= name[i] + 1)
cbentzel 2012/01/13 13:39:54 Nit: space between i and +=
41 ++count;
42 return count;
43 }
44
45 class StartParameters : public NetLog::EventParameters {
46 public:
47 StartParameters(const std::string& hostname,
48 uint16 qtype,
49 const NetLog::Source& source)
50 : hostname_(hostname), qtype_(qtype), source_(source) {}
51
52 virtual Value* ToValue() const {
mmenke 2012/01/13 16:44:37 nit: OVERRIDE
53 DictionaryValue* dict = new DictionaryValue();
54 dict->SetString("hostname", hostname_);
55 dict->SetInteger("query_type", qtype_);
56 dict->Set("source_dependency", source_.ToValue());
57 return dict;
58 }
59
60 private:
61 std::string hostname_;
62 uint16 qtype_;
mmenke 2012/01/13 16:44:37 nit: Both of these can be const
63 const NetLog::Source source_;
64 };
65
66 class ResponseParameters : public NetLog::EventParameters {
67 public:
68 ResponseParameters(int rcode, int answer_count, const NetLog::Source& source)
69 : rcode_(rcode), answer_count_(answer_count), source_(source) {}
70
71 virtual Value* ToValue() const {
mmenke 2012/01/13 16:44:37 nit: OVERRIDE
72 DictionaryValue* dict = new DictionaryValue();
73 dict->SetInteger("rcode", rcode_);
74 dict->SetInteger("answer_count", answer_count_);
75 dict->Set("source_dependency", source_.ToValue());
76 return dict;
77 }
78
79 private:
80 int rcode_;
81 int answer_count_;
mmenke 2012/01/13 16:44:37 nit: Both of these can be const
82 const NetLog::Source source_;
83 };
84
85 // ----------------------------------------------------------------------------
86
87 // A single asynchronous DNS exchange over UDP, which consists of sending out a
88 // DNS query, waiting for a response, and returning the response that it
89 // matches. Logging is done in the socket and in the outer DnsTransaction.
90 class DnsUDPAttempt {
91 public:
92 DnsUDPAttempt(scoped_ptr<DatagramClientSocket> socket,
93 const IPEndPoint& server,
94 scoped_ptr<DnsQuery> query,
95 const CompletionCallback& callback)
96 : next_state_(STATE_NONE),
97 socket_(socket.Pass()),
98 server_(server),
99 query_(query.Pass()),
100 callback_(callback) {
101 }
102
103 // Starts the attempt. Returns ERR_IO_PENDING if cannot complete synchronously
104 // and calls |callback| upon completion.
105 int Start() {
106 DCHECK_EQ(STATE_NONE, next_state_);
107 next_state_ = STATE_CONNECT;
108 return DoLoop(OK);
109 }
110
111 const DnsQuery* query() {
mmenke 2012/01/13 16:44:37 nit: const
112 return query_.get();
113 }
114
115 const DatagramClientSocket* socket() const {
116 return socket_.get();
117 }
118
119 // Returns the response or NULL if has not received a matching response from
120 // the server.
121 const DnsResponse* response() const {
122 return (response_.get() != NULL && response_->Parser().IsValid()) ?
123 response_.get() : NULL;
124 }
125
126 private:
127 enum State {
128 STATE_CONNECT,
129 STATE_SEND_QUERY,
130 STATE_SEND_QUERY_COMPLETE,
131 STATE_READ_RESPONSE,
132 STATE_READ_RESPONSE_COMPLETE,
133 STATE_NONE,
134 };
135
136 int DoLoop(int result) {
137 DCHECK_NE(STATE_NONE, next_state_);
138 int rv = result;
139 do {
140 State state = next_state_;
141 next_state_ = STATE_NONE;
142 switch (state) {
143 case STATE_CONNECT:
144 rv = DoConnect();
145 break;
146 case STATE_SEND_QUERY:
147 rv = DoSendQuery();
148 break;
149 case STATE_SEND_QUERY_COMPLETE:
150 rv = DoSendQueryComplete(rv);
151 break;
152 case STATE_READ_RESPONSE:
153 rv = DoReadResponse();
154 break;
155 case STATE_READ_RESPONSE_COMPLETE:
156 rv = DoReadResponseComplete(rv);
157 break;
158 default:
159 NOTREACHED();
160 break;
161 }
162 } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
163
164 return rv;
165 }
166
167 int DoConnect() {
168 next_state_ = STATE_SEND_QUERY;
169 return socket_->Connect(server_);
170 }
171
172 int DoSendQuery() {
173 next_state_ = STATE_SEND_QUERY_COMPLETE;
174 return socket_->Write(query_->io_buffer(),
175 query_->io_buffer()->size(),
176 base::Bind(&DnsUDPAttempt::OnIOComplete,
177 base::Unretained(this)));
178 }
179
180 int DoSendQueryComplete(int rv) {
181 if (rv < 0)
182 return rv;
183
184 // Writing to UDP should not result in a partial datagram.
185 if (rv != query_->io_buffer()->size())
186 return ERR_MSG_TOO_BIG;
187
188 next_state_ = STATE_READ_RESPONSE;
189 return OK;
190 }
191
192 int DoReadResponse() {
193 next_state_ = STATE_READ_RESPONSE_COMPLETE;
194 response_.reset(new DnsResponse());
195 return socket_->Read(response_->io_buffer(),
196 response_->io_buffer()->size(),
197 base::Bind(&DnsUDPAttempt::OnIOComplete,
198 base::Unretained(this)));
199 }
200
201 int DoReadResponseComplete(int rv) {
202 DCHECK_NE(ERR_IO_PENDING, rv);
203 if (rv < 0)
204 return rv;
205
206 DCHECK(rv);
207 if (!response_->InitParse(rv, *query_))
208 return ERR_DNS_MALFORMED_RESPONSE;
209 if (response_->flags() & dns_protocol::kFlagTC)
210 return ERR_DNS_SERVER_REQUIRES_TCP;
211 if (response_->rcode() != dns_protocol::kRcodeNOERROR &&
212 response_->rcode() != dns_protocol::kRcodeNXDOMAIN) {
213 return ERR_DNS_SERVER_FAILED;
214 }
215 if (response_->answer_count() == 0)
216 return ERR_NAME_NOT_RESOLVED;
217
218 return OK;
219 }
220
221 void OnIOComplete(int rv) {
222 rv = DoLoop(rv);
223 if (rv != ERR_IO_PENDING)
224 callback_.Run(rv);
225 }
226
227 State next_state_;
228
229 scoped_ptr<DatagramClientSocket> socket_;
230 IPEndPoint server_;
231 scoped_ptr<DnsQuery> query_;
232
233 scoped_ptr<DnsResponse> response_;
234
235 CompletionCallback callback_;
mmenke 2012/01/13 16:44:37 nit: DISALLOW_COPY_AND_ASSIGN
236 };
237
238 // ----------------------------------------------------------------------------
239
240 // Implements DnsTransaction. Configuration is supplied by DnsSession.
241 // The suffix list is built according to the DnsConfig from the session.
242 // The timeout for each DnsUDPAttempt is given by DnsSession::NextTimeout.
243 // The first server to attempt on each query is given by
244 // DnsSession::NextFirstServerIndex, and the order is round-robin afterwards.
245 // Each server is attempted DnsConfig::attempts times.
246 class DnsTransactionImpl : public DnsTransaction,
247 public base::NonThreadSafe,
248 public base::SupportsWeakPtr<DnsTransactionImpl> {
249 public:
250 DnsTransactionImpl(DnsSession* session,
251 const std::string& hostname,
252 uint16 qtype,
253 const DnsClient::CallbackType& callback,
254 const BoundNetLog& source_net_log)
255 : session_(session),
256 hostname_(hostname),
21 qtype_(qtype), 257 qtype_(qtype),
22 callback_(callback) { 258 callback_(callback),
23 } 259 net_log_(BoundNetLog::Make(session->net_log(),
24 260 NetLog::SOURCE_DNS_TRANSACTION)),
25 DnsClient::Request::~Request() {} 261 successful_attempt_(NULL) {
cbentzel 2012/01/13 13:39:54 first_server_index_ should be initialized - yeah,
26 262 DCHECK(session_);
27 // Implementation of DnsClient that uses DnsTransaction to serve requests. 263 DCHECK(!hostname_.empty());
264 DCHECK(!callback_.is_null());
265
cbentzel 2012/01/13 13:39:54 Should this check if qtype is one of the known typ
266 net_log_.BeginEvent(NetLog::TYPE_DNS_TRANSACTION, make_scoped_refptr(
267 new StartParameters(hostname_, qtype_, source_net_log.source())));
268
269 int rv = PrepareSearch();
cbentzel 2012/01/13 13:39:54 Not really sure if I like having all this in the c
szym 2012/01/13 15:43:40 Do you mean move PrepareSearch to Start? I'd rathe
cbentzel 2012/01/13 18:12:59 I did mean do it in DnsClient::CreateTransaction,
270 if (rv == OK)
271 rv = StartQuery();
272 if (rv != ERR_IO_PENDING) {
273 DCHECK_NE(OK, rv);
274 DCHECK_NE(ERR_NAME_NOT_RESOLVED, rv);
275 // Unexpected synchronous completion. Use WeakPtr in case the user
276 // destroys the transaction before the task is executed.
277 MessageLoop::current()->PostTask(
278 FROM_HERE,
279 base::Bind(&DnsTransactionImpl::DoCallback, AsWeakPtr(), rv));
280 }
281 }
282
283 virtual ~DnsTransactionImpl() {
284 STLDeleteElements(&attempts_);
285 if (!callback_.is_null()) {
286 net_log_.AddEvent(NetLog::TYPE_CANCELLED, NULL);
287 net_log_.EndEventWithNetErrorCode(NetLog::TYPE_DNS_TRANSACTION,
288 ERR_ABORTED);
289 }
290 }
291
292 virtual const std::string& GetHostname() const OVERRIDE {
293 DCHECK(CalledOnValidThread());
294 return hostname_;
295 }
296
297 virtual uint16 GetType() const OVERRIDE {
298 DCHECK(CalledOnValidThread());
299 return qtype_;
300 }
301
302 private:
303 // Prepares |qnames_| according to the DnsConfig.
304 int PrepareSearch() {
305 const DnsConfig& config = session_->config();
306
307 std::string hostname;
cbentzel 2012/01/13 13:39:54 It would be nice to use a better variable name tha
szym 2012/01/13 15:43:40 I could use dotted_hostname for the other case.
308 if (!DNSDomainFromDot(hostname_, &hostname))
309 return ERR_INVALID_ARGUMENT;
310
311 if (hostname_[hostname_.size() - 1] == '.') {
312 // It's a fully-qualified name, no suffix search.
313 qnames_.push_back(hostname);
314 return OK;
315 }
316
317 // Set true when |hostname| is put on the list.
318 bool had_hostname = false;
319
320 int ndots = CountLabels(hostname) - 1;
321 if (ndots >= config.ndots) {
322 qnames_.push_back(hostname);
323 had_hostname = true;
324 }
325
326 std::string qname;
327 for (size_t i = 0; i < config.search.size(); ++i) {
328 // Ignore invalid (too long) combinations.
329 if (!DNSDomainFromDot(hostname_ + "." + config.search[i], &qname))
330 continue;
331 if (qname.size() == hostname.size()) {
cbentzel 2012/01/13 13:39:54 Interesting - is an empty suffix allowed? Or is th
szym 2012/01/13 15:43:40 DNSDomainFromDot always appends the root domain at
332 if (had_hostname)
cbentzel 2012/01/13 13:39:54 Do you want to make this more generic in the loop
szym 2012/01/13 15:43:40 I think it'd make sense to do this filtering at th
333 continue;
334 had_hostname = true;
335 }
336 qnames_.push_back(qname);
337 }
338
339 if (!had_hostname)
340 qnames_.push_back(hostname);
341
342 return OK;
343 }
344
345 void DoCallback(int rv) {
346 if (callback_.is_null())
347 return;
348 DCHECK_NE(ERR_IO_PENDING, rv);
349 DCHECK(rv != OK || successful_attempt_ != NULL);
350
351 DnsClient::CallbackType callback = callback_;
352 callback_.Reset();
353 net_log_.EndEventWithNetErrorCode(NetLog::TYPE_DNS_TRANSACTION, rv);
354 callback.Run(this,
355 rv,
356 successful_attempt_ ? successful_attempt_->response() : NULL);
357 }
358
359 // Makes another attempt at the current name, |qnames_.front()|, using the
360 // next nameserver.
361 int MakeAttempt() {
362 int attempt_number = attempts_.size();
363
364 scoped_ptr<DatagramClientSocket> socket(
365 session_->socket_factory()->CreateDatagramClientSocket(
366 DatagramSocket::RANDOM_BIND,
367 base::Bind(&base::RandInt),
368 net_log_.net_log(),
369 net_log_.source()));
370
371 uint16 id = session_->NextQueryId();
372 scoped_ptr<DnsQuery> query;
373 if (attempts_.empty()) {
374 query.reset(new DnsQuery(id, qnames_.front(), qtype_));
375 } else {
376 query.reset(attempts_[0]->query()->CloneWithNewId(id));
cbentzel 2012/01/13 13:39:54 Note to self: See if this any easier.
377 }
378
379 net_log_.AddEvent(NetLog::TYPE_DNS_TRANSACTION_ATTEMPT, make_scoped_refptr(
380 new NetLogSourceParameter("socket", socket->NetLog().source())));
mmenke 2012/01/13 16:44:37 Think it would be a little clearer if you also log
szym 2012/01/13 17:17:42 The reason why I don't include attempt_number is b
mmenke 2012/01/13 17:22:58 I was just thinking that it would be clearer for t
381
382 const DnsConfig& config = session_->config();
383
384 int server_index = first_server_index_ +
cbentzel 2012/01/13 13:39:54 You should check for 0 length config.nameservers,
szym 2012/01/13 15:43:40 If |config.nameservers| is empty, the config is in
cbentzel 2012/01/13 18:12:59 What if the config changed while the DnsTransactio
szym 2012/01/13 20:06:17 DnsConfig is const in DnsSession, so DnsTransactio
385 (attempt_number % config.nameservers.size());
386
387 DnsUDPAttempt* attempt = new DnsUDPAttempt(
388 socket.Pass(),
389 config.nameservers[server_index],
390 query.Pass(),
391 base::Bind(&DnsTransactionImpl::OnAttemptComplete,
392 base::Unretained(this),
393 attempt_number));
394
395 base::TimeDelta timeout = session_->NextTimeout(attempt_number);
396 timer_.Start(FROM_HERE, timeout, this, &DnsTransactionImpl::OnTimeout);
397 attempts_.push_back(attempt);
398 return attempt->Start();
399 }
400
401 // Begins query for the current name. Makes the first attempt.
402 int StartQuery() {
403 std::string dotted_qname = DNSDomainToString(qnames_.front());
404 net_log_.BeginEvent(
405 NetLog::TYPE_DNS_TRANSACTION_QUERY,
mmenke 2012/01/13 17:16:24 Where do you log the corresponding end event?
406 make_scoped_refptr(new NetLogStringParameter("qname", dotted_qname)));
407
408 first_server_index_ = session_->NextFirstServerIndex();
409
410 STLDeleteElements(&attempts_);
411 return MakeAttempt();
412 }
413
414 void OnAttemptComplete(int attempt_number, int rv) {
415 timer_.Stop();
416
417 const DnsUDPAttempt* attempt = attempts_[attempt_number];
418
419 net_log_.AddEvent(
420 NetLog::TYPE_DNS_TRANSACTION_RESPONSE,
421 make_scoped_refptr(
422 new NetLogSourceParameter("socket",
423 attempt->socket()->NetLog().source())));
424
425 switch (rv) {
426 case ERR_NAME_NOT_RESOLVED:
427 // Try next suffix.
428 qnames_.pop_front();
429 if (qnames_.empty())
430 rv = ERR_NAME_NOT_RESOLVED;
431 else
432 rv = StartQuery();
433 break;
434 case OK:
435 successful_attempt_ = attempt;
436 break;
437 default:
438 // TODO(szym): Some nameservers could fail and we should just ignore
439 // them.
440 break;
441 }
442 if (rv != ERR_IO_PENDING)
443 DoCallback(rv);
444 }
445
446 void OnTimeout() {
447 const DnsConfig& config = session_->config();
448 if (attempts_.size() == config.attempts * config.nameservers.size()) {
449 DoCallback(ERR_DNS_TIMED_OUT);
450 return;
451 }
452 int rv = MakeAttempt();
453 if (rv != ERR_IO_PENDING)
454 DoCallback(rv);
455 }
456
457 scoped_refptr<DnsSession> session_;
458 std::string hostname_;
459 uint16 qtype_;
460 // Set to NULL once the transaction completes.
cbentzel 2012/01/13 13:39:54 Nit: perhaps null instead of NULL - NULL implies p
461 DnsClient::CallbackType callback_;
462
463 BoundNetLog net_log_;
464
465 // Search list of fully-qualified DNS names to query next (in DNS format).
466 std::deque<std::string> qnames_;
cbentzel 2012/01/13 13:39:54 deque not really needed here, since just doing pus
szym 2012/01/13 15:43:40 Need qnames_.pop_front().
467
468 // List of attempts for the current name.
469 std::vector<DnsUDPAttempt*> attempts_;
470 // The one of the |attempts_| that succeeded first.
mmenke 2012/01/13 16:44:37 Nit: This is the member of |attempts_| that succe
471 const DnsUDPAttempt* successful_attempt_;
472
473 // Index of the first server to try on each search query.
474 int first_server_index_;
475
476 base::OneShotTimer<DnsTransactionImpl> timer_;
477
478 DISALLOW_COPY_AND_ASSIGN(DnsTransactionImpl);
479 };
480
481 // ----------------------------------------------------------------------------
482
483 // Implementation of DnsClient that returns instances of DnsTransactionImpl.
28 class DnsClientImpl : public DnsClient { 484 class DnsClientImpl : public DnsClient {
mmenke 2012/01/13 16:44:37 Is there a compelling reason to keep DnsClient and
szym 2012/01/13 17:17:42 The reason I'd keep them separate is so that we ca
mmenke 2012/01/13 17:22:58 Ah, right. Sounds good to me.
29 public: 485 public:
30 class RequestImpl : public Request {
31 public:
32 RequestImpl(const base::StringPiece& qname,
33 uint16 qtype,
34 const RequestCallback& callback,
35 DnsSession* session,
36 const BoundNetLog& net_log)
37 : Request(qname, qtype, callback),
38 session_(session),
39 net_log_(net_log) {
40 }
41
42 virtual int Start() OVERRIDE {
43 transaction_.reset(new DnsTransaction(
44 session_,
45 qname(),
46 qtype(),
47 base::Bind(&RequestImpl::OnComplete, base::Unretained(this)),
48 net_log_));
49 return transaction_->Start();
50 }
51
52 void OnComplete(DnsTransaction* transaction, int rv) {
53 DCHECK_EQ(transaction_.get(), transaction);
54 // TODO(szym):
55 // - handle retransmissions here instead of DnsTransaction
56 // - handle rcode and flags here instead of DnsTransaction
57 // - update RTT in DnsSession
58 // - perform suffix search
59 // - handle DNS over TCP
60 DoCallback(rv, (rv == OK) ? transaction->response() : NULL);
61 }
62
63 private:
64 scoped_refptr<DnsSession> session_;
65 BoundNetLog net_log_;
66 scoped_ptr<DnsTransaction> transaction_;
67 };
68
69 explicit DnsClientImpl(DnsSession* session) { 486 explicit DnsClientImpl(DnsSession* session) {
70 session_ = session; 487 session_ = session;
71 } 488 }
72 489
73 virtual Request* CreateRequest( 490 virtual scoped_ptr<DnsTransaction> CreateTransaction(
74 const base::StringPiece& qname, 491 const std::string& hostname,
75 uint16 qtype, 492 uint16 qtype,
76 const RequestCallback& callback, 493 const CallbackType& callback,
77 const BoundNetLog& source_net_log) OVERRIDE { 494 const BoundNetLog& source_net_log) OVERRIDE {
78 return new RequestImpl(qname, qtype, callback, session_, source_net_log); 495 return scoped_ptr<DnsTransaction>(new DnsTransactionImpl(session_,
496 hostname,
497 qtype,
498 callback,
499 source_net_log));
79 } 500 }
80 501
81 private: 502 private:
82 scoped_refptr<DnsSession> session_; 503 scoped_refptr<DnsSession> session_;
83 }; 504 };
84 505
506 } // namespace
507
85 // static 508 // static
86 DnsClient* DnsClient::CreateClient(DnsSession* session) { 509 scoped_ptr<DnsClient> DnsClient::CreateClient(DnsSession* session) {
87 return new DnsClientImpl(session); 510 return scoped_ptr<DnsClient>(new DnsClientImpl(session));
88 } 511 }
89 512
90 } // namespace net 513 } // namespace net
91 514
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698