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/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 
     23 namespace net {
     24 
     25 #if !defined(OS_ANDROID)
     26 namespace internal {
     27 
     28 namespace {
     29 
     30 const 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
     35 static const char* kDnsNotifyKey =
     36     "com.apple.system.SystemConfiguration.dns_configuration";
     37 
     38 class 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 
     53 static const base::FilePath::CharType* kFilePathConfig =
     54     FILE_PATH_LITERAL(_PATH_RESCONF);
     55 
     56 class 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 
     77 ConfigParsePosixResult 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 
    110 class 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.
    167 class 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.
    204 class 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 
    237 DnsConfigServicePosix::DnsConfigServicePosix()
    238     : config_reader_(new ConfigReader(this)),
    239       hosts_reader_(new HostsReader(this)) {}
    240 
    241 DnsConfigServicePosix::~DnsConfigServicePosix() {
    242   config_reader_->Cancel();
    243   hosts_reader_->Cancel();
    244 }
    245 
    246 void DnsConfigServicePosix::ReadNow() {
    247   config_reader_->WorkNow();
    248   hosts_reader_->WorkNow();
    249 }
    250 
    251 bool 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 
    259 void 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 
    272 void 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 
    285 ConfigParsePosixResult 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
    384 scoped_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.
    390 class 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
    399 scoped_ptr<DnsConfigService> DnsConfigService::CreateSystemService() {
    400   return scoped_ptr<DnsConfigService>(new StubDnsConfigService());
    401 }
    402 #endif
    403 
    404 }  // namespace net
    405