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