xref: /aosp_15_r20/external/cronet/net/dns/dns_config_service_linux.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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