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