summaryrefslogtreecommitdiffstats
path: root/net/base/host_resolver.cc
blob: 4f0ede5daef3ee09001eb45cc1a1fb543e11520b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
// Copyright (c) 2006-2008 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/host_resolver.h"

#if defined(OS_WIN)
#include <ws2tcpip.h>
#include <wspiapi.h>  // Needed for Win2k compat.
#elif defined(OS_POSIX)
#include <netdb.h>
#include <sys/socket.h>
#endif
#if defined(OS_LINUX)
#include <resolv.h>
#endif

#include "base/compiler_specific.h"
#include "base/message_loop.h"
#include "base/stl_util-inl.h"
#include "base/string_util.h"
#include "base/time.h"
#include "base/worker_pool.h"
#include "net/base/address_list.h"
#include "net/base/net_errors.h"

#if defined(OS_LINUX)
#include "base/singleton.h"
#include "base/thread_local_storage.h"
#endif

#if defined(OS_WIN)
#include "net/base/winsock_init.h"
#endif

namespace net {

//-----------------------------------------------------------------------------

static HostMapper* host_mapper;

std::string HostMapper::MapUsingPrevious(const std::string& host) {
  return previous_mapper_.get() ? previous_mapper_->Map(host) : host;
}

HostMapper* SetHostMapper(HostMapper* value) {
  std::swap(host_mapper, value);
  return value;
}

#if defined(OS_LINUX)
// On Linux changes to /etc/resolv.conf can go unnoticed thus resulting in
// DNS queries failing either because nameservers are unknown on startup
// or because nameserver info has changed as a result of e.g. connecting to
// a new network. Some distributions patch glibc to stat /etc/resolv.conf
// to try to automatically detect such changes but these patches are not
// universal and even patched systems such as Jaunty appear to need calls
// to res_ninit to reload the nameserver information in different threads.
//
// We adopt the Mozilla solution here which is to call res_ninit when
// lookups fail and to rate limit the reloading to once per second per
// thread.

// Keep a timer per calling thread to rate limit the calling of res_ninit.
class DnsReloadTimer {
 public:
  DnsReloadTimer() {
    tls_index_.Initialize(SlotReturnFunction);
  }

  ~DnsReloadTimer() { }

  // Check if the timer for the calling thread has expired. When no
  // timer exists for the calling thread, create one.
  bool Expired() {
    const base::TimeDelta kRetryTime = base::TimeDelta::FromSeconds(1);
    base::TimeTicks now = base::TimeTicks::Now();
    base::TimeTicks* timer_ptr =
      static_cast<base::TimeTicks*>(tls_index_.Get());

    if (!timer_ptr) {
      timer_ptr = new base::TimeTicks();
      *timer_ptr = base::TimeTicks::Now();
      tls_index_.Set(timer_ptr);
      // Return true to reload dns info on the first call for each thread.
      return true;
    } else if (now - *timer_ptr > kRetryTime) {
      *timer_ptr = now;
      return true;
    } else {
      return false;
    }
  }

  // Free the allocated timer.
  static void SlotReturnFunction(void* data) {
    base::TimeTicks* tls_data = static_cast<base::TimeTicks*>(data);
    delete tls_data;
  }

 private:
  // We use thread local storage to identify which base::TimeTicks to
  // interact with.
  static ThreadLocalStorage::Slot tls_index_ ;

