Home | History | Annotate | Download | only in dns
      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 
     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 
     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 
     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:
    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.
    185 class 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.
    233 class 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 
    266 DnsConfigServicePosix::DnsConfigServicePosix()
    267     : config_reader_(new ConfigReader(this)),
    268       hosts_reader_(new HostsReader(this)) {}
    269 
    270 DnsConfigServicePosix::~DnsConfigServicePosix() {
    271   config_reader_->Cancel();
    272   hosts_reader_->Cancel();
    273 }
    274 
    275 void DnsConfigServicePosix::ReadNow() {
    276   config_reader_->WorkNow();
    277   hosts_reader_->WorkNow();
    278 }
    279 
    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 
    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 
    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 
    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
    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