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
33namespace net {
34
35namespace internal {
36
37namespace {
38
39#if !defined(OS_ANDROID)
40const base::FilePath::CharType* kFilePathHosts =
41    FILE_PATH_LITERAL("/etc/hosts");
42#else
43const 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.
50class DnsConfigWatcher {
51 public:
52  typedef base::Callback<void(bool succeeded)> CallbackType;
53
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.
61class DnsConfigWatcher : public NetworkChangeNotifier::NetworkChangeObserver {
62 public:
63  DnsConfigWatcher() {
64    NetworkChangeNotifier::AddNetworkChangeObserver(this);
65  }
66
67  virtual ~DnsConfigWatcher() {
68    NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
69  }
70
71  bool Watch(const base::Callback<void(bool succeeded)>& callback) {
72    callback_ = callback;
73    return true;
74  }
75
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
92static const base::FilePath::CharType* kFilePathConfig =
93    FILE_PATH_LITERAL(_PATH_RESCONF);
94
95class DnsConfigWatcher {
96 public:
97  typedef base::Callback<void(bool succeeded)> CallbackType;
98
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:
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)
117ConfigParsePosixResult 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.
171ConfigParsePosixResult 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
202class DnsConfigServicePosix::Watcher {
203 public:
204  explicit Watcher(DnsConfigServicePosix* service)
205      : service_(service),
206        weak_factory_(this) {}
207  ~Watcher() {}
208
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:
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  }
242  void OnConfigChangedDelayed(bool succeeded) {
243    service_->OnConfigChanged(succeeded);
244  }
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.)
261class DnsConfigServicePosix::ConfigReader : public SerialWorker {
262 public:
263  explicit ConfigReader(DnsConfigServicePosix* service)
264      : service_(service), success_(false) {}
265
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
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:
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.
309class DnsConfigServicePosix::HostsReader : public SerialWorker {
310 public:
311  explicit HostsReader(DnsConfigServicePosix* service)
312      :  service_(service), path_(kFilePathHosts), success_(false) {}
313
314 private:
315  virtual ~HostsReader() {}
316
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
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
342DnsConfigServicePosix::DnsConfigServicePosix()
343    : config_reader_(new ConfigReader(this)),
344      hosts_reader_(new HostsReader(this)) {}
345
346DnsConfigServicePosix::~DnsConfigServicePosix() {
347  config_reader_->Cancel();
348  hosts_reader_->Cancel();
349}
350
351void DnsConfigServicePosix::ReadNow() {
352  config_reader_->WorkNow();
353  hosts_reader_->WorkNow();
354}
355
356bool 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
364void 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
377void 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)
391ConfigParsePosixResult 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
495scoped_ptr<DnsConfigService> DnsConfigService::CreateSystemService() {
496  return scoped_ptr<DnsConfigService>(new internal::DnsConfigServicePosix());
497}
498
499}  // namespace net
500