Add UMA histograms and logging for bad IPC message handling
[chromium-blink-merge.git] / net / dns / dns_config_service_posix.cc
blob135f3fd68767a064c58a15652d8e549f7336d5da
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_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"
24 #if defined(OS_MACOSX) && !defined(OS_IOS)
25 #include "net/dns/dns_config_watcher_mac.h"
26 #endif
28 #if defined(OS_ANDROID)
29 #include <sys/system_properties.h>
30 #include "net/base/network_change_notifier.h"
31 #endif
33 namespace net {
35 namespace internal {
37 namespace {
39 #if !defined(OS_ANDROID)
40 const base::FilePath::CharType* kFilePathHosts =
41 FILE_PATH_LITERAL("/etc/hosts");
42 #else
43 const base::FilePath::CharType* kFilePathHosts =
44 FILE_PATH_LITERAL("/system/etc/hosts");
45 #endif
47 #if defined(OS_IOS)
49 // There is no public API to watch the DNS configuration on iOS.
50 class DnsConfigWatcher {
51 public:
52 typedef base::Callback<void(bool succeeded)> CallbackType;
54 bool Watch(const CallbackType& callback) {
55 return false;
59 #elif defined(OS_ANDROID)
60 // On Android, assume DNS config may have changed on every network change.
61 class DnsConfigWatcher : public NetworkChangeNotifier::NetworkChangeObserver {
62 public:
63 DnsConfigWatcher() {
64 NetworkChangeNotifier::AddNetworkChangeObserver(this);
67 ~DnsConfigWatcher() override {
68 NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
71 bool Watch(const base::Callback<void(bool succeeded)>& callback) {
72 callback_ = callback;
73 return true;
76 void OnNetworkChanged(NetworkChangeNotifier::ConnectionType type) override {
77 if (!callback_.is_null() && type != NetworkChangeNotifier::CONNECTION_NONE)
78 callback_.Run(true);
81 private:
82 base::Callback<void(bool succeeded)> callback_;
84 #elif !defined(OS_MACOSX)
85 // DnsConfigWatcher for OS_MACOSX is in dns_config_watcher_mac.{hh,cc}.
87 #ifndef _PATH_RESCONF // Normally defined in <resolv.h>
88 #define _PATH_RESCONF "/etc/resolv.conf"
89 #endif
91 static const base::FilePath::CharType* kFilePathConfig =
92 FILE_PATH_LITERAL(_PATH_RESCONF);
94 class DnsConfigWatcher {
95 public:
96 typedef base::Callback<void(bool succeeded)> CallbackType;
98 bool Watch(const CallbackType& callback) {
99 callback_ = callback;
100 return watcher_.Watch(base::FilePath(kFilePathConfig), false,
101 base::Bind(&DnsConfigWatcher::OnCallback,
102 base::Unretained(this)));
105 private:
106 void OnCallback(const base::FilePath& path, bool error) {
107 callback_.Run(!error);
110 base::FilePathWatcher watcher_;
111 CallbackType callback_;
113 #endif
115 #if !defined(OS_ANDROID)
116 ConfigParsePosixResult ReadDnsConfig(DnsConfig* config) {
117 ConfigParsePosixResult result;
118 config->unhandled_options = false;
119 #if defined(OS_OPENBSD)
120 // Note: res_ninit in glibc always returns 0 and sets RES_INIT.
121 // res_init behaves the same way.
122 memset(&_res, 0, sizeof(_res));
123 if (res_init() == 0) {
124 result = ConvertResStateToDnsConfig(_res, config);
125 } else {
126 result = CONFIG_PARSE_POSIX_RES_INIT_FAILED;
128 #else // all other OS_POSIX
129 struct __res_state res;
130 memset(&res, 0, sizeof(res));
131 if (res_ninit(&res) == 0) {
132 result = ConvertResStateToDnsConfig(res, config);
133 } else {
134 result = CONFIG_PARSE_POSIX_RES_INIT_FAILED;
136 // Prefer res_ndestroy where available.
137 #if defined(OS_MACOSX) || defined(OS_FREEBSD)
138 res_ndestroy(&res);
139 #else
140 res_nclose(&res);
141 #endif
142 #endif
144 #if defined(OS_MACOSX) && !defined(OS_IOS)
145 ConfigParsePosixResult error = DnsConfigWatcher::CheckDnsConfig();
146 switch (error) {
147 case CONFIG_PARSE_POSIX_OK:
148 break;
149 case CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS:
150 LOG(WARNING) << "dns_config has unhandled options!";
151 config->unhandled_options = true;
152 default:
153 return error;
155 #endif // defined(OS_MACOSX) && !defined(OS_IOS)
156 // Override timeout value to match default setting on Windows.
157 config->timeout = base::TimeDelta::FromSeconds(kDnsTimeoutSeconds);
158 return result;
160 #else // defined(OS_ANDROID)
161 // Theoretically, this is bad. __system_property_get is not a supported API
162 // (but it's currently visible to anyone using Bionic), and the properties
163 // are implementation details that may disappear in future Android releases.
164 // Practically, libcutils provides property_get, which is a public API, and the
165 // DNS code (and its clients) are already robust against failing to get the DNS
166 // config for whatever reason, so the properties can disappear and the world
167 // won't end.
168 // TODO(ttuttle): Depend on libcutils, then switch this (and other uses of
169 // __system_property_get) to property_get.
170 ConfigParsePosixResult ReadDnsConfig(DnsConfig* dns_config) {
171 std::string dns1_string, dns2_string;
172 char property_value[PROP_VALUE_MAX];
173 __system_property_get("net.dns1", property_value);
174 dns1_string = property_value;
175 __system_property_get("net.dns2", property_value);
176 dns2_string = property_value;
177 if (dns1_string.length() == 0 && dns2_string.length() == 0)
178 return CONFIG_PARSE_POSIX_NO_NAMESERVERS;
180 IPAddressNumber dns1_number, dns2_number;
181 bool parsed1 = ParseIPLiteralToNumber(dns1_string, &dns1_number);
182 bool parsed2 = ParseIPLiteralToNumber(dns2_string, &dns2_number);
183 if (!parsed1 && !parsed2)
184 return CONFIG_PARSE_POSIX_BAD_ADDRESS;
186 if (parsed1) {
187 IPEndPoint dns1(dns1_number, dns_protocol::kDefaultPort);
188 dns_config->nameservers.push_back(dns1);
190 if (parsed2) {
191 IPEndPoint dns2(dns2_number, dns_protocol::kDefaultPort);
192 dns_config->nameservers.push_back(dns2);
195 return CONFIG_PARSE_POSIX_OK;
197 #endif
199 } // namespace
201 class DnsConfigServicePosix::Watcher {
202 public:
203 explicit Watcher(DnsConfigServicePosix* service)
204 : service_(service),
205 weak_factory_(this) {}
206 ~Watcher() {}
208 bool Watch() {
209 bool success = true;
210 if (!config_watcher_.Watch(base::Bind(&Watcher::OnConfigChanged,
211 base::Unretained(this)))) {
212 LOG(ERROR) << "DNS config watch failed to start.";
213 success = false;
214 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
215 DNS_CONFIG_WATCH_FAILED_TO_START_CONFIG,
216 DNS_CONFIG_WATCH_MAX);
218 if (!hosts_watcher_.Watch(base::FilePath(kFilePathHosts), false,
219 base::Bind(&Watcher::OnHostsChanged,
220 base::Unretained(this)))) {
221 LOG(ERROR) << "DNS hosts watch failed to start.";
222 success = false;
223 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
224 DNS_CONFIG_WATCH_FAILED_TO_START_HOSTS,
225 DNS_CONFIG_WATCH_MAX);
227 return success;
230 private:
231 void OnConfigChanged(bool succeeded) {
232 // Ignore transient flutter of resolv.conf by delaying the signal a bit.
233 const base::TimeDelta kDelay = base::TimeDelta::FromMilliseconds(50);
234 base::MessageLoop::current()->PostDelayedTask(
235 FROM_HERE,
236 base::Bind(&Watcher::OnConfigChangedDelayed,
237 weak_factory_.GetWeakPtr(),
238 succeeded),
239 kDelay);
241 void OnConfigChangedDelayed(bool succeeded) {
242 service_->OnConfigChanged(succeeded);
244 void OnHostsChanged(const base::FilePath& path, bool error) {
245 service_->OnHostsChanged(!error);
248 DnsConfigServicePosix* service_;
249 DnsConfigWatcher config_watcher_;
250 base::FilePathWatcher hosts_watcher_;
252 base::WeakPtrFactory<Watcher> weak_factory_;
254 DISALLOW_COPY_AND_ASSIGN(Watcher);
257 // A SerialWorker that uses libresolv to initialize res_state and converts
258 // it to DnsConfig (except on Android, where it reads system properties
259 // net.dns1 and net.dns2; see #if around ReadDnsConfig above.)
260 class DnsConfigServicePosix::ConfigReader : public SerialWorker {
261 public:
262 explicit ConfigReader(DnsConfigServicePosix* service)
263 : service_(service), success_(false) {}
265 void DoWork() override {
266 base::TimeTicks start_time = base::TimeTicks::Now();
267 ConfigParsePosixResult result = ReadDnsConfig(&dns_config_);
268 switch (result) {
269 case CONFIG_PARSE_POSIX_MISSING_OPTIONS:
270 case CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS:
271 DCHECK(dns_config_.unhandled_options);
272 // Fall through.
273 case CONFIG_PARSE_POSIX_OK:
274 success_ = true;
275 break;
276 default:
277 success_ = false;
278 break;
280 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.ConfigParsePosix",
281 result, CONFIG_PARSE_POSIX_MAX);
282 UMA_HISTOGRAM_BOOLEAN("AsyncDNS.ConfigParseResult", success_);
283 UMA_HISTOGRAM_TIMES("AsyncDNS.ConfigParseDuration",
284 base::TimeTicks::Now() - start_time);
287 void OnWorkFinished() override {
288 DCHECK(!IsCancelled());
289 if (success_) {
290 service_->OnConfigRead(dns_config_);
291 } else {
292 LOG(WARNING) << "Failed to read DnsConfig.";
296 private:
297 ~ConfigReader() override {}
299 DnsConfigServicePosix* service_;
300 // Written in DoWork, read in OnWorkFinished, no locking necessary.
301 DnsConfig dns_config_;
302 bool success_;
304 DISALLOW_COPY_AND_ASSIGN(ConfigReader);
307 // A SerialWorker that reads the HOSTS file and runs Callback.
308 class DnsConfigServicePosix::HostsReader : public SerialWorker {
309 public:
310 explicit HostsReader(DnsConfigServicePosix* service)
311 : service_(service), path_(kFilePathHosts), success_(false) {}
313 private:
314 ~HostsReader() override {}
316 void DoWork() override {
317 base::TimeTicks start_time = base::TimeTicks::Now();
318 success_ = ParseHostsFile(path_, &hosts_);
319 UMA_HISTOGRAM_BOOLEAN("AsyncDNS.HostParseResult", success_);
320 UMA_HISTOGRAM_TIMES("AsyncDNS.HostsParseDuration",
321 base::TimeTicks::Now() - start_time);
324 void OnWorkFinished() override {
325 if (success_) {
326 service_->OnHostsRead(hosts_);
327 } else {
328 LOG(WARNING) << "Failed to read DnsHosts.";
332 DnsConfigServicePosix* service_;
333 const base::FilePath path_;
334 // Written in DoWork, read in OnWorkFinished, no locking necessary.
335 DnsHosts hosts_;
336 bool success_;
338 DISALLOW_COPY_AND_ASSIGN(HostsReader);
341 DnsConfigServicePosix::DnsConfigServicePosix()
342 : config_reader_(new ConfigReader(this)),
343 hosts_reader_(new HostsReader(this)) {}
345 DnsConfigServicePosix::~DnsConfigServicePosix() {
346 config_reader_->Cancel();
347 hosts_reader_->Cancel();
350 void DnsConfigServicePosix::ReadNow() {
351 config_reader_->WorkNow();
352 hosts_reader_->WorkNow();
355 bool DnsConfigServicePosix::StartWatching() {
356 // TODO(szym): re-start watcher if that makes sense. http://crbug.com/116139
357 watcher_.reset(new Watcher(this));
358 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus", DNS_CONFIG_WATCH_STARTED,
359 DNS_CONFIG_WATCH_MAX);
360 return watcher_->Watch();
363 void DnsConfigServicePosix::OnConfigChanged(bool succeeded) {
364 InvalidateConfig();
365 if (succeeded) {
366 config_reader_->WorkNow();
367 } else {
368 LOG(ERROR) << "DNS config watch failed.";
369 set_watch_failed(true);
370 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
371 DNS_CONFIG_WATCH_FAILED_CONFIG,
372 DNS_CONFIG_WATCH_MAX);
376 void DnsConfigServicePosix::OnHostsChanged(bool succeeded) {
377 InvalidateHosts();
378 if (succeeded) {
379 hosts_reader_->WorkNow();
380 } else {
381 LOG(ERROR) << "DNS hosts watch failed.";
382 set_watch_failed(true);
383 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
384 DNS_CONFIG_WATCH_FAILED_HOSTS,
385 DNS_CONFIG_WATCH_MAX);
389 #if !defined(OS_ANDROID)
390 ConfigParsePosixResult ConvertResStateToDnsConfig(const struct __res_state& res,
391 DnsConfig* dns_config) {
392 CHECK(dns_config != NULL);
393 if (!(res.options & RES_INIT))
394 return CONFIG_PARSE_POSIX_RES_INIT_UNSET;
396 dns_config->nameservers.clear();
398 #if defined(OS_MACOSX) || defined(OS_FREEBSD)
399 union res_sockaddr_union addresses[MAXNS];
400 int nscount = res_getservers(const_cast<res_state>(&res), addresses, MAXNS);
401 DCHECK_GE(nscount, 0);
402 DCHECK_LE(nscount, MAXNS);
403 for (int i = 0; i < nscount; ++i) {
404 IPEndPoint ipe;
405 if (!ipe.FromSockAddr(
406 reinterpret_cast<const struct sockaddr*>(&addresses[i]),
407 sizeof addresses[i])) {
408 return CONFIG_PARSE_POSIX_BAD_ADDRESS;
410 dns_config->nameservers.push_back(ipe);
412 #elif defined(OS_LINUX)
413 static_assert(arraysize(res.nsaddr_list) >= MAXNS &&
414 arraysize(res._u._ext.nsaddrs) >= MAXNS,
415 "incompatible libresolv res_state");
416 DCHECK_LE(res.nscount, MAXNS);
417 // Initially, glibc stores IPv6 in |_ext.nsaddrs| and IPv4 in |nsaddr_list|.
418 // In res_send.c:res_nsend, it merges |nsaddr_list| into |nsaddrs|,
419 // but we have to combine the two arrays ourselves.
420 for (int i = 0; i < res.nscount; ++i) {
421 IPEndPoint ipe;
422 const struct sockaddr* addr = NULL;
423 size_t addr_len = 0;
424 if (res.nsaddr_list[i].sin_family) { // The indicator used by res_nsend.
425 addr = reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]);
426 addr_len = sizeof res.nsaddr_list[i];
427 } else if (res._u._ext.nsaddrs[i] != NULL) {
428 addr = reinterpret_cast<const struct sockaddr*>(res._u._ext.nsaddrs[i]);
429 addr_len = sizeof *res._u._ext.nsaddrs[i];
430 } else {
431 return CONFIG_PARSE_POSIX_BAD_EXT_STRUCT;
433 if (!ipe.FromSockAddr(addr, addr_len))
434 return CONFIG_PARSE_POSIX_BAD_ADDRESS;
435 dns_config->nameservers.push_back(ipe);
437 #else // !(defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_FREEBSD))
438 DCHECK_LE(res.nscount, MAXNS);
439 for (int i = 0; i < res.nscount; ++i) {
440 IPEndPoint ipe;
441 if (!ipe.FromSockAddr(
442 reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]),
443 sizeof res.nsaddr_list[i])) {
444 return CONFIG_PARSE_POSIX_BAD_ADDRESS;
446 dns_config->nameservers.push_back(ipe);
448 #endif
450 dns_config->search.clear();
451 for (int i = 0; (i < MAXDNSRCH) && res.dnsrch[i]; ++i) {
452 dns_config->search.push_back(std::string(res.dnsrch[i]));
455 dns_config->ndots = res.ndots;
456 dns_config->timeout = base::TimeDelta::FromSeconds(res.retrans);
457 dns_config->attempts = res.retry;
458 #if defined(RES_ROTATE)
459 dns_config->rotate = res.options & RES_ROTATE;
460 #endif
461 #if defined(RES_USE_EDNS0)
462 dns_config->edns0 = res.options & RES_USE_EDNS0;
463 #endif
464 #if !defined(RES_USE_DNSSEC)
465 // Some versions of libresolv don't have support for the DO bit. In this
466 // case, we proceed without it.
467 static const int RES_USE_DNSSEC = 0;
468 #endif
470 // The current implementation assumes these options are set. They normally
471 // cannot be overwritten by /etc/resolv.conf
472 unsigned kRequiredOptions = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
473 if ((res.options & kRequiredOptions) != kRequiredOptions) {
474 dns_config->unhandled_options = true;
475 return CONFIG_PARSE_POSIX_MISSING_OPTIONS;
478 unsigned kUnhandledOptions = RES_USEVC | RES_IGNTC | RES_USE_DNSSEC;
479 if (res.options & kUnhandledOptions) {
480 dns_config->unhandled_options = true;
481 return CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS;
484 if (dns_config->nameservers.empty())
485 return CONFIG_PARSE_POSIX_NO_NAMESERVERS;
487 // If any name server is 0.0.0.0, assume the configuration is invalid.
488 // TODO(szym): Measure how often this happens. http://crbug.com/125599
489 const IPAddressNumber kEmptyAddress(kIPv4AddressSize);
490 for (unsigned i = 0; i < dns_config->nameservers.size(); ++i) {
491 if (dns_config->nameservers[i].address() == kEmptyAddress)
492 return CONFIG_PARSE_POSIX_NULL_ADDRESS;
494 return CONFIG_PARSE_POSIX_OK;
496 #endif // !defined(OS_ANDROID)
498 } // namespace internal
500 // static
501 scoped_ptr<DnsConfigService> DnsConfigService::CreateSystemService() {
502 return scoped_ptr<DnsConfigService>(new internal::DnsConfigServicePosix());
505 } // namespace net