Evict resources from resource pool after timeout
[chromium-blink-merge.git] / net / dns / dns_config_service_posix.cc
blob02877e78e1db39b195a690aa5a934548efa80072
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.
5 #include "net/dns/dns_config_service_posix.h"
7 #include <string>
9 #include "base/basictypes.h"
10 #include "base/bind.h"
11 #include "base/files/file.h"
12 #include "base/files/file_path.h"
13 #include "base/files/file_path_watcher.h"
14 #include "base/lazy_instance.h"
15 #include "base/location.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/metrics/histogram_macros.h"
18 #include "base/single_thread_task_runner.h"
19 #include "base/thread_task_runner_handle.h"
20 #include "base/time/time.h"
21 #include "net/base/ip_endpoint.h"
22 #include "net/base/net_util.h"
23 #include "net/dns/dns_hosts.h"
24 #include "net/dns/dns_protocol.h"
25 #include "net/dns/notify_watcher_mac.h"
26 #include "net/dns/serial_worker.h"
28 #if defined(OS_MACOSX) && !defined(OS_IOS)
29 #include "net/dns/dns_config_watcher_mac.h"
30 #endif
32 #if defined(OS_ANDROID)
33 #include <sys/system_properties.h>
34 #include "net/base/network_change_notifier.h"
35 #endif
37 namespace net {
39 namespace internal {
41 namespace {
43 #if !defined(OS_ANDROID)
44 const base::FilePath::CharType* kFilePathHosts =
45 FILE_PATH_LITERAL("/etc/hosts");
46 #else
47 const base::FilePath::CharType* kFilePathHosts =
48 FILE_PATH_LITERAL("/system/etc/hosts");
49 #endif
51 #if defined(OS_IOS)
53 // There is no public API to watch the DNS configuration on iOS.
54 class DnsConfigWatcher {
55 public:
56 typedef base::Callback<void(bool succeeded)> CallbackType;
58 bool Watch(const CallbackType& callback) {
59 return false;
63 #elif defined(OS_ANDROID)
64 // On Android, assume DNS config may have changed on every network change.
65 class DnsConfigWatcher {
66 public:
67 bool Watch(const base::Callback<void(bool succeeded)>& callback) {
68 callback_ = callback;
69 return true;
72 void OnNetworkChanged(NetworkChangeNotifier::ConnectionType type) {
73 if (!callback_.is_null() && type != NetworkChangeNotifier::CONNECTION_NONE)
74 callback_.Run(true);
77 private:
78 base::Callback<void(bool succeeded)> callback_;
80 #elif !defined(OS_MACOSX)
81 // DnsConfigWatcher for OS_MACOSX is in dns_config_watcher_mac.{hh,cc}.
83 #ifndef _PATH_RESCONF // Normally defined in <resolv.h>
84 #define _PATH_RESCONF "/etc/resolv.conf"
85 #endif
87 static const base::FilePath::CharType* kFilePathConfig =
88 FILE_PATH_LITERAL(_PATH_RESCONF);
90 class DnsConfigWatcher {
91 public:
92 typedef base::Callback<void(bool succeeded)> CallbackType;
94 bool Watch(const CallbackType& callback) {
95 callback_ = callback;
96 return watcher_.Watch(base::FilePath(kFilePathConfig), false,
97 base::Bind(&DnsConfigWatcher::OnCallback,
98 base::Unretained(this)));
101 private:
102 void OnCallback(const base::FilePath& path, bool error) {
103 callback_.Run(!error);
106 base::FilePathWatcher watcher_;
107 CallbackType callback_;
109 #endif
111 #if !defined(OS_ANDROID)
112 ConfigParsePosixResult ReadDnsConfig(DnsConfig* config) {
113 ConfigParsePosixResult result;
114 config->unhandled_options = false;
115 #if defined(OS_OPENBSD)
116 // Note: res_ninit in glibc always returns 0 and sets RES_INIT.
117 // res_init behaves the same way.
118 memset(&_res, 0, sizeof(_res));
119 if (res_init() == 0) {
120 result = ConvertResStateToDnsConfig(_res, config);
121 } else {
122 result = CONFIG_PARSE_POSIX_RES_INIT_FAILED;
124 #else // all other OS_POSIX
125 struct __res_state res;
126 memset(&res, 0, sizeof(res));
127 if (res_ninit(&res) == 0) {
128 result = ConvertResStateToDnsConfig(res, config);
129 } else {
130 result = CONFIG_PARSE_POSIX_RES_INIT_FAILED;
132 // Prefer res_ndestroy where available.
133 #if defined(OS_MACOSX) || defined(OS_FREEBSD)
134 res_ndestroy(&res);
135 #else
136 res_nclose(&res);
137 #endif
138 #endif
140 #if defined(OS_MACOSX) && !defined(OS_IOS)
141 ConfigParsePosixResult error = DnsConfigWatcher::CheckDnsConfig();
142 switch (error) {
143 case CONFIG_PARSE_POSIX_OK:
144 break;
145 case CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS:
146 LOG(WARNING) << "dns_config has unhandled options!";
147 config->unhandled_options = true;
148 default:
149 return error;
151 #endif // defined(OS_MACOSX) && !defined(OS_IOS)
152 // Override timeout value to match default setting on Windows.
153 config->timeout = base::TimeDelta::FromSeconds(kDnsTimeoutSeconds);
154 return result;
156 #else // defined(OS_ANDROID)
157 // Theoretically, this is bad. __system_property_get is not a supported API
158 // (but it's currently visible to anyone using Bionic), and the properties
159 // are implementation details that may disappear in future Android releases.
160 // Practically, libcutils provides property_get, which is a public API, and the
161 // DNS code (and its clients) are already robust against failing to get the DNS
162 // config for whatever reason, so the properties can disappear and the world
163 // won't end.
164 // TODO(ttuttle): Depend on libcutils, then switch this (and other uses of
165 // __system_property_get) to property_get.
166 ConfigParsePosixResult ReadDnsConfig(DnsConfig* dns_config) {
167 std::string dns1_string, dns2_string;
168 char property_value[PROP_VALUE_MAX];
169 __system_property_get("net.dns1", property_value);
170 dns1_string = property_value;
171 __system_property_get("net.dns2", property_value);
172 dns2_string = property_value;
173 if (dns1_string.length() == 0 && dns2_string.length() == 0)
174 return CONFIG_PARSE_POSIX_NO_NAMESERVERS;
176 IPAddressNumber dns1_number, dns2_number;
177 bool parsed1 = ParseIPLiteralToNumber(dns1_string, &dns1_number);
178 bool parsed2 = ParseIPLiteralToNumber(dns2_string, &dns2_number);
179 if (!parsed1 && !parsed2)
180 return CONFIG_PARSE_POSIX_BAD_ADDRESS;
182 if (parsed1) {
183 IPEndPoint dns1(dns1_number, dns_protocol::kDefaultPort);
184 dns_config->nameservers.push_back(dns1);
186 if (parsed2) {
187 IPEndPoint dns2(dns2_number, dns_protocol::kDefaultPort);
188 dns_config->nameservers.push_back(dns2);
191 return CONFIG_PARSE_POSIX_OK;
193 #endif
195 } // namespace
197 class DnsConfigServicePosix::Watcher {
198 public:
199 explicit Watcher(DnsConfigServicePosix* service)
200 : service_(service), weak_factory_(this) {}
201 ~Watcher() {}
203 bool Watch() {
204 bool success = true;
205 if (!config_watcher_.Watch(base::Bind(&Watcher::OnConfigChanged,
206 base::Unretained(this)))) {
207 LOG(ERROR) << "DNS config watch failed to start.";
208 success = false;
209 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
210 DNS_CONFIG_WATCH_FAILED_TO_START_CONFIG,
211 DNS_CONFIG_WATCH_MAX);
213 if (!hosts_watcher_.Watch(
214 base::FilePath(service_->file_path_hosts_), false,
215 base::Bind(&Watcher::OnHostsChanged, base::Unretained(this)))) {
216 LOG(ERROR) << "DNS hosts watch failed to start.";
217 success = false;
218 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
219 DNS_CONFIG_WATCH_FAILED_TO_START_HOSTS,
220 DNS_CONFIG_WATCH_MAX);
222 return success;
225 #if defined(OS_ANDROID)
226 void OnNetworkChanged(NetworkChangeNotifier::ConnectionType type) {
227 config_watcher_.OnNetworkChanged(type);
229 #endif // defined(OS_ANDROID)
231 private:
232 void OnConfigChanged(bool succeeded) {
233 #if defined(OS_ANDROID)
234 service_->seen_config_change_ = true;
235 #endif // defined(OS_ANDROID)
236 // Ignore transient flutter of resolv.conf by delaying the signal a bit.
237 const base::TimeDelta kDelay = base::TimeDelta::FromMilliseconds(50);
238 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
239 FROM_HERE, base::Bind(&Watcher::OnConfigChangedDelayed,
240 weak_factory_.GetWeakPtr(), succeeded),
241 kDelay);
243 void OnConfigChangedDelayed(bool succeeded) {
244 service_->OnConfigChanged(succeeded);
246 void OnHostsChanged(const base::FilePath& path, bool error) {
247 service_->OnHostsChanged(!error);
250 DnsConfigServicePosix* service_;
251 DnsConfigWatcher config_watcher_;
252 base::FilePathWatcher hosts_watcher_;
254 base::WeakPtrFactory<Watcher> weak_factory_;
256 DISALLOW_COPY_AND_ASSIGN(Watcher);
259 // A SerialWorker that uses libresolv to initialize res_state and converts
260 // it to DnsConfig (except on Android, where it reads system properties
261 // net.dns1 and net.dns2; see #if around ReadDnsConfig above.)
262 class DnsConfigServicePosix::ConfigReader : public SerialWorker {
263 public:
264 explicit ConfigReader(DnsConfigServicePosix* service)
265 : service_(service), success_(false) {}
267 void DoWork() override {
268 base::TimeTicks start_time = base::TimeTicks::Now();
269 ConfigParsePosixResult result = ReadDnsConfig(&dns_config_);
270 if (service_->dns_config_for_testing_) {
271 dns_config_ = *service_->dns_config_for_testing_;
272 result = CONFIG_PARSE_POSIX_OK;
274 switch (result) {
275 case CONFIG_PARSE_POSIX_MISSING_OPTIONS:
276 case CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS:
277 DCHECK(dns_config_.unhandled_options);
278 // Fall through.
279 case CONFIG_PARSE_POSIX_OK:
280 success_ = true;
281 break;
282 default:
283 success_ = false;
284 break;
286 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.ConfigParsePosix",
287 result, CONFIG_PARSE_POSIX_MAX);
288 UMA_HISTOGRAM_BOOLEAN("AsyncDNS.ConfigParseResult", success_);
289 UMA_HISTOGRAM_TIMES("AsyncDNS.ConfigParseDuration",
290 base::TimeTicks::Now() - start_time);
293 void OnWorkFinished() override {
294 DCHECK(!IsCancelled());
295 if (success_) {
296 service_->OnConfigRead(dns_config_);
297 } else {
298 LOG(WARNING) << "Failed to read DnsConfig.";
302 private:
303 ~ConfigReader() override {}
305 DnsConfigServicePosix* service_;
306 // Written in DoWork, read in OnWorkFinished, no locking necessary.
307 DnsConfig dns_config_;
308 bool success_;
310 DISALLOW_COPY_AND_ASSIGN(ConfigReader);
313 // A SerialWorker that reads the HOSTS file and runs Callback.
314 class DnsConfigServicePosix::HostsReader : public SerialWorker {
315 public:
316 explicit HostsReader(DnsConfigServicePosix* service)
317 : service_(service), success_(false) {}
319 private:
320 ~HostsReader() override {}
322 void DoWork() override {
323 base::TimeTicks start_time = base::TimeTicks::Now();
324 success_ =
325 ParseHostsFile(base::FilePath(service_->file_path_hosts_), &hosts_);
326 UMA_HISTOGRAM_BOOLEAN("AsyncDNS.HostParseResult", success_);
327 UMA_HISTOGRAM_TIMES("AsyncDNS.HostsParseDuration",
328 base::TimeTicks::Now() - start_time);
331 void OnWorkFinished() override {
332 if (success_) {
333 service_->OnHostsRead(hosts_);
334 } else {
335 LOG(WARNING) << "Failed to read DnsHosts.";
339 DnsConfigServicePosix* service_;
340 // Written in DoWork, read in OnWorkFinished, no locking necessary.
341 DnsHosts hosts_;
342 bool success_;
344 DISALLOW_COPY_AND_ASSIGN(HostsReader);
347 DnsConfigServicePosix::DnsConfigServicePosix()
348 : file_path_hosts_(kFilePathHosts),
349 dns_config_for_testing_(nullptr),
350 config_reader_(new ConfigReader(this)),
351 hosts_reader_(new HostsReader(this))
352 #if defined(OS_ANDROID)
354 seen_config_change_(false)
355 #endif // defined(OS_ANDROID)
359 DnsConfigServicePosix::~DnsConfigServicePosix() {
360 config_reader_->Cancel();
361 hosts_reader_->Cancel();
364 void DnsConfigServicePosix::ReadNow() {
365 config_reader_->WorkNow();
366 hosts_reader_->WorkNow();
369 bool DnsConfigServicePosix::StartWatching() {
370 // TODO(szym): re-start watcher if that makes sense. http://crbug.com/116139
371 watcher_.reset(new Watcher(this));
372 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus", DNS_CONFIG_WATCH_STARTED,
373 DNS_CONFIG_WATCH_MAX);
374 return watcher_->Watch();
377 void DnsConfigServicePosix::OnConfigChanged(bool succeeded) {
378 InvalidateConfig();
379 if (succeeded) {
380 config_reader_->WorkNow();
381 } else {
382 LOG(ERROR) << "DNS config watch failed.";
383 set_watch_failed(true);
384 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
385 DNS_CONFIG_WATCH_FAILED_CONFIG,
386 DNS_CONFIG_WATCH_MAX);
390 void DnsConfigServicePosix::OnHostsChanged(bool succeeded) {
391 InvalidateHosts();
392 if (succeeded) {
393 hosts_reader_->WorkNow();
394 } else {
395 LOG(ERROR) << "DNS hosts watch failed.";
396 set_watch_failed(true);
397 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
398 DNS_CONFIG_WATCH_FAILED_HOSTS,
399 DNS_CONFIG_WATCH_MAX);
403 void DnsConfigServicePosix::SetDnsConfigForTesting(
404 const DnsConfig* dns_config) {
405 DCHECK(CalledOnValidThread());
406 dns_config_for_testing_ = dns_config;
409 #if !defined(OS_ANDROID)
410 ConfigParsePosixResult ConvertResStateToDnsConfig(const struct __res_state& res,
411 DnsConfig* dns_config) {
412 CHECK(dns_config != NULL);
413 if (!(res.options & RES_INIT))
414 return CONFIG_PARSE_POSIX_RES_INIT_UNSET;
416 dns_config->nameservers.clear();
418 #if defined(OS_MACOSX) || defined(OS_FREEBSD)
419 union res_sockaddr_union addresses[MAXNS];
420 int nscount = res_getservers(const_cast<res_state>(&res), addresses, MAXNS);
421 DCHECK_GE(nscount, 0);
422 DCHECK_LE(nscount, MAXNS);
423 for (int i = 0; i < nscount; ++i) {
424 IPEndPoint ipe;
425 if (!ipe.FromSockAddr(
426 reinterpret_cast<const struct sockaddr*>(&addresses[i]),
427 sizeof addresses[i])) {
428 return CONFIG_PARSE_POSIX_BAD_ADDRESS;
430 dns_config->nameservers.push_back(ipe);
432 #elif defined(OS_LINUX)
433 static_assert(arraysize(res.nsaddr_list) >= MAXNS &&
434 arraysize(res._u._ext.nsaddrs) >= MAXNS,
435 "incompatible libresolv res_state");
436 DCHECK_LE(res.nscount, MAXNS);
437 // Initially, glibc stores IPv6 in |_ext.nsaddrs| and IPv4 in |nsaddr_list|.
438 // In res_send.c:res_nsend, it merges |nsaddr_list| into |nsaddrs|,
439 // but we have to combine the two arrays ourselves.
440 for (int i = 0; i < res.nscount; ++i) {
441 IPEndPoint ipe;
442 const struct sockaddr* addr = NULL;
443 size_t addr_len = 0;
444 if (res.nsaddr_list[i].sin_family) { // The indicator used by res_nsend.
445 addr = reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]);
446 addr_len = sizeof res.nsaddr_list[i];
447 } else if (res._u._ext.nsaddrs[i] != NULL) {
448 addr = reinterpret_cast<const struct sockaddr*>(res._u._ext.nsaddrs[i]);
449 addr_len = sizeof *res._u._ext.nsaddrs[i];
450 } else {
451 return CONFIG_PARSE_POSIX_BAD_EXT_STRUCT;
453 if (!ipe.FromSockAddr(addr, addr_len))
454 return CONFIG_PARSE_POSIX_BAD_ADDRESS;
455 dns_config->nameservers.push_back(ipe);
457 #else // !(defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_FREEBSD))
458 DCHECK_LE(res.nscount, MAXNS);
459 for (int i = 0; i < res.nscount; ++i) {
460 IPEndPoint ipe;
461 if (!ipe.FromSockAddr(
462 reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]),
463 sizeof res.nsaddr_list[i])) {
464 return CONFIG_PARSE_POSIX_BAD_ADDRESS;
466 dns_config->nameservers.push_back(ipe);
468 #endif
470 dns_config->search.clear();
471 for (int i = 0; (i < MAXDNSRCH) && res.dnsrch[i]; ++i) {
472 dns_config->search.push_back(std::string(res.dnsrch[i]));
475 dns_config->ndots = res.ndots;
476 dns_config->timeout = base::TimeDelta::FromSeconds(res.retrans);
477 dns_config->attempts = res.retry;
478 #if defined(RES_ROTATE)
479 dns_config->rotate = res.options & RES_ROTATE;
480 #endif
481 #if defined(RES_USE_EDNS0)
482 dns_config->edns0 = res.options & RES_USE_EDNS0;
483 #endif
484 #if !defined(RES_USE_DNSSEC)
485 // Some versions of libresolv don't have support for the DO bit. In this
486 // case, we proceed without it.
487 static const int RES_USE_DNSSEC = 0;
488 #endif
490 // The current implementation assumes these options are set. They normally
491 // cannot be overwritten by /etc/resolv.conf
492 unsigned kRequiredOptions = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
493 if ((res.options & kRequiredOptions) != kRequiredOptions) {
494 dns_config->unhandled_options = true;
495 return CONFIG_PARSE_POSIX_MISSING_OPTIONS;
498 unsigned kUnhandledOptions = RES_USEVC | RES_IGNTC | RES_USE_DNSSEC;
499 if (res.options & kUnhandledOptions) {
500 dns_config->unhandled_options = true;
501 return CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS;
504 if (dns_config->nameservers.empty())
505 return CONFIG_PARSE_POSIX_NO_NAMESERVERS;
507 // If any name server is 0.0.0.0, assume the configuration is invalid.
508 // TODO(szym): Measure how often this happens. http://crbug.com/125599
509 const IPAddressNumber kEmptyAddress(kIPv4AddressSize);
510 for (unsigned i = 0; i < dns_config->nameservers.size(); ++i) {
511 if (dns_config->nameservers[i].address() == kEmptyAddress)
512 return CONFIG_PARSE_POSIX_NULL_ADDRESS;
514 return CONFIG_PARSE_POSIX_OK;
517 #else // defined(OS_ANDROID)
519 bool DnsConfigServicePosix::SeenChangeSince(
520 const base::Time& since_time) const {
521 DCHECK(CalledOnValidThread());
522 if (seen_config_change_)
523 return true;
524 base::File hosts(base::FilePath(file_path_hosts_),
525 base::File::FLAG_OPEN | base::File::FLAG_READ);
526 base::File::Info hosts_info;
527 // File last modified times are not nearly as accurate as Time::Now() and are
528 // rounded down. This means a file modified at 1:23.456 might only
529 // be given a last modified time of 1:23.450. If we compared the last
530 // modified time directly to |since_time| we might miss changes to the hosts
531 // file because of this rounding down. To account for this the |since_time|
532 // is pushed back by 1s which should more than account for any rounding.
533 // In practice file modified times on Android are two orders of magnitude
534 // more accurate than this 1s. In practice the hosts file on Android always
535 // contains "127.0.0.1 localhost" and is never modified after Android is
536 // installed.
537 return !hosts.GetInfo(&hosts_info) ||
538 hosts_info.last_modified >=
539 (since_time - base::TimeDelta::FromSeconds(1));
542 void DnsConfigServicePosix::OnNetworkChanged(
543 NetworkChangeNotifier::ConnectionType type) {
544 DCHECK(CalledOnValidThread());
545 DCHECK(watcher_);
546 watcher_->OnNetworkChanged(type);
548 #endif // defined(OS_ANDROID)
550 } // namespace internal
552 // static
553 scoped_ptr<DnsConfigService> DnsConfigService::CreateSystemService() {
554 return scoped_ptr<DnsConfigService>(new internal::DnsConfigServicePosix());
557 } // namespace net