OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "net/dns/dns_config_service_posix.h" | |
6 | |
7 #include <string> | |
8 | |
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" | |
23 | |
24 #if defined(OS_MACOSX) && !defined(OS_IOS) | |
25 #include "net/dns/dns_config_watcher_mac.h" | |
26 #endif | |
27 | |
28 #if defined(OS_ANDROID) | |
29 #include <sys/system_properties.h> | |
30 #include "net/base/network_change_notifier.h" | |
31 #endif | |
32 | |
33 namespace net { | |
34 | |
35 namespace internal { | |
36 | |
37 namespace { | |
38 | |
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 | |
46 | |
47 #if defined(OS_IOS) | |
48 | |
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; | |
53 | |
54 bool Watch(const CallbackType& callback) { | |
55 return false; | |
56 } | |
57 }; | |
58 | |
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); | |
65 } | |
66 | |
67 ~DnsConfigWatcher() override { | |
68 NetworkChangeNotifier::RemoveNetworkChangeObserver(this); | |
69 } | |
70 | |
71 bool Watch(const base::Callback<void(bool succeeded)>& callback) { | |
72 callback_ = callback; | |
73 return true; | |
74 } | |
75 | |
76 void OnNetworkChanged(NetworkChangeNotifier::ConnectionType type) override { | |
77 if (!callback_.is_null() && type != NetworkChangeNotifier::CONNECTION_NONE) | |
78 callback_.Run(true); | |
79 } | |
80 | |
81 private: | |
82 base::Callback<void(bool succeeded)> callback_; | |
83 }; | |
84 #elif !defined(OS_MACOSX) | |
85 // DnsConfigWatcher for OS_MACOSX is in dns_config_watcher_mac.{hh,cc}. | |
86 | |
87 #ifndef _PATH_RESCONF // Normally defined in <resolv.h> | |
88 #define _PATH_RESCONF "/etc/resolv.conf" | |
89 #endif | |
90 | |
91 static const base::FilePath::CharType* kFilePathConfig = | |
92 FILE_PATH_LITERAL(_PATH_RESCONF); | |
93 | |
94 class DnsConfigWatcher { | |
95 public: | |
96 typedef base::Callback<void(bool succeeded)> CallbackType; | |
97 | |
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))); | |
103 } | |
104 | |
105 private: | |
106 void OnCallback(const base::FilePath& path, bool error) { | |
107 callback_.Run(!error); | |
108 } | |
109 | |
110 base::FilePathWatcher watcher_; | |
111 CallbackType callback_; | |
112 }; | |
113 #endif | |
114 | |
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; | |
127 } | |
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; | |
135 } | |
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 | |
143 | |
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; | |
154 } | |
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; | |
159 } | |
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; | |
179 | |
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; | |
185 | |
186 if (parsed1) { | |
187 IPEndPoint dns1(dns1_number, dns_protocol::kDefaultPort); | |
188 dns_config->nameservers.push_back(dns1); | |
189 } | |
190 if (parsed2) { | |
191 IPEndPoint dns2(dns2_number, dns_protocol::kDefaultPort); | |
192 dns_config->nameservers.push_back(dns2); | |
193 } | |
194 | |
195 return CONFIG_PARSE_POSIX_OK; | |
196 } | |
197 #endif | |
198 | |
199 } // namespace | |
200 | |
201 class DnsConfigServicePosix::Watcher { | |
202 public: | |
203 explicit Watcher(DnsConfigServicePosix* service) | |
204 : service_(service), | |
205 weak_factory_(this) {} | |
206 ~Watcher() {} | |
207 | |
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); | |
217 } | |
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); | |
226 } | |
227 return success; | |
228 } | |
229 | |
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); | |
240 } | |
241 void OnConfigChangedDelayed(bool succeeded) { | |
242 service_->OnConfigChanged(succeeded); | |
243 } | |
244 void OnHostsChanged(const base::FilePath& path, bool error) { | |
245 service_->OnHostsChanged(!error); | |
246 } | |
247 | |
248 DnsConfigServicePosix* service_; | |
249 DnsConfigWatcher config_watcher_; | |
250 base::FilePathWatcher hosts_watcher_; | |
251 | |
252 base::WeakPtrFactory<Watcher> weak_factory_; | |
253 | |
254 DISALLOW_COPY_AND_ASSIGN(Watcher); | |
255 }; | |
256 | |
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) {} | |
264 | |
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; | |
279 } | |
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); | |
285 } | |
286 | |
287 void OnWorkFinished() override { | |
288 DCHECK(!IsCancelled()); | |
289 if (success_) { | |
290 service_->OnConfigRead(dns_config_); | |
291 } else { | |
292 LOG(WARNING) << "Failed to read DnsConfig."; | |
293 } | |
294 } | |
295 | |
296 private: | |
297 ~ConfigReader() override {} | |
298 | |
299 DnsConfigServicePosix* service_; | |
300 // Written in DoWork, read in OnWorkFinished, no locking necessary. | |
301 DnsConfig dns_config_; | |
302 bool success_; | |
303 | |
304 DISALLOW_COPY_AND_ASSIGN(ConfigReader); | |
305 }; | |
306 | |
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) {} | |
312 | |
313 private: | |
314 ~HostsReader() override {} | |
315 | |
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); | |
322 } | |
323 | |
324 void OnWorkFinished() override { | |
325 if (success_) { | |
326 service_->OnHostsRead(hosts_); | |
327 } else { | |
328 LOG(WARNING) << "Failed to read DnsHosts."; | |
329 } | |
330 } | |
331 | |
332 DnsConfigServicePosix* service_; | |
333 const base::FilePath path_; | |
334 // Written in DoWork, read in OnWorkFinished, no locking necessary. | |
335 DnsHosts hosts_; | |
336 bool success_; | |
337 | |
338 DISALLOW_COPY_AND_ASSIGN(HostsReader); | |
339 }; | |
340 | |
341 DnsConfigServicePosix::DnsConfigServicePosix() | |
342 : config_reader_(new ConfigReader(this)), | |
343 hosts_reader_(new HostsReader(this)) {} | |
344 | |
345 DnsConfigServicePosix::~DnsConfigServicePosix() { | |
346 config_reader_->Cancel(); | |
347 hosts_reader_->Cancel(); | |
348 } | |
349 | |
350 void DnsConfigServicePosix::ReadNow() { | |
351 config_reader_->WorkNow(); | |
352 hosts_reader_->WorkNow(); | |
353 } | |
354 | |
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(); | |
361 } | |
362 | |
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); | |
373 } | |
374 } | |
375 | |
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); | |
386 } | |
387 } | |
388 | |
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; | |
395 | |
396 dns_config->nameservers.clear(); | |
397 | |
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; | |
409 } | |
410 dns_config->nameservers.push_back(ipe); | |
411 } | |
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; | |
432 } | |
433 if (!ipe.FromSockAddr(addr, addr_len)) | |
434 return CONFIG_PARSE_POSIX_BAD_ADDRESS; | |
435 dns_config->nameservers.push_back(ipe); | |
436 } | |
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; | |
445 } | |
446 dns_config->nameservers.push_back(ipe); | |
447 } | |
448 #endif | |
449 | |
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])); | |
453 } | |
454 | |
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 | |
469 | |
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; | |
476 } | |
477 | |
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; | |
482 } | |
483 | |
484 if (dns_config->nameservers.empty()) | |
485 return CONFIG_PARSE_POSIX_NO_NAMESERVERS; | |
486 | |
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; | |
493 } | |
494 return CONFIG_PARSE_POSIX_OK; | |
495 } | |
496 #endif // !defined(OS_ANDROID) | |
497 | |
498 } // namespace internal | |
499 | |
500 // static | |
501 scoped_ptr<DnsConfigService> DnsConfigService::CreateSystemService() { | |
502 return scoped_ptr<DnsConfigService>(new internal::DnsConfigServicePosix()); | |
503 } | |
504 | |
505 } // namespace net | |
OLD | NEW |