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