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