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 namespace net {
29
30 #if !defined(OS_ANDROID)
31 namespace internal {
32
33 namespace {
34
35 const base::FilePath::CharType* kFilePathHosts =
36 FILE_PATH_LITERAL("/etc/hosts");
37
38 #if defined(OS_IOS)
39
40 // There is no plublic API to watch the DNS configuration on iOS.
41 class DnsConfigWatcher {
42 public:
43 typedef base::Callback<void(bool succeeded)> CallbackType;
44
Watch(const CallbackType & callback)45 bool Watch(const CallbackType& callback) {
46 return false;
47 }
48 };
49
50 #elif !defined(OS_MACOSX)
51 // DnsConfigWatcher for OS_MACOSX is in dns_config_watcher_mac.{hh,cc}.
52
53 #ifndef _PATH_RESCONF // Normally defined in <resolv.h>
54 #define _PATH_RESCONF "/etc/resolv.conf"
55 #endif
56
57 static const base::FilePath::CharType* kFilePathConfig =
58 FILE_PATH_LITERAL(_PATH_RESCONF);
59
60 class DnsConfigWatcher {
61 public:
62 typedef base::Callback<void(bool succeeded)> CallbackType;
63
Watch(const CallbackType & callback)64 bool Watch(const CallbackType& callback) {
65 callback_ = callback;
66 return watcher_.Watch(base::FilePath(kFilePathConfig), false,
67 base::Bind(&DnsConfigWatcher::OnCallback,
68 base::Unretained(this)));
69 }
70
71 private:
OnCallback(const base::FilePath & path,bool error)72 void OnCallback(const base::FilePath& path, bool error) {
73 callback_.Run(!error);
74 }
75
76 base::FilePathWatcher watcher_;
77 CallbackType callback_;
78 };
79 #endif
80
ReadDnsConfig(DnsConfig * config)81 ConfigParsePosixResult ReadDnsConfig(DnsConfig* config) {
82 ConfigParsePosixResult result;
83 config->unhandled_options = false;
84 #if defined(OS_OPENBSD)
85 // Note: res_ninit in glibc always returns 0 and sets RES_INIT.
86 // res_init behaves the same way.
87 memset(&_res, 0, sizeof(_res));
88 if (res_init() == 0) {
89 result = ConvertResStateToDnsConfig(_res, config);
90 } else {
91 result = CONFIG_PARSE_POSIX_RES_INIT_FAILED;
92 }
93 #else // all other OS_POSIX
94 struct __res_state res;
95 memset(&res, 0, sizeof(res));
96 if (res_ninit(&res) == 0) {
97 result = ConvertResStateToDnsConfig(res, config);
98 } else {
99 result = CONFIG_PARSE_POSIX_RES_INIT_FAILED;
100 }
101 // Prefer res_ndestroy where available.
102 #if defined(OS_MACOSX) || defined(OS_FREEBSD)
103 res_ndestroy(&res);
104 #else
105 res_nclose(&res);
106 #endif
107 #endif
108
109 #if defined(OS_MACOSX) && !defined(OS_IOS)
110 ConfigParsePosixResult error = DnsConfigWatcher::CheckDnsConfig();
111 switch (error) {
112 case CONFIG_PARSE_POSIX_OK:
113 break;
114 case CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS:
115 LOG(WARNING) << "dns_config has unhandled options!";
116 config->unhandled_options = true;
117 default:
118 return error;
119 }
120 #endif // defined(OS_MACOSX) && !defined(OS_IOS)
121 // Override timeout value to match default setting on Windows.
122 config->timeout = base::TimeDelta::FromSeconds(kDnsTimeoutSeconds);
123 return result;
124 }
125
126 } // namespace
127
128 class DnsConfigServicePosix::Watcher {
129 public:
Watcher(DnsConfigServicePosix * service)130 explicit Watcher(DnsConfigServicePosix* service)
131 : weak_factory_(this),
132 service_(service) {}
~Watcher()133 ~Watcher() {}
134
Watch()135 bool Watch() {
136 bool success = true;
137 if (!config_watcher_.Watch(base::Bind(&Watcher::OnConfigChanged,
138 base::Unretained(this)))) {
139 LOG(ERROR) << "DNS config watch failed to start.";
140 success = false;
141 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
142 DNS_CONFIG_WATCH_FAILED_TO_START_CONFIG,
143 DNS_CONFIG_WATCH_MAX);
144 }
145 if (!hosts_watcher_.Watch(base::FilePath(kFilePathHosts), false,
146 base::Bind(&Watcher::OnHostsChanged,
147 base::Unretained(this)))) {
148 LOG(ERROR) << "DNS hosts watch failed to start.";
149 success = false;
150 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
151 DNS_CONFIG_WATCH_FAILED_TO_START_HOSTS,
152 DNS_CONFIG_WATCH_MAX);
153 }
154 return success;
155 }
156
157 private:
OnConfigChanged(bool succeeded)158 void OnConfigChanged(bool succeeded) {
159 // Ignore transient flutter of resolv.conf by delaying the signal a bit.
160 const base::TimeDelta kDelay = base::TimeDelta::FromMilliseconds(50);
161 base::MessageLoop::current()->PostDelayedTask(
162 FROM_HERE,
163 base::Bind(&Watcher::OnConfigChangedDelayed,
164 weak_factory_.GetWeakPtr(),
165 succeeded),
166 kDelay);
167 }
OnConfigChangedDelayed(bool succeeded)168 void OnConfigChangedDelayed(bool succeeded) {
169 service_->OnConfigChanged(succeeded);
170 }
OnHostsChanged(const base::FilePath & path,bool error)171 void OnHostsChanged(const base::FilePath& path, bool error) {
172 service_->OnHostsChanged(!error);
173 }
174
175 base::WeakPtrFactory<Watcher> weak_factory_;
176 DnsConfigServicePosix* service_;
177 DnsConfigWatcher config_watcher_;
178 base::FilePathWatcher hosts_watcher_;
179
180 DISALLOW_COPY_AND_ASSIGN(Watcher);
181 };
182
183 // A SerialWorker that uses libresolv to initialize res_state and converts
184 // it to DnsConfig.
185 class DnsConfigServicePosix::ConfigReader : public SerialWorker {
186 public:
ConfigReader(DnsConfigServicePosix * service)187 explicit ConfigReader(DnsConfigServicePosix* service)
188 : service_(service), success_(false) {}
189
DoWork()190 virtual void DoWork() OVERRIDE {
191 base::TimeTicks start_time = base::TimeTicks::Now();
192 ConfigParsePosixResult result = ReadDnsConfig(&dns_config_);
193 switch (result) {
194 case CONFIG_PARSE_POSIX_MISSING_OPTIONS:
195 case CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS:
196 DCHECK(dns_config_.unhandled_options);
197 // Fall through.
198 case CONFIG_PARSE_POSIX_OK:
199 success_ = true;
200 break;
201 default:
202 success_ = false;
203 break;
204 }
205 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.ConfigParsePosix",
206 result, CONFIG_PARSE_POSIX_MAX);
207 UMA_HISTOGRAM_BOOLEAN("AsyncDNS.ConfigParseResult", success_);
208 UMA_HISTOGRAM_TIMES("AsyncDNS.ConfigParseDuration",
209 base::TimeTicks::Now() - start_time);
210 }
211
OnWorkFinished()212 virtual void OnWorkFinished() OVERRIDE {
213 DCHECK(!IsCancelled());
214 if (success_) {
215 service_->OnConfigRead(dns_config_);
216 } else {
217 LOG(WARNING) << "Failed to read DnsConfig.";
218 }
219 }
220
221 private:
~ConfigReader()222 virtual ~ConfigReader() {}
223
224 DnsConfigServicePosix* service_;
225 // Written in DoWork, read in OnWorkFinished, no locking necessary.
226 DnsConfig dns_config_;
227 bool success_;
228
229 DISALLOW_COPY_AND_ASSIGN(ConfigReader);
230 };
231
232 // A SerialWorker that reads the HOSTS file and runs Callback.
233 class DnsConfigServicePosix::HostsReader : public SerialWorker {
234 public:
HostsReader(DnsConfigServicePosix * service)235 explicit HostsReader(DnsConfigServicePosix* service)
236 : service_(service), path_(kFilePathHosts), success_(false) {}
237
238 private:
~HostsReader()239 virtual ~HostsReader() {}
240
DoWork()241 virtual void DoWork() OVERRIDE {
242 base::TimeTicks start_time = base::TimeTicks::Now();
243 success_ = ParseHostsFile(path_, &hosts_);
244 UMA_HISTOGRAM_BOOLEAN("AsyncDNS.HostParseResult", success_);
245 UMA_HISTOGRAM_TIMES("AsyncDNS.HostsParseDuration",
246 base::TimeTicks::Now() - start_time);
247 }
248
OnWorkFinished()249 virtual void OnWorkFinished() OVERRIDE {
250 if (success_) {
251 service_->OnHostsRead(hosts_);
252 } else {
253 LOG(WARNING) << "Failed to read DnsHosts.";
254 }
255 }
256
257 DnsConfigServicePosix* service_;
258 const base::FilePath path_;
259 // Written in DoWork, read in OnWorkFinished, no locking necessary.
260 DnsHosts hosts_;
261 bool success_;
262
263 DISALLOW_COPY_AND_ASSIGN(HostsReader);
264 };
265
DnsConfigServicePosix()266 DnsConfigServicePosix::DnsConfigServicePosix()
267 : config_reader_(new ConfigReader(this)),
268 hosts_reader_(new HostsReader(this)) {}
269
~DnsConfigServicePosix()270 DnsConfigServicePosix::~DnsConfigServicePosix() {
271 config_reader_->Cancel();
272 hosts_reader_->Cancel();
273 }
274
ReadNow()275 void DnsConfigServicePosix::ReadNow() {
276 config_reader_->WorkNow();
277 hosts_reader_->WorkNow();
278 }
279
StartWatching()280 bool DnsConfigServicePosix::StartWatching() {
281 // TODO(szym): re-start watcher if that makes sense. http://crbug.com/116139
282 watcher_.reset(new Watcher(this));
283 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus", DNS_CONFIG_WATCH_STARTED,
284 DNS_CONFIG_WATCH_MAX);
285 return watcher_->Watch();
286 }
287
OnConfigChanged(bool succeeded)288 void DnsConfigServicePosix::OnConfigChanged(bool succeeded) {
289 InvalidateConfig();
290 if (succeeded) {
291 config_reader_->WorkNow();
292 } else {
293 LOG(ERROR) << "DNS config watch failed.";
294 set_watch_failed(true);
295 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
296 DNS_CONFIG_WATCH_FAILED_CONFIG,
297 DNS_CONFIG_WATCH_MAX);
298 }
299 }
300
OnHostsChanged(bool succeeded)301 void DnsConfigServicePosix::OnHostsChanged(bool succeeded) {
302 InvalidateHosts();
303 if (succeeded) {
304 hosts_reader_->WorkNow();
305 } else {
306 LOG(ERROR) << "DNS hosts watch failed.";
307 set_watch_failed(true);
308 UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
309 DNS_CONFIG_WATCH_FAILED_HOSTS,
310 DNS_CONFIG_WATCH_MAX);
311 }
312 }
313
ConvertResStateToDnsConfig(const struct __res_state & res,DnsConfig * dns_config)314 ConfigParsePosixResult ConvertResStateToDnsConfig(const struct __res_state& res,
315 DnsConfig* dns_config) {
316 CHECK(dns_config != NULL);
317 if (!(res.options & RES_INIT))
318 return CONFIG_PARSE_POSIX_RES_INIT_UNSET;
319
320 dns_config->nameservers.clear();
321
322 #if defined(OS_MACOSX) || defined(OS_FREEBSD)
323 union res_sockaddr_union addresses[MAXNS];
324 int nscount = res_getservers(const_cast<res_state>(&res), addresses, MAXNS);
325 DCHECK_GE(nscount, 0);
326 DCHECK_LE(nscount, MAXNS);
327 for (int i = 0; i < nscount; ++i) {
328 IPEndPoint ipe;
329 if (!ipe.FromSockAddr(
330 reinterpret_cast<const struct sockaddr*>(&addresses[i]),
331 sizeof addresses[i])) {
332 return CONFIG_PARSE_POSIX_BAD_ADDRESS;
333 }
334 dns_config->nameservers.push_back(ipe);
335 }
336 #elif defined(OS_LINUX)
337 COMPILE_ASSERT(arraysize(res.nsaddr_list) >= MAXNS &&
338 arraysize(res._u._ext.nsaddrs) >= MAXNS,
339 incompatible_libresolv_res_state);
340 DCHECK_LE(res.nscount, MAXNS);
341 // Initially, glibc stores IPv6 in |_ext.nsaddrs| and IPv4 in |nsaddr_list|.
342 // In res_send.c:res_nsend, it merges |nsaddr_list| into |nsaddrs|,
343 // but we have to combine the two arrays ourselves.
344 for (int i = 0; i < res.nscount; ++i) {
345 IPEndPoint ipe;
346 const struct sockaddr* addr = NULL;
347 size_t addr_len = 0;
348 if (res.nsaddr_list[i].sin_family) { // The indicator used by res_nsend.
349 addr = reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]);
350 addr_len = sizeof res.nsaddr_list[i];
351 } else if (res._u._ext.nsaddrs[i] != NULL) {
352 addr = reinterpret_cast<const struct sockaddr*>(res._u._ext.nsaddrs[i]);
353 addr_len = sizeof *res._u._ext.nsaddrs[i];
354 } else {
355 return CONFIG_PARSE_POSIX_BAD_EXT_STRUCT;
356 }
357 if (!ipe.FromSockAddr(addr, addr_len))
358 return CONFIG_PARSE_POSIX_BAD_ADDRESS;
359 dns_config->nameservers.push_back(ipe);
360 }
361 #else // !(defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_FREEBSD))
362 DCHECK_LE(res.nscount, MAXNS);
363 for (int i = 0; i < res.nscount; ++i) {
364 IPEndPoint ipe;
365 if (!ipe.FromSockAddr(
366 reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]),
367 sizeof res.nsaddr_list[i])) {
368 return CONFIG_PARSE_POSIX_BAD_ADDRESS;
369 }
370 dns_config->nameservers.push_back(ipe);
371 }
372 #endif
373
374 dns_config->search.clear();
375 for (int i = 0; (i < MAXDNSRCH) && res.dnsrch[i]; ++i) {
376 dns_config->search.push_back(std::string(res.dnsrch[i]));
377 }
378
379 dns_config->ndots = res.ndots;
380 dns_config->timeout = base::TimeDelta::FromSeconds(res.retrans);
381 dns_config->attempts = res.retry;
382 #if defined(RES_ROTATE)
383 dns_config->rotate = res.options & RES_ROTATE;
384 #endif
385 dns_config->edns0 = res.options & RES_USE_EDNS0;
386
387 // The current implementation assumes these options are set. They normally
388 // cannot be overwritten by /etc/resolv.conf
389 unsigned kRequiredOptions = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
390 if ((res.options & kRequiredOptions) != kRequiredOptions) {
391 dns_config->unhandled_options = true;
392 return CONFIG_PARSE_POSIX_MISSING_OPTIONS;
393 }
394
395 unsigned kUnhandledOptions = RES_USEVC | RES_IGNTC | RES_USE_DNSSEC;
396 if (res.options & kUnhandledOptions) {
397 dns_config->unhandled_options = true;
398 return CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS;
399 }
400
401 if (dns_config->nameservers.empty())
402 return CONFIG_PARSE_POSIX_NO_NAMESERVERS;
403
404 // If any name server is 0.0.0.0, assume the configuration is invalid.
405 // TODO(szym): Measure how often this happens. http://crbug.com/125599
406 const IPAddressNumber kEmptyAddress(kIPv4AddressSize);
407 for (unsigned i = 0; i < dns_config->nameservers.size(); ++i) {
408 if (dns_config->nameservers[i].address() == kEmptyAddress)
409 return CONFIG_PARSE_POSIX_NULL_ADDRESS;
410 }
411 return CONFIG_PARSE_POSIX_OK;
412 }
413
414 } // namespace internal
415
416 // static
CreateSystemService()417 scoped_ptr<DnsConfigService> DnsConfigService::CreateSystemService() {
418 return scoped_ptr<DnsConfigService>(new internal::DnsConfigServicePosix());
419 }
420
421 #else // defined(OS_ANDROID)
422 // Android NDK provides only a stub <resolv.h> header.
423 class StubDnsConfigService : public DnsConfigService {
424 public:
425 StubDnsConfigService() {}
426 virtual ~StubDnsConfigService() {}
427 private:
428 virtual void ReadNow() OVERRIDE {}
429 virtual bool StartWatching() OVERRIDE { return false; }
430 };
431 // static
432 scoped_ptr<DnsConfigService> DnsConfigService::CreateSystemService() {
433 return scoped_ptr<DnsConfigService>(new StubDnsConfigService());
434 }
435 #endif
436
437 } // namespace net
438