123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541 |
- // Copyright 2021 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "net/dns/dns_config_service_linux.h"
- #include <netdb.h>
- #include <netinet/in.h>
- #include <resolv.h>
- #include <sys/socket.h>
- #include <sys/types.h>
- #include <map>
- #include <memory>
- #include <string>
- #include <type_traits>
- #include <utility>
- #include <vector>
- #include "base/bind.h"
- #include "base/callback.h"
- #include "base/check.h"
- #include "base/containers/contains.h"
- #include "base/files/file_path.h"
- #include "base/files/file_path_watcher.h"
- #include "base/location.h"
- #include "base/logging.h"
- #include "base/memory/raw_ptr.h"
- #include "base/metrics/histogram_functions.h"
- #include "base/metrics/histogram_macros.h"
- #include "base/sequence_checker.h"
- #include "base/threading/scoped_blocking_call.h"
- #include "base/time/time.h"
- #include "net/base/ip_endpoint.h"
- #include "net/dns/dns_config.h"
- #include "net/dns/nsswitch_reader.h"
- #include "net/dns/public/resolv_reader.h"
- #include "net/dns/serial_worker.h"
- #include "third_party/abseil-cpp/absl/types/optional.h"
- namespace net {
- namespace internal {
- namespace {
- const base::FilePath::CharType kFilePathHosts[] =
- FILE_PATH_LITERAL("/etc/hosts");
- #ifndef _PATH_RESCONF // Normally defined in <resolv.h>
- #define _PATH_RESCONF FILE_PATH_LITERAL("/etc/resolv.conf")
- #endif
- constexpr base::FilePath::CharType kFilePathResolv[] = _PATH_RESCONF;
- #ifndef _PATH_NSSWITCH_CONF // Normally defined in <netdb.h>
- #define _PATH_NSSWITCH_CONF FILE_PATH_LITERAL("/etc/nsswitch.conf")
- #endif
- constexpr base::FilePath::CharType kFilePathNsswitch[] = _PATH_NSSWITCH_CONF;
- absl::optional<DnsConfig> ConvertResStateToDnsConfig(
- const struct __res_state& res) {
- absl::optional<std::vector<net::IPEndPoint>> nameservers =
- GetNameservers(res);
- DnsConfig dns_config;
- dns_config.unhandled_options = false;
- if (!nameservers.has_value())
- return absl::nullopt;
- // Expected to be validated by GetNameservers()
- DCHECK(res.options & RES_INIT);
- dns_config.nameservers = std::move(nameservers.value());
- dns_config.search.clear();
- for (int i = 0; (i < MAXDNSRCH) && res.dnsrch[i]; ++i) {
- dns_config.search.emplace_back(res.dnsrch[i]);
- }
- dns_config.ndots = res.ndots;
- dns_config.fallback_period = base::Seconds(res.retrans);
- dns_config.attempts = res.retry;
- #if defined(RES_ROTATE)
- dns_config.rotate = res.options & RES_ROTATE;
- #endif
- #if !defined(RES_USE_DNSSEC)
- // Some versions of libresolv don't have support for the DO bit. In this
- // case, we proceed without it.
- static const int RES_USE_DNSSEC = 0;
- #endif
- // The current implementation assumes these options are set. They normally
- // cannot be overwritten by /etc/resolv.conf
- const unsigned kRequiredOptions = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
- if ((res.options & kRequiredOptions) != kRequiredOptions) {
- dns_config.unhandled_options = true;
- return dns_config;
- }
- const unsigned kUnhandledOptions = RES_USEVC | RES_IGNTC | RES_USE_DNSSEC;
- if (res.options & kUnhandledOptions) {
- dns_config.unhandled_options = true;
- return dns_config;
- }
- if (dns_config.nameservers.empty())
- return absl::nullopt;
- // If any name server is 0.0.0.0, assume the configuration is invalid.
- for (const IPEndPoint& nameserver : dns_config.nameservers) {
- if (nameserver.address().IsZero())
- return absl::nullopt;
- }
- return dns_config;
- }
- // Helper to add the effective result of `action` to `in_out_parsed_behavior`.
- // Returns false if `action` results in inconsistent behavior (setting an action
- // for a status that already has a different action).
- bool SetActionBehavior(const NsswitchReader::ServiceAction& action,
- std::map<NsswitchReader::Status, NsswitchReader::Action>&
- in_out_parsed_behavior) {
- if (action.negated) {
- for (NsswitchReader::Status status :
- {NsswitchReader::Status::kSuccess, NsswitchReader::Status::kNotFound,
- NsswitchReader::Status::kUnavailable,
- NsswitchReader::Status::kTryAgain}) {
- if (status != action.status) {
- NsswitchReader::ServiceAction effective_action = {
- /*negated=*/false, status, action.action};
- if (!SetActionBehavior(effective_action, in_out_parsed_behavior))
- return false;
- }
- }
- } else {
- if (in_out_parsed_behavior.count(action.status) >= 1 &&
- in_out_parsed_behavior[action.status] != action.action) {
- return false;
- }
- in_out_parsed_behavior[action.status] = action.action;
- }
- return true;
- }
- // Helper to determine if `actions` match `expected_actions`, meaning `actions`
- // contains no unknown statuses or actions and for every expectation set in
- // `expected_actions`, the expected action matches the effective result from
- // `actions`.
- bool AreActionsCompatible(
- const std::vector<NsswitchReader::ServiceAction>& actions,
- const std::map<NsswitchReader::Status, NsswitchReader::Action>
- expected_actions) {
- std::map<NsswitchReader::Status, NsswitchReader::Action> parsed_behavior;
- for (const NsswitchReader::ServiceAction& action : actions) {
- if (action.status == NsswitchReader::Status::kUnknown ||
- action.action == NsswitchReader::Action::kUnknown) {
- return false;
- }
- if (!SetActionBehavior(action, parsed_behavior))
- return false;
- }
- // Default behavior if not configured.
- if (parsed_behavior.count(NsswitchReader::Status::kSuccess) == 0)
- parsed_behavior[NsswitchReader::Status::kSuccess] =
- NsswitchReader::Action::kReturn;
- if (parsed_behavior.count(NsswitchReader::Status::kNotFound) == 0)
- parsed_behavior[NsswitchReader::Status::kNotFound] =
- NsswitchReader::Action::kContinue;
- if (parsed_behavior.count(NsswitchReader::Status::kUnavailable) == 0)
- parsed_behavior[NsswitchReader::Status::kUnavailable] =
- NsswitchReader::Action::kContinue;
- if (parsed_behavior.count(NsswitchReader::Status::kTryAgain) == 0)
- parsed_behavior[NsswitchReader::Status::kTryAgain] =
- NsswitchReader::Action::kContinue;
- for (const std::pair<const NsswitchReader::Status, NsswitchReader::Action>&
- expected : expected_actions) {
- if (parsed_behavior[expected.first] != expected.second)
- return false;
- }
- return true;
- }
- // These values are emitted in metrics. Entries should not be renumbered and
- // numeric values should never be reused. (See NsswitchIncompatibleReason in
- // tools/metrics/histograms/enums.xml.)
- enum class IncompatibleNsswitchReason {
- kFilesMissing = 0,
- kMultipleFiles = 1,
- kBadFilesActions = 2,
- kDnsMissing = 3,
- kBadDnsActions = 4,
- kBadMdnsMinimalActions = 5,
- kBadOtherServiceActions = 6,
- kUnknownService = 7,
- kIncompatibleService = 8,
- kMaxValue = kIncompatibleService
- };
- void RecordIncompatibleNsswitchReason(
- IncompatibleNsswitchReason reason,
- absl::optional<NsswitchReader::Service> service_token) {
- base::UmaHistogramEnumeration("Net.DNS.DnsConfig.Nsswitch.IncompatibleReason",
- reason);
- if (service_token) {
- base::UmaHistogramEnumeration(
- "Net.DNS.DnsConfig.Nsswitch.IncompatibleService",
- service_token.value());
- }
- }
- bool IsNsswitchConfigCompatible(
- const std::vector<NsswitchReader::ServiceSpecification>& nsswitch_hosts) {
- bool files_found = false;
- for (const NsswitchReader::ServiceSpecification& specification :
- nsswitch_hosts) {
- switch (specification.service) {
- case NsswitchReader::Service::kUnknown:
- RecordIncompatibleNsswitchReason(
- IncompatibleNsswitchReason::kUnknownService, specification.service);
- return false;
- case NsswitchReader::Service::kFiles:
- if (files_found) {
- RecordIncompatibleNsswitchReason(
- IncompatibleNsswitchReason::kMultipleFiles,
- specification.service);
- return false;
- }
- files_found = true;
- // Chrome will use the result on HOSTS hit and otherwise continue to
- // DNS. `kFiles` entries must match that behavior to be compatible.
- if (!AreActionsCompatible(specification.actions,
- {{NsswitchReader::Status::kSuccess,
- NsswitchReader::Action::kReturn},
- {NsswitchReader::Status::kNotFound,
- NsswitchReader::Action::kContinue},
- {NsswitchReader::Status::kUnavailable,
- NsswitchReader::Action::kContinue},
- {NsswitchReader::Status::kTryAgain,
- NsswitchReader::Action::kContinue}})) {
- RecordIncompatibleNsswitchReason(
- IncompatibleNsswitchReason::kBadFilesActions,
- specification.service);
- return false;
- }
- break;
- case NsswitchReader::Service::kDns:
- if (!files_found) {
- RecordIncompatibleNsswitchReason(
- IncompatibleNsswitchReason::kFilesMissing,
- /*service_token=*/absl::nullopt);
- return false;
- }
- // Chrome will always stop if DNS finds a result or will otherwise
- // fallback to the system resolver (and get whatever behavior is
- // configured in nsswitch.conf), so the only compatibility requirement
- // is that `kDns` entries are configured to return on success.
- if (!AreActionsCompatible(specification.actions,
- {{NsswitchReader::Status::kSuccess,
- NsswitchReader::Action::kReturn}})) {
- RecordIncompatibleNsswitchReason(
- IncompatibleNsswitchReason::kBadDnsActions,
- specification.service);
- return false;
- }
- // Ignore any entries after `kDns` because Chrome will fallback to the
- // system resolver if a result was not found in DNS.
- return true;
- case NsswitchReader::Service::kMdns:
- case NsswitchReader::Service::kMdns4:
- case NsswitchReader::Service::kMdns6:
- case NsswitchReader::Service::kResolve:
- RecordIncompatibleNsswitchReason(
- IncompatibleNsswitchReason::kIncompatibleService,
- specification.service);
- return false;
- case NsswitchReader::Service::kMdnsMinimal:
- case NsswitchReader::Service::kMdns4Minimal:
- case NsswitchReader::Service::kMdns6Minimal:
- // Always compatible as long as `kUnavailable` is `kContinue` because
- // the service is expected to always result in `kUnavailable` for any
- // names Chrome would attempt to resolve (non-*.local names because
- // Chrome always delegates *.local names to the system resolver).
- if (!AreActionsCompatible(specification.actions,
- {{NsswitchReader::Status::kUnavailable,
- NsswitchReader::Action::kContinue}})) {
- RecordIncompatibleNsswitchReason(
- IncompatibleNsswitchReason::kBadMdnsMinimalActions,
- specification.service);
- return false;
- }
- break;
- case NsswitchReader::Service::kMyHostname:
- case NsswitchReader::Service::kNis:
- // Similar enough to Chrome behavior (or unlikely to matter for Chrome
- // resolutions) to be considered compatible unless the actions do
- // something very weird to skip remaining services without a result.
- if (!AreActionsCompatible(specification.actions,
- {{NsswitchReader::Status::kNotFound,
- NsswitchReader::Action::kContinue},
- {NsswitchReader::Status::kUnavailable,
- NsswitchReader::Action::kContinue},
- {NsswitchReader::Status::kTryAgain,
- NsswitchReader::Action::kContinue}})) {
- RecordIncompatibleNsswitchReason(
- IncompatibleNsswitchReason::kBadOtherServiceActions,
- specification.service);
- return false;
- }
- break;
- }
- }
- RecordIncompatibleNsswitchReason(IncompatibleNsswitchReason::kDnsMissing,
- /*service_token=*/absl::nullopt);
- return false;
- }
- } // namespace
- class DnsConfigServiceLinux::Watcher : public DnsConfigService::Watcher {
- public:
- explicit Watcher(DnsConfigServiceLinux& service)
- : DnsConfigService::Watcher(service) {}
- ~Watcher() override = default;
- Watcher(const Watcher&) = delete;
- Watcher& operator=(const Watcher&) = delete;
- bool Watch() override {
- CheckOnCorrectSequence();
- bool success = true;
- if (!resolv_watcher_.Watch(
- base::FilePath(kFilePathResolv),
- base::FilePathWatcher::Type::kNonRecursive,
- base::BindRepeating(&Watcher::OnResolvFilePathWatcherChange,
- base::Unretained(this)))) {
- LOG(ERROR) << "DNS config (resolv.conf) watch failed to start.";
- success = false;
- }
- if (!nsswitch_watcher_.Watch(
- base::FilePath(kFilePathNsswitch),
- base::FilePathWatcher::Type::kNonRecursive,
- base::BindRepeating(&Watcher::OnNsswitchFilePathWatcherChange,
- base::Unretained(this)))) {
- LOG(ERROR) << "DNS nsswitch.conf watch failed to start.";
- success = false;
- }
- if (!hosts_watcher_.Watch(
- base::FilePath(kFilePathHosts),
- base::FilePathWatcher::Type::kNonRecursive,
- base::BindRepeating(&Watcher::OnHostsFilePathWatcherChange,
- base::Unretained(this)))) {
- LOG(ERROR) << "DNS hosts watch failed to start.";
- success = false;
- }
- return success;
- }
- private:
- void OnResolvFilePathWatcherChange(const base::FilePath& path, bool error) {
- base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.FileChange", true);
- OnConfigChanged(!error);
- }
- void OnNsswitchFilePathWatcherChange(const base::FilePath& path, bool error) {
- base::UmaHistogramBoolean("Net.DNS.DnsConfig.Nsswitch.FileChange", true);
- OnConfigChanged(!error);
- }
- void OnHostsFilePathWatcherChange(const base::FilePath& path, bool error) {
- OnHostsChanged(!error);
- }
- base::FilePathWatcher resolv_watcher_;
- base::FilePathWatcher nsswitch_watcher_;
- base::FilePathWatcher hosts_watcher_;
- };
- // A SerialWorker that uses libresolv to initialize res_state and converts
- // it to DnsConfig.
- class DnsConfigServiceLinux::ConfigReader : public SerialWorker {
- public:
- explicit ConfigReader(DnsConfigServiceLinux& service,
- std::unique_ptr<ResolvReader> resolv_reader,
- std::unique_ptr<NsswitchReader> nsswitch_reader)
- : service_(&service),
- work_item_(std::make_unique<WorkItem>(std::move(resolv_reader),
- std::move(nsswitch_reader))) {
- // Allow execution on another thread; nothing thread-specific about
- // constructor.
- DETACH_FROM_SEQUENCE(sequence_checker_);
- }
- ~ConfigReader() override = default;
- ConfigReader(const ConfigReader&) = delete;
- ConfigReader& operator=(const ConfigReader&) = delete;
- std::unique_ptr<SerialWorker::WorkItem> CreateWorkItem() override {
- // Reuse same `WorkItem` to allow reuse of contained reader objects.
- DCHECK(work_item_);
- return std::move(work_item_);
- }
- bool OnWorkFinished(std::unique_ptr<SerialWorker::WorkItem>
- serial_worker_work_item) override {
- DCHECK(serial_worker_work_item);
- DCHECK(!work_item_);
- DCHECK(!IsCancelled());
- work_item_.reset(static_cast<WorkItem*>(serial_worker_work_item.release()));
- if (work_item_->dns_config_.has_value()) {
- service_->OnConfigRead(std::move(work_item_->dns_config_).value());
- return true;
- } else {
- LOG(WARNING) << "Failed to read DnsConfig.";
- return false;
- }
- }
- private:
- class WorkItem : public SerialWorker::WorkItem {
- public:
- WorkItem(std::unique_ptr<ResolvReader> resolv_reader,
- std::unique_ptr<NsswitchReader> nsswitch_reader)
- : resolv_reader_(std::move(resolv_reader)),
- nsswitch_reader_(std::move(nsswitch_reader)) {
- DCHECK(resolv_reader_);
- DCHECK(nsswitch_reader_);
- }
- void DoWork() override {
- base::ScopedBlockingCall scoped_blocking_call(
- FROM_HERE, base::BlockingType::MAY_BLOCK);
- {
- std::unique_ptr<ScopedResState> res = resolv_reader_->GetResState();
- if (res) {
- dns_config_ = ConvertResStateToDnsConfig(res->state());
- }
- }
- base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.Read",
- dns_config_.has_value());
- if (!dns_config_.has_value())
- return;
- base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.Valid",
- dns_config_->IsValid());
- base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.Compatible",
- !dns_config_->unhandled_options);
- // Override `fallback_period` value to match default setting on
- // Windows.
- dns_config_->fallback_period = kDnsDefaultFallbackPeriod;
- if (dns_config_ && !dns_config_->unhandled_options) {
- std::vector<NsswitchReader::ServiceSpecification> nsswitch_hosts =
- nsswitch_reader_->ReadAndParseHosts();
- base::UmaHistogramCounts100("Net.DNS.DnsConfig.Nsswitch.NumServices",
- nsswitch_hosts.size());
- dns_config_->unhandled_options =
- !IsNsswitchConfigCompatible(nsswitch_hosts);
- base::UmaHistogramBoolean("Net.DNS.DnsConfig.Nsswitch.Compatible",
- !dns_config_->unhandled_options);
- base::UmaHistogramBoolean(
- "Net.DNS.DnsConfig.Nsswitch.NisServiceInHosts",
- base::Contains(nsswitch_hosts, NsswitchReader::Service::kNis,
- &NsswitchReader::ServiceSpecification::service));
- }
- }
- private:
- friend class ConfigReader;
- absl::optional<DnsConfig> dns_config_;
- std::unique_ptr<ResolvReader> resolv_reader_;
- std::unique_ptr<NsswitchReader> nsswitch_reader_;
- };
- // Raw pointer to owning DnsConfigService.
- const raw_ptr<DnsConfigServiceLinux> service_;
- // Null while the `WorkItem` is running on the `ThreadPool`.
- std::unique_ptr<WorkItem> work_item_;
- };
- DnsConfigServiceLinux::DnsConfigServiceLinux()
- : DnsConfigService(kFilePathHosts) {
- // Allow constructing on one thread and living on another.
- DETACH_FROM_SEQUENCE(sequence_checker_);
- }
- DnsConfigServiceLinux::~DnsConfigServiceLinux() {
- if (config_reader_)
- config_reader_->Cancel();
- }
- void DnsConfigServiceLinux::ReadConfigNow() {
- if (!config_reader_)
- CreateReader();
- config_reader_->WorkNow();
- }
- bool DnsConfigServiceLinux::StartWatching() {
- CreateReader();
- watcher_ = std::make_unique<Watcher>(*this);
- return watcher_->Watch();
- }
- void DnsConfigServiceLinux::CreateReader() {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK(!config_reader_);
- DCHECK(resolv_reader_);
- DCHECK(nsswitch_reader_);
- config_reader_ = std::make_unique<ConfigReader>(
- *this, std::move(resolv_reader_), std::move(nsswitch_reader_));
- }
- } // namespace internal
- // static
- std::unique_ptr<DnsConfigService> DnsConfigService::CreateSystemService() {
- return std::make_unique<internal::DnsConfigServiceLinux>();
- }
- } // namespace net
|