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/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"
29 #if defined(OS_ANDROID)
30 #include <sys/system_properties.h>
31 #include "net/base/network_change_notifier.h"
40 #if !defined(OS_ANDROID)
41 const base::FilePath::CharType
* kFilePathHosts
=
42 FILE_PATH_LITERAL("/etc/hosts");
44 const base::FilePath::CharType
* kFilePathHosts
=
45 FILE_PATH_LITERAL("/system/etc/hosts");
50 // There is no public API to watch the DNS configuration on iOS.
51 class DnsConfigWatcher
{
53 typedef base::Callback
<void(bool succeeded
)> CallbackType
;
55 bool Watch(const CallbackType
& callback
) {
60 #elif defined(OS_ANDROID)
61 // On Android, assume DNS config may have changed on every network change.
62 class DnsConfigWatcher
{
64 bool Watch(const base::Callback
<void(bool succeeded
)>& callback
) {
69 void OnNetworkChanged(NetworkChangeNotifier::ConnectionType type
) {
70 if (!callback_
.is_null() && type
!= NetworkChangeNotifier::CONNECTION_NONE
)
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"
84 static const base::FilePath::CharType
* kFilePathConfig
=
85 FILE_PATH_LITERAL(_PATH_RESCONF
);
87 class DnsConfigWatcher
{
89 typedef base::Callback
<void(bool succeeded
)> CallbackType
;
91 bool Watch(const CallbackType
& callback
) {
93 return watcher_
.Watch(base::FilePath(kFilePathConfig
), false,
94 base::Bind(&DnsConfigWatcher::OnCallback
,
95 base::Unretained(this)));
99 void OnCallback(const base::FilePath
& path
, bool error
) {
100 callback_
.Run(!error
);
103 base::FilePathWatcher watcher_
;
104 CallbackType callback_
;
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
);
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
);
127 result
= CONFIG_PARSE_POSIX_RES_INIT_FAILED
;
129 // Prefer res_ndestroy where available.
130 #if defined(OS_MACOSX) || defined(OS_FREEBSD)
137 #if defined(OS_MACOSX) && !defined(OS_IOS)
138 ConfigParsePosixResult error
= DnsConfigWatcher::CheckDnsConfig();
140 case CONFIG_PARSE_POSIX_OK
:
142 case CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS
:
143 LOG(WARNING
) << "dns_config has unhandled options!";
144 config
->unhandled_options
= true;
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
);
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
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
;
180 IPEndPoint
dns1(dns1_number
, dns_protocol::kDefaultPort
);
181 dns_config
->nameservers
.push_back(dns1
);
184 IPEndPoint
dns2(dns2_number
, dns_protocol::kDefaultPort
);
185 dns_config
->nameservers
.push_back(dns2
);
188 return CONFIG_PARSE_POSIX_OK
;
194 class DnsConfigServicePosix::Watcher
{
196 explicit Watcher(DnsConfigServicePosix
* service
)
197 : service_(service
), weak_factory_(this) {}
202 if (!config_watcher_
.Watch(base::Bind(&Watcher::OnConfigChanged
,
203 base::Unretained(this)))) {
204 LOG(ERROR
) << "DNS config watch failed to start.";
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.";
215 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
216 DNS_CONFIG_WATCH_FAILED_TO_START_HOSTS
,
217 DNS_CONFIG_WATCH_MAX
);
222 #if defined(OS_ANDROID)
223 void OnNetworkChanged(NetworkChangeNotifier::ConnectionType type
) {
224 config_watcher_
.OnNetworkChanged(type
);
226 #endif // defined(OS_ANDROID)
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(
237 base::Bind(&Watcher::OnConfigChangedDelayed
,
238 weak_factory_
.GetWeakPtr(),
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
{
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
;
274 case CONFIG_PARSE_POSIX_MISSING_OPTIONS
:
275 case CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS
:
276 DCHECK(dns_config_
.unhandled_options
);
278 case CONFIG_PARSE_POSIX_OK
:
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());
295 service_
->OnConfigRead(dns_config_
);
297 LOG(WARNING
) << "Failed to read DnsConfig.";
302 ~ConfigReader() override
{}
304 DnsConfigServicePosix
* service_
;
305 // Written in DoWork, read in OnWorkFinished, no locking necessary.
306 DnsConfig dns_config_
;
309 DISALLOW_COPY_AND_ASSIGN(ConfigReader
);
312 // A SerialWorker that reads the HOSTS file and runs Callback.
313 class DnsConfigServicePosix::HostsReader
: public SerialWorker
{
315 explicit HostsReader(DnsConfigServicePosix
* service
)
316 : service_(service
), success_(false) {}
319 ~HostsReader() override
{}
321 void DoWork() override
{
322 base::TimeTicks start_time
= base::TimeTicks::Now();
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
{
332 service_
->OnHostsRead(hosts_
);
334 LOG(WARNING
) << "Failed to read DnsHosts.";
338 DnsConfigServicePosix
* service_
;
339 // Written in DoWork, read in OnWorkFinished, no locking necessary.
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
) {
379 config_reader_
->WorkNow();
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
) {
392 hosts_reader_
->WorkNow();
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
) {
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
) {
441 const struct sockaddr
* addr
= NULL
;
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
];
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
) {
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
);
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
;
480 #if defined(RES_USE_EDNS0)
481 dns_config
->edns0
= res
.options
& RES_USE_EDNS0
;
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;
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_
)
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
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());
545 watcher_
->OnNetworkChanged(type
);
547 #endif // defined(OS_ANDROID)
549 } // namespace internal
552 scoped_ptr
<DnsConfigService
> DnsConfigService::CreateSystemService() {
553 return scoped_ptr
<DnsConfigService
>(new internal::DnsConfigServicePosix());