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