  DISALLOW_COPY_AND_ASSIGN(DnsReloadTimer);
};

// A TLS slot to the TimeTicks for the current thread.
// static
ThreadLocalStorage::Slot DnsReloadTimer::tls_index_(base::LINKER_INITIALIZED);

#endif  // defined(OS_LINUX)

static int HostResolverProc(const std::string& host, struct addrinfo** out) {
  struct addrinfo hints = {0};
  hints.ai_family = AF_UNSPEC;

#if defined(OS_WIN)
  // DO NOT USE AI_ADDRCONFIG ON WINDOWS.
  //
  // The following comment in <winsock2.h> is the best documentation I found
  // on AI_ADDRCONFIG for Windows:
  //   Flags used in "hints" argument to getaddrinfo()
  //       - AI_ADDRCONFIG is supported starting with Vista
  //       - default is AI_ADDRCONFIG ON whether the flag is set or not
  //         because the performance penalty in not having ADDRCONFIG in
  //         the multi-protocol stack environment is severe;
  //         this defaulting may be disabled by specifying the AI_ALL flag,
  //         in that case AI_ADDRCONFIG must be EXPLICITLY specified to
  //         enable ADDRCONFIG behavior
  //
  // Not only is AI_ADDRCONFIG unnecessary, but it can be harmful.  If the
  // computer is not connected to a network, AI_ADDRCONFIG causes getaddrinfo
  // to fail with WSANO_DATA (11004) for "localhost", probably because of the
  // following note on AI_ADDRCONFIG in the MSDN getaddrinfo page:
  //   The IPv4 or IPv6 loopback address is not considered a valid global
  //   address.
  // See http://crbug.com/5234.
  hints.ai_flags = 0;
#else
  hints.ai_flags = AI_ADDRCONFIG;
#endif

  // Restrict result set to only this socket type to avoid duplicates.
  hints.ai_socktype = SOCK_STREAM;

  int err = getaddrinfo(host.c_str(), NULL, &hints, out);
#if defined(OS_LINUX)
  net::DnsReloadTimer* dns_timer = Singleton<net::DnsReloadTimer>::get();
  // If we fail, re-initialise the resolver just in case there have been any
  // changes to /etc/resolv.conf and retry. See http://crbug.com/11380 for info.
  if (err && dns_timer->Expired()) {
    res_nclose(&_res);
    if (!res_ninit(&_res))
      err = getaddrinfo(host.c_str(), NULL, &hints, out);
  }
#endif

  return err ? ERR_NAME_NOT_RESOLVED : OK;
}

static int ResolveAddrInfo(HostMapper* mapper, const std::string& host,
                           struct addrinfo** out) {
  if (mapper) {
    std::string mapped_host = mapper->Map(host);

    if (mapped_host.empty())
      return ERR_NAME_NOT_RESOLVED;

    return HostResolverProc(mapped_host, out);
  } else {
    return HostResolverProc(host, out);
  }
}

//-----------------------------------------------------------------------------

class HostResolver::Request {
 public:
  Request(int id, const RequestInfo& info, CompletionCallback* callback,
          AddressList* addresses)
      : id_(id), info_(info), job_(NULL), callback_(callback),
        addresses_(addresses) {}

  // Mark the request as cancelled.
  void MarkAsCancelled() {
    job_ = NULL;
    callback_ = NULL;
    addresses_ = NULL;
  }

  bool was_cancelled() const {
    return callback_ == NULL;
  }

  void set_job(Job* job) {
    DCHECK(job != NULL);
    // Identify which job the request is waiting on.
    job_ = job;
  }

  void OnComplete(int error, const AddressList& addrlist) {
    if (error == OK)
      addresses_->SetFrom(addrlist, port());
    callback_->Run(error);
  }

  int port() const {
    return info_.port();
  }

  Job* job() const {
    return job_;
  }

  int id() const {
    return id_;
  }

  const RequestInfo& info() const {
    return info_;
  }

 private:
  // Unique ID for this request. Used by observers to identify requests.
  int id_;

  // The request info that started the request.
  RequestInfo info_;

  // The resolve job (running in worker pool) that this request is dependent on.
  Job* job_;

  // The user's callback to invoke when the request completes.
  CompletionCallback* callback_;

  // The address list to save result into.
  AddressList* addresses_;

  DISALLOW_COPY_AND_ASSIGN(Request);
};

//-----------------------------------------------------------------------------

// This class represents a request to the worker pool for a "getaddrinfo()"
// call.
class HostResolver::Job : public base::RefCountedThreadSafe<HostResolver::Job> {
 public:
  Job(HostResolver* resolver, const std::string& host)
      : host_(host),
        resolver_(resolver),
        origin_loop_(MessageLoop::current()),
        host_mapper_(host_mapper),
        error_(OK),
        results_(NULL) {
  }

  ~Job() {
    if (results_)
      freeaddrinfo(results_);

    // Free the requests attached to this job.
    STLDeleteElements(&requests_);
  }

