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_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"
28 #if defined(OS_ANDROID)
29 #include <sys/system_properties.h>
30 #include "net/base/network_change_notifier.h"
39 #if !defined(OS_ANDROID)
40 const base::FilePath::CharType
* kFilePathHosts
=
41 FILE_PATH_LITERAL("/etc/hosts");
43 const base::FilePath::CharType
* kFilePathHosts
=
44 FILE_PATH_LITERAL("/system/etc/hosts");
49 // There is no public API to watch the DNS configuration on iOS.
50 class DnsConfigWatcher
{
52 typedef base::Callback
<void(bool succeeded
)> CallbackType
;
54 bool Watch(const CallbackType
& callback
) {
59 #elif defined(OS_ANDROID)
60 // On Android, assume DNS config may have changed on every network change.
61 class DnsConfigWatcher
: public NetworkChangeNotifier::NetworkChangeObserver
{
64 NetworkChangeNotifier::AddNetworkChangeObserver(this);
67 ~DnsConfigWatcher() override
{
68 NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
71 bool Watch(const base::Callback
<void(bool succeeded
)>& callback
) {
76 void OnNetworkChanged(NetworkChangeNotifier::ConnectionType type
) override
{
77 if (!callback_
.is_null() && type
!= NetworkChangeNotifier::CONNECTION_NONE
)
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"
91 static const base::FilePath::CharType
* kFilePathConfig
=
92 FILE_PATH_LITERAL(_PATH_RESCONF
);
94 class DnsConfigWatcher
{
96 typedef base::Callback
<void(bool succeeded
)> CallbackType
;
98 bool Watch(const CallbackType
& callback
) {
100 return watcher_
.Watch(base::FilePath(kFilePathConfig
), false,
101 base::Bind(&DnsConfigWatcher::OnCallback
,
102 base::Unretained(this)));
106 void OnCallback(const base::FilePath
& path
, bool error
) {
107 callback_
.Run(!error
);
110 base::FilePathWatcher watcher_
;
111 CallbackType callback_
;
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
);
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
);
134 result
= CONFIG_PARSE_POSIX_RES_INIT_FAILED
;
136 // Prefer res_ndestroy where available.
137 #if defined(OS_MACOSX) || defined(OS_FREEBSD)
144 #if defined(OS_MACOSX) && !defined(OS_IOS)
145 ConfigParsePosixResult error
= DnsConfigWatcher::CheckDnsConfig();
147 case CONFIG_PARSE_POSIX_OK
:
149 case CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS
:
150 LOG(WARNING
) << "dns_config has unhandled options!";
151 config
->unhandled_options
= true;
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
);
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
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
;
187 IPEndPoint
dns1(dns1_number
, dns_protocol::kDefaultPort
);
188 dns_config
->nameservers
.push_back(dns1
);
191 IPEndPoint
dns2(dns2_number
, dns_protocol::kDefaultPort
);
192 dns_config
->nameservers
.push_back(dns2
);
195 return CONFIG_PARSE_POSIX_OK
;
201 class DnsConfigServicePosix::Watcher
{
203 explicit Watcher(DnsConfigServicePosix
* service
)
205 weak_factory_(this) {}
210 if (!config_watcher_
.Watch(base::Bind(&Watcher::OnConfigChanged
,
211 base::Unretained(this)))) {
212 LOG(ERROR
) << "DNS config watch failed to start.";
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.";
223 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
224 DNS_CONFIG_WATCH_FAILED_TO_START_HOSTS
,
225 DNS_CONFIG_WATCH_MAX
);
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(
236 base::Bind(&Watcher::OnConfigChangedDelayed
,
237 weak_factory_
.GetWeakPtr(),
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
{
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_
);
269 case CONFIG_PARSE_POSIX_MISSING_OPTIONS
:
270 case CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS
:
271 DCHECK(dns_config_
.unhandled_options
);
273 case CONFIG_PARSE_POSIX_OK
:
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());
290 service_
->OnConfigRead(dns_config_
);
292 LOG(WARNING
) << "Failed to read DnsConfig.";
297 ~ConfigReader() override
{}
299 DnsConfigServicePosix
* service_
;
300 // Written in DoWork, read in OnWorkFinished, no locking necessary.
301 DnsConfig dns_config_
;
304 DISALLOW_COPY_AND_ASSIGN(ConfigReader
);
307 // A SerialWorker that reads the HOSTS file and runs Callback.
308 class DnsConfigServicePosix::HostsReader
: public SerialWorker
{
310 explicit HostsReader(DnsConfigServicePosix
* service
)
311 : service_(service
), path_(kFilePathHosts
), success_(false) {}
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
{
326 service_
->OnHostsRead(hosts_
);
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.
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
) {
366 config_reader_
->WorkNow();
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
) {
379 hosts_reader_
->WorkNow();
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
) {
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
) {
422 const struct sockaddr
* addr
= NULL
;
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
];
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
) {
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
);
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
;
461 #if defined(RES_USE_EDNS0)
462 dns_config
->edns0
= res
.options
& RES_USE_EDNS0
;
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;
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
501 scoped_ptr
<DnsConfigService
> DnsConfigService::CreateSystemService() {
502 return scoped_ptr
<DnsConfigService
>(new internal::DnsConfigServicePosix());