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