  // Attaches a request to this job. The job takes ownership of |req| and will
  // take care to delete it.
  void AddRequest(HostResolver::Request* req) {
    req->set_job(this);
    requests_.push_back(req);
  }

  // Called from origin loop.
  void Start() {
    // Dispatch the job to a worker thread.
    if (!WorkerPool::PostTask(FROM_HERE,
            NewRunnableMethod(this, &Job::DoLookup), true)) {
      NOTREACHED();

      // Since we could be running within Resolve() right now, we can't just
      // call OnLookupComplete().  Instead we must wait until Resolve() has
      // returned (IO_PENDING).
      error_ = ERR_UNEXPECTED;
      MessageLoop::current()->PostTask(
          FROM_HERE, NewRunnableMethod(this, &Job::OnLookupComplete));
    }
  }

  // Cancels the current job. Callable from origin thread.
  void Cancel() {
    HostResolver* resolver = resolver_;
    resolver_ = NULL;

    // Mark the job as cancelled, so when worker thread completes it will
    // not try to post completion to origin loop.
    {
      AutoLock locked(origin_loop_lock_);
      origin_loop_ = NULL;
    }

    // We don't have to do anything further to actually cancel the requests
    // that were attached to this job (since they are unreachable now).
    // But we will call HostResolver::CancelRequest(Request*) on each one
    // in order to notify any observers.
    for (RequestsList::const_iterator it = requests_.begin();
         it != requests_.end(); ++it) {
      HostResolver::Request* req = *it;
      if (!req->was_cancelled())
        resolver->CancelRequest(req);
    }
  }

  // Called from origin thread.
  bool was_cancelled() const {
    return resolver_ == NULL;
  }

  // Called from origin thread.
  const std::string& host() const {
    return host_;
  }

  // Called from origin thread.
  const RequestsList& requests() const {
    return requests_;
  }

 private:
  void DoLookup() {
    // Running on the worker thread
    error_ = ResolveAddrInfo(host_mapper_, host_, &results_);

    Task* reply = NewRunnableMethod(this, &Job::OnLookupComplete);

    // The origin loop could go away while we are trying to post to it, so we
    // need to call its PostTask method inside a lock.  See ~HostResolver.
    {
      AutoLock locked(origin_loop_lock_);
      if (origin_loop_) {
        origin_loop_->PostTask(FROM_HERE, reply);
        reply = NULL;
      }
    }

    // Does nothing if it got posted.
    delete reply;
  }

  // Callback for when DoLookup() completes (runs on origin thread).
  void OnLookupComplete() {
    // Should be running on origin loop.
    // TODO(eroman): this is being hit by URLRequestTest.CancelTest*,
    // because MessageLoop::current() == NULL.
    //DCHECK_EQ(origin_loop_, MessageLoop::current());
    DCHECK(error_ || results_);

    if (was_cancelled())
      return;

    DCHECK(!requests_.empty());

     // Adopt the address list using the port number of the first request.
    AddressList addrlist;
    if (error_ == OK) {
      addrlist.Adopt(results_);
      addrlist.SetPort(requests_[0]->port());
      results_ = NULL;
    }

    resolver_->OnJobComplete(this, error_, addrlist);
  }

  // Set on the origin thread, read on the worker thread.
  std::string host_;

  // Only used on the origin thread (where Resolve was called).
  HostResolver* resolver_;
  RequestsList requests_;  // The requests waiting on this job.

  // Used to post ourselves onto the origin thread.
  Lock origin_loop_lock_;
  MessageLoop* origin_loop_;

  // Hold an owning reference to the host mapper that we are going to use.
  // This may not be the current host mapper by the time we call
  // ResolveAddrInfo, but that's OK... we'll use it anyways, and the owning
  // reference ensures that it remains valid until we are done.
  scoped_refptr<HostMapper> host_mapper_;

  // Assigned on the worker thread, read on the origin thread.
  int error_;
  struct addrinfo* results_;

