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"
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"
32 #if defined(OS_ANDROID)
33 #include <sys/system_properties.h>
34 #include "net/base/network_change_notifier.h"
43 #if !defined(OS_ANDROID)
44 const base::FilePath::CharType
* kFilePathHosts
=
45 FILE_PATH_LITERAL("/etc/hosts");
47 const base::FilePath::CharType
* kFilePathHosts
=
48 FILE_PATH_LITERAL("/system/etc/hosts");
53 // There is no public API to watch the DNS configuration on iOS.
54 class DnsConfigWatcher
{
56 typedef base::Callback
<void(bool succeeded
)> CallbackType
;
58 bool Watch(const CallbackType
& callback
) {
63 #elif defined(OS_ANDROID)
64 // On Android, assume DNS config may have changed on every network change.
65 class DnsConfigWatcher
{
67 bool Watch(const base::Callback
<void(bool succeeded
)>& callback
) {
72 void OnNetworkChanged(NetworkChangeNotifier::ConnectionType type
) {
73 if (!callback_
.is_null() && type
!= NetworkChangeNotifier::CONNECTION_NONE
)
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"
87 static const base::FilePath::CharType
* kFilePathConfig
=
88 FILE_PATH_LITERAL(_PATH_RESCONF
);
90 class DnsConfigWatcher
{
92 typedef base::Callback
<void(bool succeeded
)> CallbackType
;
94 bool Watch(const CallbackType
& callback
) {
96 return watcher_
.Watch(base::FilePath(kFilePathConfig
), false,
97 base::Bind(&DnsConfigWatcher::OnCallback
,
98 base::Unretained(this)));
102 void OnCallback(const base::FilePath
& path
, bool error
) {
103 callback_
.Run(!error
);
106 base::FilePathWatcher watcher_
;
107 CallbackType callback_
;
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
);
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
);
130 result
= CONFIG_PARSE_POSIX_RES_INIT_FAILED
;
132 // Prefer res_ndestroy where available.
133 #if defined(OS_MACOSX) || defined(OS_FREEBSD)
140 #if defined(OS_MACOSX) && !defined(OS_IOS)
141 ConfigParsePosixResult error
= DnsConfigWatcher::CheckDnsConfig();
143 case CONFIG_PARSE_POSIX_OK
:
145 case CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS
:
146 LOG(WARNING
) << "dns_config has unhandled options!";
147 config
->unhandled_options
= true;
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
);
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
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
;
183 IPEndPoint
dns1(dns1_number
, dns_protocol::kDefaultPort
);
184 dns_config
->nameservers
.push_back(dns1
);
187 IPEndPoint
dns2(dns2_number
, dns_protocol::kDefaultPort
);
188 dns_config
->nameservers
.push_back(dns2
);
191 return CONFIG_PARSE_POSIX_OK
;
197 class DnsConfigServicePosix::Watcher
{
199 explicit Watcher(DnsConfigServicePosix
* service
)
200 : service_(service
), weak_factory_(this) {}
205 if (!config_watcher_
.Watch(base::Bind(&Watcher::OnConfigChanged
,
206 base::Unretained(this)))) {
207 LOG(ERROR
) << "DNS config watch failed to start.";
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.";
218 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
219 DNS_CONFIG_WATCH_FAILED_TO_START_HOSTS
,
220 DNS_CONFIG_WATCH_MAX
);
225 #if defined(OS_ANDROID)
226 void OnNetworkChanged(NetworkChangeNotifier::ConnectionType type
) {
227 config_watcher_
.OnNetworkChanged(type
);
229 #endif // defined(OS_ANDROID)
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
),
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
{
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
;
275 case CONFIG_PARSE_POSIX_MISSING_OPTIONS
:
276 case CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS
:
277 DCHECK(dns_config_
.unhandled_options
);
279 case CONFIG_PARSE_POSIX_OK
:
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());
296 service_
->OnConfigRead(dns_config_
);
298 LOG(WARNING
) << "Failed to read DnsConfig.";
303 ~ConfigReader() override
{}
305 DnsConfigServicePosix
* service_
;
306 // Written in DoWork, read in OnWorkFinished, no locking necessary.
307 DnsConfig dns_config_
;
310 DISALLOW_COPY_AND_ASSIGN(ConfigReader
);
313 // A SerialWorker that reads the HOSTS file and runs Callback.
314 class DnsConfigServicePosix::HostsReader
: public SerialWorker
{
316 explicit HostsReader(DnsConfigServicePosix
* service
)
317 : service_(service
), success_(false) {}
320 ~HostsReader() override
{}
322 void DoWork() override
{
323 base::TimeTicks start_time
= base::TimeTicks::Now();
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
{
333 service_
->OnHostsRead(hosts_
);
335 LOG(WARNING
) << "Failed to read DnsHosts.";
339 DnsConfigServicePosix
* service_
;
340 // Written in DoWork, read in OnWorkFinished, no locking necessary.
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
) {
380 config_reader_
->WorkNow();
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
) {
393 hosts_reader_
->WorkNow();
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
) {
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
) {
442 const struct sockaddr
* addr
= NULL
;
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
];
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
) {
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
);
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
;
481 #if defined(RES_USE_EDNS0)
482 dns_config
->edns0
= res
.options
& RES_USE_EDNS0
;
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;
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_
)
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
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());
546 watcher_
->OnNetworkChanged(type
);
548 #endif // defined(OS_ANDROID)
550 } // namespace internal
553 scoped_ptr
<DnsConfigService
> DnsConfigService::CreateSystemService() {
554 return scoped_ptr
<DnsConfigService
>(new internal::DnsConfigServicePosix());