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