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
28namespace net {
29
30#if !defined(OS_ANDROID)
31namespace internal {
32
33namespace {
34
35const 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.
41class DnsConfigWatcher {
42 public:
43  typedef base::Callback<void(bool succeeded)> CallbackType;
44
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
57static const base::FilePath::CharType* kFilePathConfig =
58    FILE_PATH_LITERAL(_PATH_RESCONF);
59
60class DnsConfigWatcher {
61 public:
62  typedef base::Callback<void(bool succeeded)> CallbackType;
63
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:
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
81ConfigParsePosixResult 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
128class DnsConfigServicePosix::Watcher {
129 public:
130  explicit Watcher(DnsConfigServicePosix* service)
131      : weak_factory_(this),
132        service_(service) {}
133  ~Watcher() {}
134
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:
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  }
168  void OnConfigChangedDelayed(bool succeeded) {
169    service_->OnConfigChanged(succeeded);
170  }
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.
185class DnsConfigServicePosix::ConfigReader : public SerialWorker {
186 public:
187  explicit ConfigReader(DnsConfigServicePosix* service)
188      : service_(service), success_(false) {}
189
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
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:
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.
233class DnsConfigServicePosix::HostsReader : public SerialWorker {
234 public:
235  explicit HostsReader(DnsConfigServicePosix* service)
236      :  service_(service), path_(kFilePathHosts), success_(false) {}
237
238 private:
239  virtual ~HostsReader() {}
240
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
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
266DnsConfigServicePosix::DnsConfigServicePosix()
267    : config_reader_(new ConfigReader(this)),
268      hosts_reader_(new HostsReader(this)) {}
269
270DnsConfigServicePosix::~DnsConfigServicePosix() {
271  config_reader_->Cancel();
272  hosts_reader_->Cancel();
273}
274
275void DnsConfigServicePosix::ReadNow() {
276  config_reader_->WorkNow();
277  hosts_reader_->WorkNow();
278}
279
280bool 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
288void 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
301void 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
314ConfigParsePosixResult 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
417scoped_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.
423class 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
432scoped_ptr<DnsConfigService> DnsConfigService::CreateSystemService() {
433  return scoped_ptr<DnsConfigService>(new StubDnsConfigService());
434}
435#endif
436
437}  // namespace net
438