  DISALLOW_COPY_AND_ASSIGN(Job);
};

//-----------------------------------------------------------------------------

HostResolver::HostResolver(int max_cache_entries, int cache_duration_ms)
    : cache_(max_cache_entries, cache_duration_ms), next_request_id_(0),
      shutdown_(false) {
#if defined(OS_WIN)
  EnsureWinsockInit();
#endif
}

HostResolver::~HostResolver() {
  // Cancel the outstanding jobs. Those jobs may contain several attached
  // requests, which will also be cancelled.
  for (JobMap::iterator it = jobs_.begin(); it != jobs_.end(); ++it)
    it->second->Cancel();

  // In case we are being deleted during the processing of a callback.
  if (cur_completing_job_)
    cur_completing_job_->Cancel();
}

// TODO(eroman): Don't create cache entries for hostnames which are simply IP
// address literals.
int HostResolver::Resolve(const RequestInfo& info,
                          AddressList* addresses,
                          CompletionCallback* callback,
                          Request** out_req) {
  if (shutdown_)
    return ERR_UNEXPECTED;

  // Choose a unique ID number for observers to see.
  int request_id = next_request_id_++;

  // Notify registered observers.
  NotifyObserversStartRequest(request_id, info);

  // If we have an unexpired cache entry, use it.
  if (info.allow_cached_response()) {
    const HostCache::Entry* cache_entry = cache_.Lookup(
        info.hostname(), base::TimeTicks::Now());
    if (cache_entry) {
      addresses->SetFrom(cache_entry->addrlist, info.port());
      int error = OK;

      // Notify registered observers.
      NotifyObserversFinishRequest(request_id, info, error);

      return error;
    }
  }

  // If no callback was specified, do a synchronous resolution.
  if (!callback) {
    struct addrinfo* results;
    int error = ResolveAddrInfo(host_mapper, info.hostname(), &results);

    // Adopt the address list.
    AddressList addrlist;
    if (error == OK) {
      addrlist.Adopt(results);
      addrlist.SetPort(info.port());
      *addresses = addrlist;
    }

    // Write to cache.
    cache_.Set(info.hostname(), error, addrlist, base::TimeTicks::Now());

    // Notify registered observers.
    NotifyObserversFinishRequest(request_id, info, error);

    return error;
  }

  // Create a handle for this request, and pass it back to the user if they
  // asked for it (out_req != NULL).
  Request* req = new Request(request_id, info, callback, addresses);
  if (out_req)
    *out_req = req;

  // Next we need to attach our request to a "job". This job is responsible for
  // calling "getaddrinfo(hostname)" on a worker thread.
  scoped_refptr<Job> job;

  // If there is already an outstanding job to resolve |info.hostname()|, use
  // it. This prevents starting concurrent resolves for the same hostname.
  job = FindOutstandingJob(info.hostname());
  if (job) {
    job->AddRequest(req);
  } else {
    // Create a new job for this request.
    job = new Job(this, info.hostname());
    job->AddRequest(req);
    AddOutstandingJob(job);
    // TODO(eroman): Bound the total number of concurrent jobs.
    // http://crbug.com/9598
    job->Start();
  }

  // Completion happens during OnJobComplete(Job*).
  return ERR_IO_PENDING;
}

// See OnJobComplete(Job*) for why it is important not to clean out
// cancelled requests from Job::requests_.
void HostResolver::CancelRequest(Request* req) {
  DCHECK(req);
  DCHECK(req->job());
  // NULL out the fields of req, to mark it as cancelled.
  req->MarkAsCancelled();
  NotifyObserversCancelRequest(req->id(), req->info());
}

void HostResolver::AddObserver(Observer* observer) {
  observers_.push_back(observer);
}

void HostResolver::RemoveObserver(Observer* observer) {
  ObserversList::iterator it =
      std::find(observers_.begin(), observers_.end(), observer);

  // Observer must exist.
  DCHECK(it != observers_.end());

  observers_.erase(it);
}

void HostResolver::Shutdown() {
  shutdown_ = true;

  // Cancel the outstanding jobs.
  for (JobMap::iterator it = jobs_.begin(); it != jobs_.end(); ++it)
    it->second->Cancel();
  jobs_.clear();
}

void HostResolver::AddOutstandingJob(Job* job) {
  scoped_refptr<Job>& found_job = jobs_[job->host()];
  DCHECK(!found_job);
  found_job = job;
}

HostResolver::Job* HostResolver::FindOutstandingJob(
    const std::string& hostname) {
  JobMap::iterator it = jobs_.find(hostname);
  if (it != jobs_.end())
    return it->second;
  return NULL;
}

void HostResolver::RemoveOutstandingJob(Job* job) {
  JobMap::iterator it = jobs_.find(job->host());
  DCHECK(it != jobs_.end());
  DCHECK_EQ(it->second.get(), job);
  jobs_.erase(it);
}

void HostResolver::OnJobComplete(Job* job,
                                 int error,
                                 const AddressList& addrlist) {
  RemoveOutstandingJob(job);

  // Write result to the cache.
  cache_.Set(job->host(), error, addrlist, base::TimeTicks::Now());

  // Make a note that we are executing within OnJobComplete() in case the
  // HostResolver is deleted by a callback invocation.
  DCHECK(!cur_completing_job_);
  cur_completing_job_ = job;

  // Complete all of the requests that were attached to the job.
  for (RequestsList::const_iterator it = job->requests().begin();
       it != job->requests().end(); ++it) {
    Request* req = *it;
    if (!req->was_cancelled()) {
      DCHECK_EQ(job, req->job());

      // Notify registered observers.
      NotifyObserversFinishRequest(req->id(), req->info(), error);

      req->OnComplete(error, addrlist);

      // Check if the job was cancelled as a result of running the callback.
      // (Meaning that |this| was deleted).
      if (job->was_cancelled())
        return;
    }
  }

  cur_completing_job_ = NULL;
}

void HostResolver::NotifyObserversStartRequest(int request_id,
                                               const RequestInfo& info) {
  for (ObserversList::iterator it = observers_.begin();
       it != observers_.end(); ++it) {
    (*it)->OnStartResolution(request_id, info);
  }
}

void HostResolver::NotifyObserversFinishRequest(int request_id,
                                                const RequestInfo& info,
                                                int error) {
  bool was_resolved = error == OK;
  for (ObserversList::iterator it = observers_.begin();
       it != observers_.end(); ++it) {
    (*it)->OnFinishResolutionWithStatus(request_id, was_resolved, info);
  }
}

void HostResolver::NotifyObserversCancelRequest(int request_id,
                                                const RequestInfo& info) {
  for (ObserversList::iterator it = observers_.begin();
       it != observers_.end(); ++it) {
    (*it)->OnCancelResolution(request_id, info);
  }
}

//-----------------------------------------------------------------------------

SingleRequestHostResolver::SingleRequestHostResolver(HostResolver* resolver)
    : resolver_(resolver),
      cur_request_(NULL),
      cur_request_callback_(NULL),
      ALLOW_THIS_IN_INITIALIZER_LIST(
          callback_(this, &SingleRequestHostResolver::OnResolveCompletion)) {
  DCHECK(resolver_ != NULL);
}

SingleRequestHostResolver::~SingleRequestHostResolver() {
  if (cur_request_) {
    resolver_->CancelRequest(cur_request_);
  }
}

int SingleRequestHostResolver::Resolve(const HostResolver::RequestInfo& info,
                                       AddressList* addresses,
                                       CompletionCallback* callback) {
  DCHECK(!cur_request_ && !cur_request_callback_) << "resolver already in use";

  HostResolver::Request* request = NULL;

  // We need to be notified of completion before |callback| is called, so that
  // we can clear out |cur_request_*|.
  CompletionCallback* transient_callback = callback ? &callback_ : NULL;

  int rv = resolver_->Resolve(info, addresses, transient_callback, &request);

  if (rv == ERR_IO_PENDING) {
    // Cleared in OnResolveCompletion().
    cur_request_ = request;
    cur_request_callback_ = callback;
  }

  return rv;
}

void SingleRequestHostResolver::OnResolveCompletion(int result) {
  DCHECK(cur_request_ && cur_request_callback_);

  CompletionCallback* callback = cur_request_callback_;

  // Clear the outstanding request information.
  cur_request_ = NULL;
  cur_request_callback_ = NULL;

  // Call the user's original callback.
  callback->Run(result);
}

}  // namespace net