1 // Copyright 2021 The Chromium Authors
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_linux.h"
6
7 #include <netdb.h>
8 #include <netinet/in.h>
9 #include <resolv.h>
10 #include <sys/socket.h>
11 #include <sys/types.h>
12
13 #include <map>
14 #include <memory>
15 #include <optional>
16 #include <string>
17 #include <type_traits>
18 #include <utility>
19 #include <vector>
20
21 #include "base/check.h"
22 #include "base/files/file_path.h"
23 #include "base/files/file_path_watcher.h"
24 #include "base/functional/bind.h"
25 #include "base/functional/callback.h"
26 #include "base/location.h"
27 #include "base/logging.h"
28 #include "base/memory/raw_ptr.h"
29 #include "base/metrics/histogram_functions.h"
30 #include "base/metrics/histogram_macros.h"
31 #include "base/sequence_checker.h"
32 #include "base/threading/scoped_blocking_call.h"
33 #include "base/time/time.h"
34 #include "net/base/ip_endpoint.h"
35 #include "net/dns/dns_config.h"
36 #include "net/dns/nsswitch_reader.h"
37 #include "net/dns/public/resolv_reader.h"
38 #include "net/dns/serial_worker.h"
39
40 namespace net {
41
42 namespace internal {
43
44 namespace {
45
46 const base::FilePath::CharType kFilePathHosts[] =
47 FILE_PATH_LITERAL("/etc/hosts");
48
49 #ifndef _PATH_RESCONF // Normally defined in <resolv.h>
50 #define _PATH_RESCONF FILE_PATH_LITERAL("/etc/resolv.conf")
51 #endif
52
53 constexpr base::FilePath::CharType kFilePathResolv[] = _PATH_RESCONF;
54
55 #ifndef _PATH_NSSWITCH_CONF // Normally defined in <netdb.h>
56 #define _PATH_NSSWITCH_CONF FILE_PATH_LITERAL("/etc/nsswitch.conf")
57 #endif
58
59 constexpr base::FilePath::CharType kFilePathNsswitch[] = _PATH_NSSWITCH_CONF;
60
ConvertResStateToDnsConfig(const struct __res_state & res)61 std::optional<DnsConfig> ConvertResStateToDnsConfig(
62 const struct __res_state& res) {
63 std::optional<std::vector<net::IPEndPoint>> nameservers = GetNameservers(res);
64 DnsConfig dns_config;
65 dns_config.unhandled_options = false;
66
67 if (!nameservers.has_value())
68 return std::nullopt;
69
70 // Expected to be validated by GetNameservers()
71 DCHECK(res.options & RES_INIT);
72
73 dns_config.nameservers = std::move(nameservers.value());
74 dns_config.search.clear();
75 for (int i = 0; (i < MAXDNSRCH) && res.dnsrch[i]; ++i) {
76 dns_config.search.emplace_back(res.dnsrch[i]);
77 }
78
79 dns_config.ndots = res.ndots;
80 dns_config.fallback_period = base::Seconds(res.retrans);
81 dns_config.attempts = res.retry;
82 #if defined(RES_ROTATE)
83 dns_config.rotate = res.options & RES_ROTATE;
84 #endif
85 #if !defined(RES_USE_DNSSEC)
86 // Some versions of libresolv don't have support for the DO bit. In this
87 // case, we proceed without it.
88 static const int RES_USE_DNSSEC = 0;
89 #endif
90
91 // The current implementation assumes these options are set. They normally
92 // cannot be overwritten by /etc/resolv.conf
93 const unsigned kRequiredOptions = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
94 if ((res.options & kRequiredOptions) != kRequiredOptions) {
95 dns_config.unhandled_options = true;
96 return dns_config;
97 }
98
99 const unsigned kUnhandledOptions = RES_USEVC | RES_IGNTC | RES_USE_DNSSEC;
100 if (res.options & kUnhandledOptions) {
101 dns_config.unhandled_options = true;
102 return dns_config;
103 }
104
105 if (dns_config.nameservers.empty())
106 return std::nullopt;
107
108 // If any name server is 0.0.0.0, assume the configuration is invalid.
109 for (const IPEndPoint& nameserver : dns_config.nameservers) {
110 if (nameserver.address().IsZero())
111 return std::nullopt;
112 }
113 return dns_config;
114 }
115
116 // Helper to add the effective result of `action` to `in_out_parsed_behavior`.
117 // Returns false if `action` results in inconsistent behavior (setting an action
118 // for a status that already has a different action).
SetActionBehavior(const NsswitchReader::ServiceAction & action,std::map<NsswitchReader::Status,NsswitchReader::Action> & in_out_parsed_behavior)119 bool SetActionBehavior(const NsswitchReader::ServiceAction& action,
120 std::map<NsswitchReader::Status, NsswitchReader::Action>&
121 in_out_parsed_behavior) {
122 if (action.negated) {
123 for (NsswitchReader::Status status :
124 {NsswitchReader::Status::kSuccess, NsswitchReader::Status::kNotFound,
125 NsswitchReader::Status::kUnavailable,
126 NsswitchReader::Status::kTryAgain}) {
127 if (status != action.status) {
128 NsswitchReader::ServiceAction effective_action = {
129 /*negated=*/false, status, action.action};
130 if (!SetActionBehavior(effective_action, in_out_parsed_behavior))
131 return false;
132 }
133 }
134 } else {
135 if (in_out_parsed_behavior.count(action.status) >= 1 &&
136 in_out_parsed_behavior[action.status] != action.action) {
137 return false;
138 }
139 in_out_parsed_behavior[action.status] = action.action;
140 }
141
142 return true;
143 }
144
145 // Helper to determine if `actions` match `expected_actions`, meaning `actions`
146 // contains no unknown statuses or actions and for every expectation set in
147 // `expected_actions`, the expected action matches the effective result from
148 // `actions`.
AreActionsCompatible(const std::vector<NsswitchReader::ServiceAction> & actions,const std::map<NsswitchReader::Status,NsswitchReader::Action> expected_actions)149 bool AreActionsCompatible(
150 const std::vector<NsswitchReader::ServiceAction>& actions,
151 const std::map<NsswitchReader::Status, NsswitchReader::Action>
152 expected_actions) {
153 std::map<NsswitchReader::Status, NsswitchReader::Action> parsed_behavior;
154
155 for (const NsswitchReader::ServiceAction& action : actions) {
156 if (action.status == NsswitchReader::Status::kUnknown ||
157 action.action == NsswitchReader::Action::kUnknown) {
158 return false;
159 }
160
161 if (!SetActionBehavior(action, parsed_behavior))
162 return false;
163 }
164
165 // Default behavior if not configured.
166 if (parsed_behavior.count(NsswitchReader::Status::kSuccess) == 0)
167 parsed_behavior[NsswitchReader::Status::kSuccess] =
168 NsswitchReader::Action::kReturn;
169 if (parsed_behavior.count(NsswitchReader::Status::kNotFound) == 0)
170 parsed_behavior[NsswitchReader::Status::kNotFound] =
171 NsswitchReader::Action::kContinue;
172 if (parsed_behavior.count(NsswitchReader::Status::kUnavailable) == 0)
173 parsed_behavior[NsswitchReader::Status::kUnavailable] =
174 NsswitchReader::Action::kContinue;
175 if (parsed_behavior.count(NsswitchReader::Status::kTryAgain) == 0)
176 parsed_behavior[NsswitchReader::Status::kTryAgain] =
177 NsswitchReader::Action::kContinue;
178
179 for (const std::pair<const NsswitchReader::Status, NsswitchReader::Action>&
180 expected : expected_actions) {
181 if (parsed_behavior[expected.first] != expected.second)
182 return false;
183 }
184
185 return true;
186 }
187
188 // These values are emitted in metrics. Entries should not be renumbered and
189 // numeric values should never be reused. (See NsswitchIncompatibleReason in
190 // tools/metrics/histograms/enums.xml.)
191 enum class IncompatibleNsswitchReason {
192 kFilesMissing = 0,
193 kMultipleFiles = 1,
194 kBadFilesActions = 2,
195 kDnsMissing = 3,
196 kBadDnsActions = 4,
197 kBadMdnsMinimalActions = 5,
198 kBadOtherServiceActions = 6,
199 kUnknownService = 7,
200 kIncompatibleService = 8,
201 kMaxValue = kIncompatibleService
202 };
203
RecordIncompatibleNsswitchReason(IncompatibleNsswitchReason reason,std::optional<NsswitchReader::Service> service_token)204 void RecordIncompatibleNsswitchReason(
205 IncompatibleNsswitchReason reason,
206 std::optional<NsswitchReader::Service> service_token) {
207 if (service_token) {
208 base::UmaHistogramEnumeration(
209 "Net.DNS.DnsConfig.Nsswitch.IncompatibleService",
210 service_token.value());
211 }
212 }
213
IsNsswitchConfigCompatible(const std::vector<NsswitchReader::ServiceSpecification> & nsswitch_hosts)214 bool IsNsswitchConfigCompatible(
215 const std::vector<NsswitchReader::ServiceSpecification>& nsswitch_hosts) {
216 bool files_found = false;
217 for (const NsswitchReader::ServiceSpecification& specification :
218 nsswitch_hosts) {
219 switch (specification.service) {
220 case NsswitchReader::Service::kUnknown:
221 RecordIncompatibleNsswitchReason(
222 IncompatibleNsswitchReason::kUnknownService, specification.service);
223 return false;
224
225 case NsswitchReader::Service::kFiles:
226 if (files_found) {
227 RecordIncompatibleNsswitchReason(
228 IncompatibleNsswitchReason::kMultipleFiles,
229 specification.service);
230 return false;
231 }
232 files_found = true;
233 // Chrome will use the result on HOSTS hit and otherwise continue to
234 // DNS. `kFiles` entries must match that behavior to be compatible.
235 if (!AreActionsCompatible(specification.actions,
236 {{NsswitchReader::Status::kSuccess,
237 NsswitchReader::Action::kReturn},
238 {NsswitchReader::Status::kNotFound,
239 NsswitchReader::Action::kContinue},
240 {NsswitchReader::Status::kUnavailable,
241 NsswitchReader::Action::kContinue},
242 {NsswitchReader::Status::kTryAgain,
243 NsswitchReader::Action::kContinue}})) {
244 RecordIncompatibleNsswitchReason(
245 IncompatibleNsswitchReason::kBadFilesActions,
246 specification.service);
247 return false;
248 }
249 break;
250
251 case NsswitchReader::Service::kDns:
252 if (!files_found) {
253 RecordIncompatibleNsswitchReason(
254 IncompatibleNsswitchReason::kFilesMissing,
255 /*service_token=*/std::nullopt);
256 return false;
257 }
258 // Chrome will always stop if DNS finds a result or will otherwise
259 // fallback to the system resolver (and get whatever behavior is
260 // configured in nsswitch.conf), so the only compatibility requirement
261 // is that `kDns` entries are configured to return on success.
262 if (!AreActionsCompatible(specification.actions,
263 {{NsswitchReader::Status::kSuccess,
264 NsswitchReader::Action::kReturn}})) {
265 RecordIncompatibleNsswitchReason(
266 IncompatibleNsswitchReason::kBadDnsActions,
267 specification.service);
268 return false;
269 }
270
271 // Ignore any entries after `kDns` because Chrome will fallback to the
272 // system resolver if a result was not found in DNS.
273 return true;
274
275 case NsswitchReader::Service::kMdns:
276 case NsswitchReader::Service::kMdns4:
277 case NsswitchReader::Service::kMdns6:
278 case NsswitchReader::Service::kResolve:
279 case NsswitchReader::Service::kNis:
280 RecordIncompatibleNsswitchReason(
281 IncompatibleNsswitchReason::kIncompatibleService,
282 specification.service);
283 return false;
284
285 case NsswitchReader::Service::kMdnsMinimal:
286 case NsswitchReader::Service::kMdns4Minimal:
287 case NsswitchReader::Service::kMdns6Minimal:
288 // Always compatible as long as `kUnavailable` is `kContinue` because
289 // the service is expected to always result in `kUnavailable` for any
290 // names Chrome would attempt to resolve (non-*.local names because
291 // Chrome always delegates *.local names to the system resolver).
292 if (!AreActionsCompatible(specification.actions,
293 {{NsswitchReader::Status::kUnavailable,
294 NsswitchReader::Action::kContinue}})) {
295 RecordIncompatibleNsswitchReason(
296 IncompatibleNsswitchReason::kBadMdnsMinimalActions,
297 specification.service);
298 return false;
299 }
300 break;
301
302 case NsswitchReader::Service::kMyHostname:
303 // Similar enough to Chrome behavior (or unlikely to matter for Chrome
304 // resolutions) to be considered compatible unless the actions do
305 // something very weird to skip remaining services without a result.
306 if (!AreActionsCompatible(specification.actions,
307 {{NsswitchReader::Status::kNotFound,
308 NsswitchReader::Action::kContinue},
309 {NsswitchReader::Status::kUnavailable,
310 NsswitchReader::Action::kContinue},
311 {NsswitchReader::Status::kTryAgain,
312 NsswitchReader::Action::kContinue}})) {
313 RecordIncompatibleNsswitchReason(
314 IncompatibleNsswitchReason::kBadOtherServiceActions,
315 specification.service);
316 return false;
317 }
318 break;
319 }
320 }
321
322 RecordIncompatibleNsswitchReason(IncompatibleNsswitchReason::kDnsMissing,
323 /*service_token=*/std::nullopt);
324 return false;
325 }
326
327 } // namespace
328
329 class DnsConfigServiceLinux::Watcher : public DnsConfigService::Watcher {
330 public:
Watcher(DnsConfigServiceLinux & service)331 explicit Watcher(DnsConfigServiceLinux& service)
332 : DnsConfigService::Watcher(service) {}
333 ~Watcher() override = default;
334
335 Watcher(const Watcher&) = delete;
336 Watcher& operator=(const Watcher&) = delete;
337
Watch()338 bool Watch() override {
339 CheckOnCorrectSequence();
340
341 bool success = true;
342 if (!resolv_watcher_.Watch(
343 base::FilePath(kFilePathResolv),
344 base::FilePathWatcher::Type::kNonRecursive,
345 base::BindRepeating(&Watcher::OnResolvFilePathWatcherChange,
346 base::Unretained(this)))) {
347 LOG(ERROR) << "DNS config (resolv.conf) watch failed to start.";
348 success = false;
349 }
350
351 if (!nsswitch_watcher_.Watch(
352 base::FilePath(kFilePathNsswitch),
353 base::FilePathWatcher::Type::kNonRecursive,
354 base::BindRepeating(&Watcher::OnNsswitchFilePathWatcherChange,
355 base::Unretained(this)))) {
356 LOG(ERROR) << "DNS nsswitch.conf watch failed to start.";
357 success = false;
358 }
359
360 if (!hosts_watcher_.Watch(
361 base::FilePath(kFilePathHosts),
362 base::FilePathWatcher::Type::kNonRecursive,
363 base::BindRepeating(&Watcher::OnHostsFilePathWatcherChange,
364 base::Unretained(this)))) {
365 LOG(ERROR) << "DNS hosts watch failed to start.";
366 success = false;
367 }
368 return success;
369 }
370
371 private:
OnResolvFilePathWatcherChange(const base::FilePath & path,bool error)372 void OnResolvFilePathWatcherChange(const base::FilePath& path, bool error) {
373 base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.FileChange", true);
374 OnConfigChanged(!error);
375 }
376
OnNsswitchFilePathWatcherChange(const base::FilePath & path,bool error)377 void OnNsswitchFilePathWatcherChange(const base::FilePath& path, bool error) {
378 OnConfigChanged(!error);
379 }
380
OnHostsFilePathWatcherChange(const base::FilePath & path,bool error)381 void OnHostsFilePathWatcherChange(const base::FilePath& path, bool error) {
382 OnHostsChanged(!error);
383 }
384
385 base::FilePathWatcher resolv_watcher_;
386 base::FilePathWatcher nsswitch_watcher_;
387 base::FilePathWatcher hosts_watcher_;
388 };
389
390 // A SerialWorker that uses libresolv to initialize res_state and converts
391 // it to DnsConfig.
392 class DnsConfigServiceLinux::ConfigReader : public SerialWorker {
393 public:
ConfigReader(DnsConfigServiceLinux & service,std::unique_ptr<ResolvReader> resolv_reader,std::unique_ptr<NsswitchReader> nsswitch_reader)394 explicit ConfigReader(DnsConfigServiceLinux& service,
395 std::unique_ptr<ResolvReader> resolv_reader,
396 std::unique_ptr<NsswitchReader> nsswitch_reader)
397 : service_(&service),
398 work_item_(std::make_unique<WorkItem>(std::move(resolv_reader),
399 std::move(nsswitch_reader))) {
400 // Allow execution on another thread; nothing thread-specific about
401 // constructor.
402 DETACH_FROM_SEQUENCE(sequence_checker_);
403 }
404
405 ~ConfigReader() override = default;
406
407 ConfigReader(const ConfigReader&) = delete;
408 ConfigReader& operator=(const ConfigReader&) = delete;
409
CreateWorkItem()410 std::unique_ptr<SerialWorker::WorkItem> CreateWorkItem() override {
411 // Reuse same `WorkItem` to allow reuse of contained reader objects.
412 DCHECK(work_item_);
413 return std::move(work_item_);
414 }
415
OnWorkFinished(std::unique_ptr<SerialWorker::WorkItem> serial_worker_work_item)416 bool OnWorkFinished(std::unique_ptr<SerialWorker::WorkItem>
417 serial_worker_work_item) override {
418 DCHECK(serial_worker_work_item);
419 DCHECK(!work_item_);
420 DCHECK(!IsCancelled());
421
422 work_item_.reset(static_cast<WorkItem*>(serial_worker_work_item.release()));
423 if (work_item_->dns_config_.has_value()) {
424 service_->OnConfigRead(std::move(work_item_->dns_config_).value());
425 return true;
426 } else {
427 LOG(WARNING) << "Failed to read DnsConfig.";
428 return false;
429 }
430 }
431
432 private:
433 class WorkItem : public SerialWorker::WorkItem {
434 public:
WorkItem(std::unique_ptr<ResolvReader> resolv_reader,std::unique_ptr<NsswitchReader> nsswitch_reader)435 WorkItem(std::unique_ptr<ResolvReader> resolv_reader,
436 std::unique_ptr<NsswitchReader> nsswitch_reader)
437 : resolv_reader_(std::move(resolv_reader)),
438 nsswitch_reader_(std::move(nsswitch_reader)) {
439 DCHECK(resolv_reader_);
440 DCHECK(nsswitch_reader_);
441 }
442
DoWork()443 void DoWork() override {
444 base::ScopedBlockingCall scoped_blocking_call(
445 FROM_HERE, base::BlockingType::MAY_BLOCK);
446
447 {
448 std::unique_ptr<ScopedResState> res = resolv_reader_->GetResState();
449 if (res) {
450 dns_config_ = ConvertResStateToDnsConfig(res->state());
451 }
452 }
453
454 base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.Read",
455 dns_config_.has_value());
456 if (!dns_config_.has_value())
457 return;
458 base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.Valid",
459 dns_config_->IsValid());
460 base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.Compatible",
461 !dns_config_->unhandled_options);
462
463 // Override `fallback_period` value to match default setting on
464 // Windows.
465 dns_config_->fallback_period = kDnsDefaultFallbackPeriod;
466
467 if (dns_config_ && !dns_config_->unhandled_options) {
468 std::vector<NsswitchReader::ServiceSpecification> nsswitch_hosts =
469 nsswitch_reader_->ReadAndParseHosts();
470 dns_config_->unhandled_options =
471 !IsNsswitchConfigCompatible(nsswitch_hosts);
472 base::UmaHistogramBoolean("Net.DNS.DnsConfig.Nsswitch.Compatible",
473 !dns_config_->unhandled_options);
474 }
475 }
476
477 private:
478 friend class ConfigReader;
479 std::optional<DnsConfig> dns_config_;
480 std::unique_ptr<ResolvReader> resolv_reader_;
481 std::unique_ptr<NsswitchReader> nsswitch_reader_;
482 };
483
484 // Raw pointer to owning DnsConfigService.
485 const raw_ptr<DnsConfigServiceLinux> service_;
486
487 // Null while the `WorkItem` is running on the `ThreadPool`.
488 std::unique_ptr<WorkItem> work_item_;
489 };
490
DnsConfigServiceLinux()491 DnsConfigServiceLinux::DnsConfigServiceLinux()
492 : DnsConfigService(kFilePathHosts) {
493 // Allow constructing on one thread and living on another.
494 DETACH_FROM_SEQUENCE(sequence_checker_);
495 }
496
~DnsConfigServiceLinux()497 DnsConfigServiceLinux::~DnsConfigServiceLinux() {
498 if (config_reader_)
499 config_reader_->Cancel();
500 }
501
ReadConfigNow()502 void DnsConfigServiceLinux::ReadConfigNow() {
503 if (!config_reader_)
504 CreateReader();
505 config_reader_->WorkNow();
506 }
507
StartWatching()508 bool DnsConfigServiceLinux::StartWatching() {
509 CreateReader();
510 watcher_ = std::make_unique<Watcher>(*this);
511 return watcher_->Watch();
512 }
513
CreateReader()514 void DnsConfigServiceLinux::CreateReader() {
515 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
516 DCHECK(!config_reader_);
517 DCHECK(resolv_reader_);
518 DCHECK(nsswitch_reader_);
519 config_reader_ = std::make_unique<ConfigReader>(
520 *this, std::move(resolv_reader_), std::move(nsswitch_reader_));
521 }
522
523 } // namespace internal
524
525 // static
CreateSystemService()526 std::unique_ptr<DnsConfigService> DnsConfigService::CreateSystemService() {
527 return std::make_unique<internal::DnsConfigServiceLinux>();
528 }
529
530 } // namespace net
531