• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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