• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 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/dns/dns_config_service_posix.h"
6 
7 #include <string>
8 
9 #include "base/basictypes.h"
10 #include "base/bind.h"
11 #include "base/files/file_path.h"
12 #include "base/files/file_path_watcher.h"
13 #include "base/lazy_instance.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/metrics/histogram.h"
16 #include "base/time/time.h"
17 #include "net/base/ip_endpoint.h"
18 #include "net/base/net_util.h"
19 #include "net/dns/dns_hosts.h"
20 #include "net/dns/dns_protocol.h"
21 #include "net/dns/notify_watcher_mac.h"
22 #include "net/dns/serial_worker.h"
23 
24 #if defined(OS_MACOSX) && !defined(OS_IOS)
25 #include "net/dns/dns_config_watcher_mac.h"
26 #endif
27 
28 namespace net {
29 
30 #if !defined(OS_ANDROID)
31 namespace internal {
32 
33 namespace {
34 
35 const base::FilePath::CharType* kFilePathHosts =
36     FILE_PATH_LITERAL("/etc/hosts");
37 
38 #if defined(OS_IOS)
39 
40 // There is no plublic API to watch the DNS configuration on iOS.
41 class DnsConfigWatcher {
42  public:
43   typedef base::Callback<void(bool succeeded)> CallbackType;
44 
Watch(const CallbackType & callback)45   bool Watch(const CallbackType& callback) {
46     return false;
47   }
48 };
49 
50 #elif !defined(OS_MACOSX)
51 // DnsConfigWatcher for OS_MACOSX is in dns_config_watcher_mac.{hh,cc}.
52 
53 #ifndef _PATH_RESCONF  // Normally defined in <resolv.h>
54 #define _PATH_RESCONF "/etc/resolv.conf"
55 #endif
56 
57 static const base::FilePath::CharType* kFilePathConfig =
58     FILE_PATH_LITERAL(_PATH_RESCONF);
59 
60 class DnsConfigWatcher {
61  public:
62   typedef base::Callback<void(bool succeeded)> CallbackType;
63 
Watch(const CallbackType & callback)64   bool Watch(const CallbackType& callback) {
65     callback_ = callback;
66     return watcher_.Watch(base::FilePath(kFilePathConfig), false,
67                           base::Bind(&DnsConfigWatcher::OnCallback,
68                                      base::Unretained(this)));
69   }
70 
71  private:
OnCallback(const base::FilePath & path,bool error)72   void OnCallback(const base::FilePath& path, bool error) {
73     callback_.Run(!error);
74   }
75 
76   base::FilePathWatcher watcher_;
77   CallbackType callback_;
78 };
79 #endif
80 
ReadDnsConfig(DnsConfig * config)81 ConfigParsePosixResult ReadDnsConfig(DnsConfig* config) {
82   ConfigParsePosixResult result;
83   config->unhandled_options = false;
84 #if defined(OS_OPENBSD)
85   // Note: res_ninit in glibc always returns 0 and sets RES_INIT.
86   // res_init behaves the same way.
87   memset(&_res, 0, sizeof(_res));
88   if (res_init() == 0) {
89     result = ConvertResStateToDnsConfig(_res, config);
90   } else {
91     result = CONFIG_PARSE_POSIX_RES_INIT_FAILED;
92   }
93 #else  // all other OS_POSIX
94   struct __res_state res;
95   memset(&res, 0, sizeof(res));
96   if (res_ninit(&res) == 0) {
97     result = ConvertResStateToDnsConfig(res, config);
98   } else {
99     result = CONFIG_PARSE_POSIX_RES_INIT_FAILED;
100   }
101   // Prefer res_ndestroy where available.
102 #if defined(OS_MACOSX) || defined(OS_FREEBSD)
103   res_ndestroy(&res);
104 #else
105   res_nclose(&res);
106 #endif
107 #endif
108 
109 #if defined(OS_MACOSX) && !defined(OS_IOS)
110   ConfigParsePosixResult error = DnsConfigWatcher::CheckDnsConfig();
111   switch (error) {
112     case CONFIG_PARSE_POSIX_OK:
113       break;
114     case CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS:
115       LOG(WARNING) << "dns_config has unhandled options!";
116       config->unhandled_options = true;
117     default:
118       return error;
119   }
120 #endif  // defined(OS_MACOSX) && !defined(OS_IOS)
121   // Override timeout value to match default setting on Windows.
122   config->timeout = base::TimeDelta::FromSeconds(kDnsTimeoutSeconds);
123   return result;
124 }
125 
126 }  // namespace
127 
128 class DnsConfigServicePosix::Watcher {
129  public:
Watcher(DnsConfigServicePosix * service)130   explicit Watcher(DnsConfigServicePosix* service)
131       : weak_factory_(this),
132         service_(service) {}
~Watcher()133   ~Watcher() {}
134 
Watch()135   bool Watch() {
136     bool success = true;
137     if (!config_watcher_.Watch(base::Bind(&Watcher::OnConfigChanged,
138                                           base::Unretained(this)))) {
139       LOG(ERROR) << "DNS config watch failed to start.";
140       success = false;
141       UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
142                                 DNS_CONFIG_WATCH_FAILED_TO_START_CONFIG,
143                                 DNS_CONFIG_WATCH_MAX);
144     }
145     if (!hosts_watcher_.Watch(base::FilePath(kFilePathHosts), false,
146                               base::Bind(&Watcher::OnHostsChanged,
147                                          base::Unretained(this)))) {
148       LOG(ERROR) << "DNS hosts watch failed to start.";
149       success = false;
150       UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
151                                 DNS_CONFIG_WATCH_FAILED_TO_START_HOSTS,
152                                 DNS_CONFIG_WATCH_MAX);
153     }
154     return success;
155   }
156 
157  private:
OnConfigChanged(bool succeeded)158   void OnConfigChanged(bool succeeded) {
159     // Ignore transient flutter of resolv.conf by delaying the signal a bit.
160     const base::TimeDelta kDelay = base::TimeDelta::FromMilliseconds(50);
161     base::MessageLoop::current()->PostDelayedTask(
162         FROM_HERE,
163         base::Bind(&Watcher::OnConfigChangedDelayed,
164                    weak_factory_.GetWeakPtr(),
165                    succeeded),
166         kDelay);
167   }
OnConfigChangedDelayed(bool succeeded)168   void OnConfigChangedDelayed(bool succeeded) {
169     service_->OnConfigChanged(succeeded);
170   }
OnHostsChanged(const base::FilePath & path,bool error)171   void OnHostsChanged(const base::FilePath& path, bool error) {
172     service_->OnHostsChanged(!error);
173   }
174 
175   base::WeakPtrFactory<Watcher> weak_factory_;
176   DnsConfigServicePosix* service_;
177   DnsConfigWatcher config_watcher_;
178   base::FilePathWatcher hosts_watcher_;
179 
180   DISALLOW_COPY_AND_ASSIGN(Watcher);
181 };
182 
183 // A SerialWorker that uses libresolv to initialize res_state and converts
184 // it to DnsConfig.
185 class DnsConfigServicePosix::ConfigReader : public SerialWorker {
186  public:
ConfigReader(DnsConfigServicePosix * service)187   explicit ConfigReader(DnsConfigServicePosix* service)
188       : service_(service), success_(false) {}
189 
DoWork()190   virtual void DoWork() OVERRIDE {
191     base::TimeTicks start_time = base::TimeTicks::Now();
192     ConfigParsePosixResult result = ReadDnsConfig(&dns_config_);
193     switch (result) {
194       case CONFIG_PARSE_POSIX_MISSING_OPTIONS:
195       case CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS:
196         DCHECK(dns_config_.unhandled_options);
197         // Fall through.
198       case CONFIG_PARSE_POSIX_OK:
199         success_ = true;
200         break;
201       default:
202         success_ = false;
203         break;
204     }
205     UMA_HISTOGRAM_ENUMERATION("AsyncDNS.ConfigParsePosix",
206                               result, CONFIG_PARSE_POSIX_MAX);
207     UMA_HISTOGRAM_BOOLEAN("AsyncDNS.ConfigParseResult", success_);
208     UMA_HISTOGRAM_TIMES("AsyncDNS.ConfigParseDuration",
209                         base::TimeTicks::Now() - start_time);
210   }
211 
OnWorkFinished()212   virtual void OnWorkFinished() OVERRIDE {
213     DCHECK(!IsCancelled());
214     if (success_) {
215       service_->OnConfigRead(dns_config_);
216     } else {
217       LOG(WARNING) << "Failed to read DnsConfig.";
218     }
219   }
220 
221  private:
~ConfigReader()222   virtual ~ConfigReader() {}
223 
224   DnsConfigServicePosix* service_;
225   // Written in DoWork, read in OnWorkFinished, no locking necessary.
226   DnsConfig dns_config_;
227   bool success_;
228 
229   DISALLOW_COPY_AND_ASSIGN(ConfigReader);
230 };
231 
232 // A SerialWorker that reads the HOSTS file and runs Callback.
233 class DnsConfigServicePosix::HostsReader : public SerialWorker {
234  public:
HostsReader(DnsConfigServicePosix * service)235   explicit HostsReader(DnsConfigServicePosix* service)
236       :  service_(service), path_(kFilePathHosts), success_(false) {}
237 
238  private:
~HostsReader()239   virtual ~HostsReader() {}
240 
DoWork()241   virtual void DoWork() OVERRIDE {
242     base::TimeTicks start_time = base::TimeTicks::Now();
243     success_ = ParseHostsFile(path_, &hosts_);
244     UMA_HISTOGRAM_BOOLEAN("AsyncDNS.HostParseResult", success_);
245     UMA_HISTOGRAM_TIMES("AsyncDNS.HostsParseDuration",
246                         base::TimeTicks::Now() - start_time);
247   }
248 
OnWorkFinished()249   virtual void OnWorkFinished() OVERRIDE {
250     if (success_) {
251       service_->OnHostsRead(hosts_);
252     } else {
253       LOG(WARNING) << "Failed to read DnsHosts.";
254     }
255   }
256 
257   DnsConfigServicePosix* service_;
258   const base::FilePath path_;
259   // Written in DoWork, read in OnWorkFinished, no locking necessary.
260   DnsHosts hosts_;
261   bool success_;
262 
263   DISALLOW_COPY_AND_ASSIGN(HostsReader);
264 };
265 
DnsConfigServicePosix()266 DnsConfigServicePosix::DnsConfigServicePosix()
267     : config_reader_(new ConfigReader(this)),
268       hosts_reader_(new HostsReader(this)) {}
269 
~DnsConfigServicePosix()270 DnsConfigServicePosix::~DnsConfigServicePosix() {
271   config_reader_->Cancel();
272   hosts_reader_->Cancel();
273 }
274 
ReadNow()275 void DnsConfigServicePosix::ReadNow() {
276   config_reader_->WorkNow();
277   hosts_reader_->WorkNow();
278 }
279 
StartWatching()280 bool DnsConfigServicePosix::StartWatching() {
281   // TODO(szym): re-start watcher if that makes sense. http://crbug.com/116139
282   watcher_.reset(new Watcher(this));
283   UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus", DNS_CONFIG_WATCH_STARTED,
284                             DNS_CONFIG_WATCH_MAX);
285   return watcher_->Watch();
286 }
287 
OnConfigChanged(bool succeeded)288 void DnsConfigServicePosix::OnConfigChanged(bool succeeded) {
289   InvalidateConfig();
290   if (succeeded) {
291     config_reader_->WorkNow();
292   } else {
293     LOG(ERROR) << "DNS config watch failed.";
294     set_watch_failed(true);
295     UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
296                               DNS_CONFIG_WATCH_FAILED_CONFIG,
297                               DNS_CONFIG_WATCH_MAX);
298   }
299 }
300 
OnHostsChanged(bool succeeded)301 void DnsConfigServicePosix::OnHostsChanged(bool succeeded) {
302   InvalidateHosts();
303   if (succeeded) {
304     hosts_reader_->WorkNow();
305   } else {
306     LOG(ERROR) << "DNS hosts watch failed.";
307     set_watch_failed(true);
308     UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
309                               DNS_CONFIG_WATCH_FAILED_HOSTS,
310                               DNS_CONFIG_WATCH_MAX);
311   }
312 }
313 
ConvertResStateToDnsConfig(const struct __res_state & res,DnsConfig * dns_config)314 ConfigParsePosixResult ConvertResStateToDnsConfig(const struct __res_state& res,
315                                                   DnsConfig* dns_config) {
316   CHECK(dns_config != NULL);
317   if (!(res.options & RES_INIT))
318     return CONFIG_PARSE_POSIX_RES_INIT_UNSET;
319 
320   dns_config->nameservers.clear();
321 
322 #if defined(OS_MACOSX) || defined(OS_FREEBSD)
323   union res_sockaddr_union addresses[MAXNS];
324   int nscount = res_getservers(const_cast<res_state>(&res), addresses, MAXNS);
325   DCHECK_GE(nscount, 0);
326   DCHECK_LE(nscount, MAXNS);
327   for (int i = 0; i < nscount; ++i) {
328     IPEndPoint ipe;
329     if (!ipe.FromSockAddr(
330             reinterpret_cast<const struct sockaddr*>(&addresses[i]),
331             sizeof addresses[i])) {
332       return CONFIG_PARSE_POSIX_BAD_ADDRESS;
333     }
334     dns_config->nameservers.push_back(ipe);
335   }
336 #elif defined(OS_LINUX)
337   COMPILE_ASSERT(arraysize(res.nsaddr_list) >= MAXNS &&
338                  arraysize(res._u._ext.nsaddrs) >= MAXNS,
339                  incompatible_libresolv_res_state);
340   DCHECK_LE(res.nscount, MAXNS);
341   // Initially, glibc stores IPv6 in |_ext.nsaddrs| and IPv4 in |nsaddr_list|.
342   // In res_send.c:res_nsend, it merges |nsaddr_list| into |nsaddrs|,
343   // but we have to combine the two arrays ourselves.
344   for (int i = 0; i < res.nscount; ++i) {
345     IPEndPoint ipe;
346     const struct sockaddr* addr = NULL;
347     size_t addr_len = 0;
348     if (res.nsaddr_list[i].sin_family) {  // The indicator used by res_nsend.
349       addr = reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]);
350       addr_len = sizeof res.nsaddr_list[i];
351     } else if (res._u._ext.nsaddrs[i] != NULL) {
352       addr = reinterpret_cast<const struct sockaddr*>(res._u._ext.nsaddrs[i]);
353       addr_len = sizeof *res._u._ext.nsaddrs[i];
354     } else {
355       return CONFIG_PARSE_POSIX_BAD_EXT_STRUCT;
356     }
357     if (!ipe.FromSockAddr(addr, addr_len))
358       return CONFIG_PARSE_POSIX_BAD_ADDRESS;
359     dns_config->nameservers.push_back(ipe);
360   }
361 #else  // !(defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_FREEBSD))
362   DCHECK_LE(res.nscount, MAXNS);
363   for (int i = 0; i < res.nscount; ++i) {
364     IPEndPoint ipe;
365     if (!ipe.FromSockAddr(
366             reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]),
367             sizeof res.nsaddr_list[i])) {
368       return CONFIG_PARSE_POSIX_BAD_ADDRESS;
369     }
370     dns_config->nameservers.push_back(ipe);
371   }
372 #endif
373 
374   dns_config->search.clear();
375   for (int i = 0; (i < MAXDNSRCH) && res.dnsrch[i]; ++i) {
376     dns_config->search.push_back(std::string(res.dnsrch[i]));
377   }
378 
379   dns_config->ndots = res.ndots;
380   dns_config->timeout = base::TimeDelta::FromSeconds(res.retrans);
381   dns_config->attempts = res.retry;
382 #if defined(RES_ROTATE)
383   dns_config->rotate = res.options & RES_ROTATE;
384 #endif
385   dns_config->edns0 = res.options & RES_USE_EDNS0;
386 
387   // The current implementation assumes these options are set. They normally
388   // cannot be overwritten by /etc/resolv.conf
389   unsigned kRequiredOptions = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
390   if ((res.options & kRequiredOptions) != kRequiredOptions) {
391     dns_config->unhandled_options = true;
392     return CONFIG_PARSE_POSIX_MISSING_OPTIONS;
393   }
394 
395   unsigned kUnhandledOptions = RES_USEVC | RES_IGNTC | RES_USE_DNSSEC;
396   if (res.options & kUnhandledOptions) {
397     dns_config->unhandled_options = true;
398     return CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS;
399   }
400 
401   if (dns_config->nameservers.empty())
402     return CONFIG_PARSE_POSIX_NO_NAMESERVERS;
403 
404   // If any name server is 0.0.0.0, assume the configuration is invalid.
405   // TODO(szym): Measure how often this happens. http://crbug.com/125599
406   const IPAddressNumber kEmptyAddress(kIPv4AddressSize);
407   for (unsigned i = 0; i < dns_config->nameservers.size(); ++i) {
408     if (dns_config->nameservers[i].address() == kEmptyAddress)
409       return CONFIG_PARSE_POSIX_NULL_ADDRESS;
410   }
411   return CONFIG_PARSE_POSIX_OK;
412 }
413 
414 }  // namespace internal
415 
416 // static
CreateSystemService()417 scoped_ptr<DnsConfigService> DnsConfigService::CreateSystemService() {
418   return scoped_ptr<DnsConfigService>(new internal::DnsConfigServicePosix());
419 }
420 
421 #else  // defined(OS_ANDROID)
422 // Android NDK provides only a stub <resolv.h> header.
423 class StubDnsConfigService : public DnsConfigService {
424  public:
425   StubDnsConfigService() {}
426   virtual ~StubDnsConfigService() {}
427  private:
428   virtual void ReadNow() OVERRIDE {}
429   virtual bool StartWatching() OVERRIDE { return false; }
430 };
431 // static
432 scoped_ptr<DnsConfigService> DnsConfigService::CreateSystemService() {
433   return scoped_ptr<DnsConfigService>(new StubDnsConfigService());
434 }
435 #endif
436 
437 }  // namespace net
438