host_resolver_proc.cc revision c407dc5cd9bdc5668497f21b26b09d988ab439de
1// Copyright (c) 2010 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "net/base/host_resolver_proc.h"
6
7#include "build/build_config.h"
8
9#if defined(OS_POSIX) && !defined(OS_MACOSX)
10#include <resolv.h>
11#endif
12
13#include "base/logging.h"
14#include "base/time.h"
15#include "net/base/address_list.h"
16#include "net/base/net_errors.h"
17#include "net/base/sys_addrinfo.h"
18
19#if defined(OS_POSIX) && !defined(OS_MACOSX)
20#include "base/singleton.h"
21#include "base/thread_local_storage.h"
22#endif
23
24namespace net {
25
26HostResolverProc* HostResolverProc::default_proc_ = NULL;
27
28HostResolverProc::HostResolverProc(HostResolverProc* previous) {
29  SetPreviousProc(previous);
30
31  // Implicitly fall-back to the global default procedure.
32  if (!previous)
33    SetPreviousProc(default_proc_);
34}
35
36void HostResolverProc::SetPreviousProc(HostResolverProc* proc) {
37  HostResolverProc* current_previous = previous_proc_;
38  previous_proc_ = NULL;
39  // Now that we've guaranteed |this| is the last proc in a chain, we can
40  // detect potential cycles using GetLastProc().
41  previous_proc_ = (GetLastProc(proc) == this) ? current_previous : proc;
42}
43
44void HostResolverProc::SetLastProc(HostResolverProc* proc) {
45  GetLastProc(this)->SetPreviousProc(proc);
46}
47
48// static
49HostResolverProc* HostResolverProc::GetLastProc(HostResolverProc* proc) {
50  if (proc == NULL)
51    return NULL;
52  HostResolverProc* last_proc = proc;
53  while (last_proc->previous_proc_ != NULL)
54    last_proc = last_proc->previous_proc_;
55  return last_proc;
56}
57
58// static
59HostResolverProc* HostResolverProc::SetDefault(HostResolverProc* proc) {
60  HostResolverProc* old = default_proc_;
61  default_proc_ = proc;
62  return old;
63}
64
65// static
66HostResolverProc* HostResolverProc::GetDefault() {
67  return default_proc_;
68}
69
70int HostResolverProc::ResolveUsingPrevious(
71    const std::string& host,
72    AddressFamily address_family,
73    HostResolverFlags host_resolver_flags,
74    AddressList* addrlist,
75    int* os_error) {
76  if (previous_proc_) {
77    return previous_proc_->Resolve(host, address_family, host_resolver_flags,
78                                   addrlist, os_error);
79  }
80
81  // Final fallback is the system resolver.
82  return SystemHostResolverProc(host, address_family, host_resolver_flags,
83                                addrlist, os_error);
84}
85
86#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_OPENBSD)
87// On Linux/BSD, changes to /etc/resolv.conf can go unnoticed thus resulting
88// in DNS queries failing either because nameservers are unknown on startup
89// or because nameserver info has changed as a result of e.g. connecting to
90// a new network. Some distributions patch glibc to stat /etc/resolv.conf
91// to try to automatically detect such changes but these patches are not
92// universal and even patched systems such as Jaunty appear to need calls
93// to res_ninit to reload the nameserver information in different threads.
94//
95// We adopt the Mozilla solution here which is to call res_ninit when
96// lookups fail and to rate limit the reloading to once per second per
97// thread.
98//
99// OpenBSD does not have thread-safe res_ninit/res_nclose so we can't do
100// the same trick there.
101
102// Keep a timer per calling thread to rate limit the calling of res_ninit.
103class DnsReloadTimer {
104 public:
105  // Check if the timer for the calling thread has expired. When no
106  // timer exists for the calling thread, create one.
107  bool Expired() {
108    const base::TimeDelta kRetryTime = base::TimeDelta::FromSeconds(1);
109    base::TimeTicks now = base::TimeTicks::Now();
110    base::TimeTicks* timer_ptr =
111      static_cast<base::TimeTicks*>(tls_index_.Get());
112
113    if (!timer_ptr) {
114      timer_ptr = new base::TimeTicks();
115      *timer_ptr = base::TimeTicks::Now();
116      tls_index_.Set(timer_ptr);
117      // Return true to reload dns info on the first call for each thread.
118      return true;
119    } else if (now - *timer_ptr > kRetryTime) {
120      *timer_ptr = now;
121      return true;
122    } else {
123      return false;
124    }
125  }
126
127  // Free the allocated timer.
128  static void SlotReturnFunction(void* data) {
129    base::TimeTicks* tls_data = static_cast<base::TimeTicks*>(data);
130    delete tls_data;
131  }
132
133 private:
134  friend struct DefaultSingletonTraits<DnsReloadTimer>;
135
136  DnsReloadTimer() {
137    // During testing the DnsReloadTimer Singleton may be created and destroyed
138    // multiple times. Initialize the ThreadLocalStorage slot only once.
139    if (!tls_index_.initialized())
140      tls_index_.Initialize(SlotReturnFunction);
141  }
142
143  ~DnsReloadTimer() {
144  }
145
146  // We use thread local storage to identify which base::TimeTicks to
147  // interact with.
148  static ThreadLocalStorage::Slot tls_index_ ;
149
150  DISALLOW_COPY_AND_ASSIGN(DnsReloadTimer);
151};
152
153// A TLS slot to the TimeTicks for the current thread.
154// static
155ThreadLocalStorage::Slot DnsReloadTimer::tls_index_(base::LINKER_INITIALIZED);
156
157#endif  // defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_OPENBSD)
158
159int SystemHostResolverProc(const std::string& host,
160                           AddressFamily address_family,
161                           HostResolverFlags host_resolver_flags,
162                           AddressList* addrlist,
163                           int* os_error) {
164  if (os_error)
165    *os_error = 0;
166
167  // The result of |getaddrinfo| for empty hosts is inconsistent across systems.
168  // On Windows it gives the default interface's address, whereas on Linux it
169  // gives an error. We will make it fail on all platforms for consistency.
170  if (host.empty())
171    return ERR_NAME_NOT_RESOLVED;
172
173  struct addrinfo* ai = NULL;
174  struct addrinfo hints = {0};
175
176  switch (address_family) {
177    case ADDRESS_FAMILY_IPV4:
178      hints.ai_family = AF_INET;
179      break;
180    case ADDRESS_FAMILY_IPV6:
181      hints.ai_family = AF_INET6;
182      break;
183    case ADDRESS_FAMILY_UNSPECIFIED:
184      hints.ai_family = AF_UNSPEC;
185      break;
186    default:
187      NOTREACHED();
188      hints.ai_family = AF_UNSPEC;
189  }
190
191#if defined(OS_WIN) || defined(OS_OPENBSD)
192  // DO NOT USE AI_ADDRCONFIG ON WINDOWS.
193  //
194  // The following comment in <winsock2.h> is the best documentation I found
195  // on AI_ADDRCONFIG for Windows:
196  //   Flags used in "hints" argument to getaddrinfo()
197  //       - AI_ADDRCONFIG is supported starting with Vista
198  //       - default is AI_ADDRCONFIG ON whether the flag is set or not
199  //         because the performance penalty in not having ADDRCONFIG in
200  //         the multi-protocol stack environment is severe;
201  //         this defaulting may be disabled by specifying the AI_ALL flag,
202  //         in that case AI_ADDRCONFIG must be EXPLICITLY specified to
203  //         enable ADDRCONFIG behavior
204  //
205  // Not only is AI_ADDRCONFIG unnecessary, but it can be harmful.  If the
206  // computer is not connected to a network, AI_ADDRCONFIG causes getaddrinfo
207  // to fail with WSANO_DATA (11004) for "localhost", probably because of the
208  // following note on AI_ADDRCONFIG in the MSDN getaddrinfo page:
209  //   The IPv4 or IPv6 loopback address is not considered a valid global
210  //   address.
211  // See http://crbug.com/5234.
212  //
213  // OpenBSD does not support it, either.
214  hints.ai_flags = 0;
215#else
216  hints.ai_flags = AI_ADDRCONFIG;
217#endif
218
219  if (host_resolver_flags & HOST_RESOLVER_CANONNAME)
220    hints.ai_flags |= AI_CANONNAME;
221
222  // Restrict result set to only this socket type to avoid duplicates.
223  hints.ai_socktype = SOCK_STREAM;
224
225  int err = getaddrinfo(host.c_str(), NULL, &hints, &ai);
226#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_OPENBSD)
227  net::DnsReloadTimer* dns_timer = Singleton<net::DnsReloadTimer>::get();
228  // If we fail, re-initialise the resolver just in case there have been any
229  // changes to /etc/resolv.conf and retry. See http://crbug.com/11380 for info.
230  if (err && dns_timer->Expired()) {
231    res_nclose(&_res);
232    if (!res_ninit(&_res))
233      err = getaddrinfo(host.c_str(), NULL, &hints, &ai);
234  }
235#endif
236
237  if (err) {
238    if (os_error) {
239#if defined(OS_WIN)
240      *os_error = WSAGetLastError();
241#else
242      *os_error = err;
243#endif
244    }
245    return ERR_NAME_NOT_RESOLVED;
246  }
247
248  addrlist->Adopt(ai);
249  return OK;
250}
251
252}  // namespace net
253