blob: b9d65f6a5cc474116b5a6f58ad808a58259ff8fe [file] [log] [blame]
Eric Orthf79bc2e2021-04-02 22:23:061// Copyright 2021 The Chromium Authors. All rights reserved.
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
Eric Orthc30a1b72021-04-07 22:32:377#include <netdb.h>
Eric Orthf79bc2e2021-04-02 22:23:068#include <netinet/in.h>
9#include <resolv.h>
10#include <sys/socket.h>
11#include <sys/types.h>
12
Eric Orth0704a01d2021-04-08 17:03:1913#include <map>
Eric Orthf79bc2e2021-04-02 22:23:0614#include <memory>
15#include <string>
16#include <type_traits>
17#include <utility>
Eric Orthc30a1b72021-04-07 22:32:3718#include <vector>
Eric Orthf79bc2e2021-04-02 22:23:0619
20#include "base/bind.h"
21#include "base/callback.h"
22#include "base/check.h"
Tsuyoshi Horoea5c57b2022-08-10 02:15:0123#include "base/containers/contains.h"
Eric Orthf79bc2e2021-04-02 22:23:0624#include "base/files/file_path.h"
25#include "base/files/file_path_watcher.h"
26#include "base/location.h"
27#include "base/logging.h"
Keishi Hattorif28f4f82022-06-21 11:32:1528#include "base/memory/raw_ptr.h"
Tsuyoshi Horoea5c57b2022-08-10 02:15:0129#include "base/metrics/histogram_functions.h"
Eric Ortha4b7ca02021-04-09 22:28:5830#include "base/metrics/histogram_macros.h"
Eric Orthf79bc2e2021-04-02 22:23:0631#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"
Eric Orthc30a1b72021-04-07 22:32:3736#include "net/dns/nsswitch_reader.h"
Lina Ismail32c5ad42021-08-14 03:59:3437#include "net/dns/public/resolv_reader.h"
Eric Orthf79bc2e2021-04-02 22:23:0638#include "net/dns/serial_worker.h"
Anton Bikineev068d2912021-05-15 20:43:5239#include "third_party/abseil-cpp/absl/types/optional.h"
Eric Orthf79bc2e2021-04-02 22:23:0640
41namespace net {
42
43namespace internal {
44
45namespace {
46
47const base::FilePath::CharType kFilePathHosts[] =
48 FILE_PATH_LITERAL("/etc/hosts");
49
50#ifndef _PATH_RESCONF // Normally defined in <resolv.h>
Eric Orthc30a1b72021-04-07 22:32:3751#define _PATH_RESCONF FILE_PATH_LITERAL("/etc/resolv.conf")
Eric Orthf79bc2e2021-04-02 22:23:0652#endif
53
Eric Orthc30a1b72021-04-07 22:32:3754constexpr base::FilePath::CharType kFilePathResolv[] = _PATH_RESCONF;
Eric Orthf79bc2e2021-04-02 22:23:0655
Eric Orthc30a1b72021-04-07 22:32:3756#ifndef _PATH_NSSWITCH_CONF // Normally defined in <netdb.h>
57#define _PATH_NSSWITCH_CONF FILE_PATH_LITERAL("/etc/nsswitch.conf")
58#endif
Eric Orthf79bc2e2021-04-02 22:23:0659
Eric Orthc30a1b72021-04-07 22:32:3760constexpr base::FilePath::CharType kFilePathNsswitch[] = _PATH_NSSWITCH_CONF;
Eric Orthf79bc2e2021-04-02 22:23:0661
Anton Bikineev068d2912021-05-15 20:43:5262absl::optional<DnsConfig> ConvertResStateToDnsConfig(
Eric Orthf79bc2e2021-04-02 22:23:0663 const struct __res_state& res) {
Lina Ismail32c5ad42021-08-14 03:59:3464 absl::optional<std::vector<net::IPEndPoint>> nameservers =
65 GetNameservers(res);
Eric Orthf79bc2e2021-04-02 22:23:0666 DnsConfig dns_config;
67 dns_config.unhandled_options = false;
68
Lina Ismail32c5ad42021-08-14 03:59:3469 if (!nameservers.has_value())
Anton Bikineev068d2912021-05-15 20:43:5270 return absl::nullopt;
Eric Orthf79bc2e2021-04-02 22:23:0671
Lina Ismail32c5ad42021-08-14 03:59:3472 // Expected to be validated by GetNameservers()
73 DCHECK(res.options & RES_INIT);
Eric Orthf79bc2e2021-04-02 22:23:0674
Lina Ismail32c5ad42021-08-14 03:59:3475 dns_config.nameservers = std::move(nameservers.value());
Eric Orthf79bc2e2021-04-02 22:23:0676 dns_config.search.clear();
77 for (int i = 0; (i < MAXDNSRCH) && res.dnsrch[i]; ++i) {
78 dns_config.search.emplace_back(res.dnsrch[i]);
79 }
80
81 dns_config.ndots = res.ndots;
Peter Kastinge5a38ed2021-10-02 03:06:3582 dns_config.fallback_period = base::Seconds(res.retrans);
Eric Orthf79bc2e2021-04-02 22:23:0683 dns_config.attempts = res.retry;
84#if defined(RES_ROTATE)
85 dns_config.rotate = res.options & RES_ROTATE;
86#endif
87#if !defined(RES_USE_DNSSEC)
88 // Some versions of libresolv don't have support for the DO bit. In this
89 // case, we proceed without it.
90 static const int RES_USE_DNSSEC = 0;
91#endif
92
93 // The current implementation assumes these options are set. They normally
94 // cannot be overwritten by /etc/resolv.conf
95 const unsigned kRequiredOptions = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
96 if ((res.options & kRequiredOptions) != kRequiredOptions) {
97 dns_config.unhandled_options = true;
98 return dns_config;
99 }
100
101 const unsigned kUnhandledOptions = RES_USEVC | RES_IGNTC | RES_USE_DNSSEC;
102 if (res.options & kUnhandledOptions) {
103 dns_config.unhandled_options = true;
104 return dns_config;
105 }
106
107 if (dns_config.nameservers.empty())
Anton Bikineev068d2912021-05-15 20:43:52108 return absl::nullopt;
Eric Orthf79bc2e2021-04-02 22:23:06109
110 // If any name server is 0.0.0.0, assume the configuration is invalid.
111 for (const IPEndPoint& nameserver : dns_config.nameservers) {
112 if (nameserver.address().IsZero())
Anton Bikineev068d2912021-05-15 20:43:52113 return absl::nullopt;
Eric Orthf79bc2e2021-04-02 22:23:06114 }
115 return dns_config;
116}
117
Eric Orth0704a01d2021-04-08 17:03:19118// Helper to add the effective result of `action` to `in_out_parsed_behavior`.
119// Returns false if `action` results in inconsistent behavior (setting an action
120// for a status that already has a different action).
121bool SetActionBehavior(const NsswitchReader::ServiceAction& action,
122 std::map<NsswitchReader::Status, NsswitchReader::Action>&
123 in_out_parsed_behavior) {
124 if (action.negated) {
125 for (NsswitchReader::Status status :
126 {NsswitchReader::Status::kSuccess, NsswitchReader::Status::kNotFound,
127 NsswitchReader::Status::kUnavailable,
128 NsswitchReader::Status::kTryAgain}) {
129 if (status != action.status) {
130 NsswitchReader::ServiceAction effective_action = {
131 /*negated=*/false, status, action.action};
132 if (!SetActionBehavior(effective_action, in_out_parsed_behavior))
133 return false;
134 }
135 }
136 } else {
137 if (in_out_parsed_behavior.count(action.status) >= 1 &&
138 in_out_parsed_behavior[action.status] != action.action) {
139 return false;
140 }
141 in_out_parsed_behavior[action.status] = action.action;
142 }
143
144 return true;
145}
146
147// Helper to determine if `actions` match `expected_actions`, meaning `actions`
148// contains no unknown statuses or actions and for every expectation set in
149// `expected_actions`, the expected action matches the effective result from
150// `actions`.
151bool AreActionsCompatible(
152 const std::vector<NsswitchReader::ServiceAction>& actions,
153 const std::map<NsswitchReader::Status, NsswitchReader::Action>
154 expected_actions) {
155 std::map<NsswitchReader::Status, NsswitchReader::Action> parsed_behavior;
156
157 for (const NsswitchReader::ServiceAction& action : actions) {
158 if (action.status == NsswitchReader::Status::kUnknown ||
159 action.action == NsswitchReader::Action::kUnknown) {
160 return false;
161 }
162
163 if (!SetActionBehavior(action, parsed_behavior))
164 return false;
165 }
166
167 // Default behavior if not configured.
168 if (parsed_behavior.count(NsswitchReader::Status::kSuccess) == 0)
169 parsed_behavior[NsswitchReader::Status::kSuccess] =
170 NsswitchReader::Action::kReturn;
171 if (parsed_behavior.count(NsswitchReader::Status::kNotFound) == 0)
172 parsed_behavior[NsswitchReader::Status::kNotFound] =
173 NsswitchReader::Action::kContinue;
174 if (parsed_behavior.count(NsswitchReader::Status::kUnavailable) == 0)
175 parsed_behavior[NsswitchReader::Status::kUnavailable] =
176 NsswitchReader::Action::kContinue;
177 if (parsed_behavior.count(NsswitchReader::Status::kTryAgain) == 0)
178 parsed_behavior[NsswitchReader::Status::kTryAgain] =
179 NsswitchReader::Action::kContinue;
180
181 for (const std::pair<const NsswitchReader::Status, NsswitchReader::Action>&
182 expected : expected_actions) {
183 if (parsed_behavior[expected.first] != expected.second)
184 return false;
185 }
186
187 return true;
188}
189
Eric Ortha4b7ca02021-04-09 22:28:58190// These values are emitted in metrics. Entries should not be renumbered and
191// numeric values should never be reused. (See NsswitchIncompatibleReason in
192// tools/metrics/histograms/enums.xml.)
Eric Orth0704a01d2021-04-08 17:03:19193enum class IncompatibleNsswitchReason {
194 kFilesMissing = 0,
Eric Ortha4b7ca02021-04-09 22:28:58195 kMultipleFiles = 1,
196 kBadFilesActions = 2,
197 kDnsMissing = 3,
198 kBadDnsActions = 4,
199 kBadMdnsMinimalActions = 5,
200 kBadOtherServiceActions = 6,
201 kUnknownService = 7,
202 kIncompatibleService = 8,
203 kMaxValue = kIncompatibleService
Eric Orth0704a01d2021-04-08 17:03:19204};
205
Eric Ortha4b7ca02021-04-09 22:28:58206void RecordIncompatibleNsswitchReason(
207 IncompatibleNsswitchReason reason,
Anton Bikineev068d2912021-05-15 20:43:52208 absl::optional<NsswitchReader::Service> service_token) {
Tsuyoshi Horoea5c57b2022-08-10 02:15:01209 base::UmaHistogramEnumeration("Net.DNS.DnsConfig.Nsswitch.IncompatibleReason",
210 reason);
Eric Ortha4b7ca02021-04-09 22:28:58211 if (service_token) {
Tsuyoshi Horoea5c57b2022-08-10 02:15:01212 base::UmaHistogramEnumeration(
213 "Net.DNS.DnsConfig.Nsswitch.IncompatibleService",
214 service_token.value());
Eric Ortha4b7ca02021-04-09 22:28:58215 }
Eric Orth0704a01d2021-04-08 17:03:19216}
217
Eric Orthc30a1b72021-04-07 22:32:37218bool IsNsswitchConfigCompatible(
219 const std::vector<NsswitchReader::ServiceSpecification>& nsswitch_hosts) {
Eric Orth0704a01d2021-04-08 17:03:19220 bool files_found = false;
221 for (const NsswitchReader::ServiceSpecification& specification :
222 nsswitch_hosts) {
223 switch (specification.service) {
224 case NsswitchReader::Service::kUnknown:
225 RecordIncompatibleNsswitchReason(
Eric Ortha4b7ca02021-04-09 22:28:58226 IncompatibleNsswitchReason::kUnknownService, specification.service);
Eric Orth0704a01d2021-04-08 17:03:19227 return false;
228
229 case NsswitchReader::Service::kFiles:
230 if (files_found) {
231 RecordIncompatibleNsswitchReason(
Eric Ortha4b7ca02021-04-09 22:28:58232 IncompatibleNsswitchReason::kMultipleFiles,
233 specification.service);
Eric Orth0704a01d2021-04-08 17:03:19234 return false;
235 }
236 files_found = true;
237 // Chrome will use the result on HOSTS hit and otherwise continue to
238 // DNS. `kFiles` entries must match that behavior to be compatible.
239 if (!AreActionsCompatible(specification.actions,
240 {{NsswitchReader::Status::kSuccess,
241 NsswitchReader::Action::kReturn},
242 {NsswitchReader::Status::kNotFound,
243 NsswitchReader::Action::kContinue},
244 {NsswitchReader::Status::kUnavailable,
245 NsswitchReader::Action::kContinue},
246 {NsswitchReader::Status::kTryAgain,
247 NsswitchReader::Action::kContinue}})) {
248 RecordIncompatibleNsswitchReason(
Eric Ortha4b7ca02021-04-09 22:28:58249 IncompatibleNsswitchReason::kBadFilesActions,
250 specification.service);
Eric Orth0704a01d2021-04-08 17:03:19251 return false;
252 }
253 break;
254
255 case NsswitchReader::Service::kDns:
256 if (!files_found) {
257 RecordIncompatibleNsswitchReason(
Eric Ortha4b7ca02021-04-09 22:28:58258 IncompatibleNsswitchReason::kFilesMissing,
Anton Bikineev068d2912021-05-15 20:43:52259 /*service_token=*/absl::nullopt);
Eric Orth0704a01d2021-04-08 17:03:19260 return false;
261 }
262 // Chrome will always stop if DNS finds a result or will otherwise
263 // fallback to the system resolver (and get whatever behavior is
264 // configured in nsswitch.conf), so the only compatibility requirement
265 // is that `kDns` entries are configured to return on success.
266 if (!AreActionsCompatible(specification.actions,
267 {{NsswitchReader::Status::kSuccess,
268 NsswitchReader::Action::kReturn}})) {
269 RecordIncompatibleNsswitchReason(
Eric Ortha4b7ca02021-04-09 22:28:58270 IncompatibleNsswitchReason::kBadDnsActions,
271 specification.service);
Eric Orth0704a01d2021-04-08 17:03:19272 return false;
273 }
274
275 // Ignore any entries after `kDns` because Chrome will fallback to the
276 // system resolver if a result was not found in DNS.
277 return true;
278
279 case NsswitchReader::Service::kMdns:
280 case NsswitchReader::Service::kMdns4:
281 case NsswitchReader::Service::kMdns6:
282 case NsswitchReader::Service::kResolve:
283 RecordIncompatibleNsswitchReason(
Eric Ortha4b7ca02021-04-09 22:28:58284 IncompatibleNsswitchReason::kIncompatibleService,
285 specification.service);
Eric Orth0704a01d2021-04-08 17:03:19286 return false;
287
288 case NsswitchReader::Service::kMdnsMinimal:
289 case NsswitchReader::Service::kMdns4Minimal:
290 case NsswitchReader::Service::kMdns6Minimal:
291 // Always compatible as long as `kUnavailable` is `kContinue` because
292 // the service is expected to always result in `kUnavailable` for any
293 // names Chrome would attempt to resolve (non-*.local names because
294 // Chrome always delegates *.local names to the system resolver).
295 if (!AreActionsCompatible(specification.actions,
296 {{NsswitchReader::Status::kUnavailable,
297 NsswitchReader::Action::kContinue}})) {
298 RecordIncompatibleNsswitchReason(
Eric Ortha4b7ca02021-04-09 22:28:58299 IncompatibleNsswitchReason::kBadMdnsMinimalActions,
300 specification.service);
Eric Orth0704a01d2021-04-08 17:03:19301 return false;
302 }
303 break;
304
305 case NsswitchReader::Service::kMyHostname:
306 case NsswitchReader::Service::kNis:
307 // Similar enough to Chrome behavior (or unlikely to matter for Chrome
308 // resolutions) to be considered compatible unless the actions do
309 // something very weird to skip remaining services without a result.
310 if (!AreActionsCompatible(specification.actions,
311 {{NsswitchReader::Status::kNotFound,
312 NsswitchReader::Action::kContinue},
313 {NsswitchReader::Status::kUnavailable,
314 NsswitchReader::Action::kContinue},
315 {NsswitchReader::Status::kTryAgain,
316 NsswitchReader::Action::kContinue}})) {
317 RecordIncompatibleNsswitchReason(
Eric Ortha4b7ca02021-04-09 22:28:58318 IncompatibleNsswitchReason::kBadOtherServiceActions,
319 specification.service);
Eric Orth0704a01d2021-04-08 17:03:19320 return false;
321 }
322 break;
323 }
324 }
325
Eric Ortha4b7ca02021-04-09 22:28:58326 RecordIncompatibleNsswitchReason(IncompatibleNsswitchReason::kDnsMissing,
Anton Bikineev068d2912021-05-15 20:43:52327 /*service_token=*/absl::nullopt);
Eric Orth0704a01d2021-04-08 17:03:19328 return false;
Eric Orthc30a1b72021-04-07 22:32:37329}
330
331} // namespace
332
333class DnsConfigServiceLinux::Watcher : public DnsConfigService::Watcher {
334 public:
335 explicit Watcher(DnsConfigServiceLinux& service)
336 : DnsConfigService::Watcher(service) {}
337 ~Watcher() override = default;
338
339 Watcher(const Watcher&) = delete;
340 Watcher& operator=(const Watcher&) = delete;
341
342 bool Watch() override {
343 CheckOnCorrectSequence();
344
345 bool success = true;
346 if (!resolv_watcher_.Watch(
347 base::FilePath(kFilePathResolv),
348 base::FilePathWatcher::Type::kNonRecursive,
Eric Ortha4b7ca02021-04-09 22:28:58349 base::BindRepeating(&Watcher::OnResolvFilePathWatcherChange,
Eric Orthc30a1b72021-04-07 22:32:37350 base::Unretained(this)))) {
351 LOG(ERROR) << "DNS config (resolv.conf) watch failed to start.";
352 success = false;
353 }
354
355 if (!nsswitch_watcher_.Watch(
356 base::FilePath(kFilePathNsswitch),
357 base::FilePathWatcher::Type::kNonRecursive,
Eric Ortha4b7ca02021-04-09 22:28:58358 base::BindRepeating(&Watcher::OnNsswitchFilePathWatcherChange,
Eric Orthc30a1b72021-04-07 22:32:37359 base::Unretained(this)))) {
360 LOG(ERROR) << "DNS nsswitch.conf watch failed to start.";
361 success = false;
362 }
363
364 if (!hosts_watcher_.Watch(
365 base::FilePath(kFilePathHosts),
366 base::FilePathWatcher::Type::kNonRecursive,
367 base::BindRepeating(&Watcher::OnHostsFilePathWatcherChange,
368 base::Unretained(this)))) {
369 LOG(ERROR) << "DNS hosts watch failed to start.";
370 success = false;
371 }
372 return success;
373 }
374
375 private:
Eric Ortha4b7ca02021-04-09 22:28:58376 void OnResolvFilePathWatcherChange(const base::FilePath& path, bool error) {
Tsuyoshi Horoea5c57b2022-08-10 02:15:01377 base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.FileChange", true);
Eric Ortha4b7ca02021-04-09 22:28:58378 OnConfigChanged(!error);
379 }
380
381 void OnNsswitchFilePathWatcherChange(const base::FilePath& path, bool error) {
Tsuyoshi Horoea5c57b2022-08-10 02:15:01382 base::UmaHistogramBoolean("Net.DNS.DnsConfig.Nsswitch.FileChange", true);
Eric Orthc30a1b72021-04-07 22:32:37383 OnConfigChanged(!error);
384 }
385
386 void OnHostsFilePathWatcherChange(const base::FilePath& path, bool error) {
387 OnHostsChanged(!error);
388 }
389
390 base::FilePathWatcher resolv_watcher_;
391 base::FilePathWatcher nsswitch_watcher_;
392 base::FilePathWatcher hosts_watcher_;
393};
394
395// A SerialWorker that uses libresolv to initialize res_state and converts
396// it to DnsConfig.
397class DnsConfigServiceLinux::ConfigReader : public SerialWorker {
398 public:
399 explicit ConfigReader(DnsConfigServiceLinux& service,
400 std::unique_ptr<ResolvReader> resolv_reader,
401 std::unique_ptr<NsswitchReader> nsswitch_reader)
402 : service_(&service),
Eric Orth8ec1b8e2021-10-11 19:33:47403 work_item_(std::make_unique<WorkItem>(std::move(resolv_reader),
404 std::move(nsswitch_reader))) {
Eric Orthc30a1b72021-04-07 22:32:37405 // Allow execution on another thread; nothing thread-specific about
406 // constructor.
407 DETACH_FROM_SEQUENCE(sequence_checker_);
Eric Orthc30a1b72021-04-07 22:32:37408 }
409
Eric Orth8ec1b8e2021-10-11 19:33:47410 ~ConfigReader() override = default;
411
Eric Orthc30a1b72021-04-07 22:32:37412 ConfigReader(const ConfigReader&) = delete;
413 ConfigReader& operator=(const ConfigReader&) = delete;
414
Eric Orth8ec1b8e2021-10-11 19:33:47415 std::unique_ptr<SerialWorker::WorkItem> CreateWorkItem() override {
416 // Reuse same `WorkItem` to allow reuse of contained reader objects.
417 DCHECK(work_item_);
418 return std::move(work_item_);
Eric Orthc30a1b72021-04-07 22:32:37419 }
420
Erik Andersond328a542022-03-04 17:35:30421 bool OnWorkFinished(std::unique_ptr<SerialWorker::WorkItem>
Eric Orth8ec1b8e2021-10-11 19:33:47422 serial_worker_work_item) override {
423 DCHECK(serial_worker_work_item);
424 DCHECK(!work_item_);
Eric Orthc30a1b72021-04-07 22:32:37425 DCHECK(!IsCancelled());
Eric Orth8ec1b8e2021-10-11 19:33:47426
427 work_item_.reset(static_cast<WorkItem*>(serial_worker_work_item.release()));
428 if (work_item_->dns_config_.has_value()) {
429 service_->OnConfigRead(std::move(work_item_->dns_config_).value());
Erik Andersond328a542022-03-04 17:35:30430 return true;
Eric Orthc30a1b72021-04-07 22:32:37431 } else {
432 LOG(WARNING) << "Failed to read DnsConfig.";
Erik Andersond328a542022-03-04 17:35:30433 return false;
Eric Orthc30a1b72021-04-07 22:32:37434 }
435 }
436
437 private:
Eric Orth8ec1b8e2021-10-11 19:33:47438 class WorkItem : public SerialWorker::WorkItem {
439 public:
440 WorkItem(std::unique_ptr<ResolvReader> resolv_reader,
441 std::unique_ptr<NsswitchReader> nsswitch_reader)
442 : resolv_reader_(std::move(resolv_reader)),
443 nsswitch_reader_(std::move(nsswitch_reader)) {
444 DCHECK(resolv_reader_);
445 DCHECK(nsswitch_reader_);
446 }
Eric Orthc30a1b72021-04-07 22:32:37447
Eric Orth8ec1b8e2021-10-11 19:33:47448 void DoWork() override {
449 base::ScopedBlockingCall scoped_blocking_call(
450 FROM_HERE, base::BlockingType::MAY_BLOCK);
451
Dominique Fauteux-Chapleaue08899a2021-12-15 21:03:40452 {
453 std::unique_ptr<ScopedResState> res = resolv_reader_->GetResState();
454 if (res) {
455 dns_config_ = ConvertResStateToDnsConfig(res->state());
456 }
Eric Orth8ec1b8e2021-10-11 19:33:47457 }
458
Tsuyoshi Horoea5c57b2022-08-10 02:15:01459 base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.Read",
460 dns_config_.has_value());
Eric Orth8ec1b8e2021-10-11 19:33:47461 if (!dns_config_.has_value())
462 return;
Tsuyoshi Horoea5c57b2022-08-10 02:15:01463 base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.Valid",
464 dns_config_->IsValid());
465 base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.Compatible",
466 !dns_config_->unhandled_options);
Eric Orth8ec1b8e2021-10-11 19:33:47467
468 // Override `fallback_period` value to match default setting on
469 // Windows.
470 dns_config_->fallback_period = kDnsDefaultFallbackPeriod;
471
472 if (dns_config_ && !dns_config_->unhandled_options) {
473 std::vector<NsswitchReader::ServiceSpecification> nsswitch_hosts =
474 nsswitch_reader_->ReadAndParseHosts();
Tsuyoshi Horoea5c57b2022-08-10 02:15:01475 base::UmaHistogramCounts100("Net.DNS.DnsConfig.Nsswitch.NumServices",
476 nsswitch_hosts.size());
Eric Orth8ec1b8e2021-10-11 19:33:47477 dns_config_->unhandled_options =
478 !IsNsswitchConfigCompatible(nsswitch_hosts);
Tsuyoshi Horoea5c57b2022-08-10 02:15:01479 base::UmaHistogramBoolean("Net.DNS.DnsConfig.Nsswitch.Compatible",
480 !dns_config_->unhandled_options);
481 base::UmaHistogramBoolean(
482 "Net.DNS.DnsConfig.Nsswitch.NisServiceInHosts",
483 base::Contains(nsswitch_hosts, NsswitchReader::Service::kNis,
484 &NsswitchReader::ServiceSpecification::service));
Eric Orth8ec1b8e2021-10-11 19:33:47485 }
486 }
487
488 private:
489 friend class ConfigReader;
490 absl::optional<DnsConfig> dns_config_;
491 std::unique_ptr<ResolvReader> resolv_reader_;
492 std::unique_ptr<NsswitchReader> nsswitch_reader_;
493 };
494
495 // Raw pointer to owning DnsConfigService.
Keishi Hattorif28f4f82022-06-21 11:32:15496 const raw_ptr<DnsConfigServiceLinux> service_;
Eric Orth8ec1b8e2021-10-11 19:33:47497
498 // Null while the `WorkItem` is running on the `ThreadPool`.
499 std::unique_ptr<WorkItem> work_item_;
Eric Orthc30a1b72021-04-07 22:32:37500};
501
Eric Orthc30a1b72021-04-07 22:32:37502DnsConfigServiceLinux::DnsConfigServiceLinux()
503 : DnsConfigService(kFilePathHosts) {
504 // Allow constructing on one thread and living on another.
505 DETACH_FROM_SEQUENCE(sequence_checker_);
506}
507
508DnsConfigServiceLinux::~DnsConfigServiceLinux() {
509 if (config_reader_)
510 config_reader_->Cancel();
511}
512
513void DnsConfigServiceLinux::ReadConfigNow() {
514 if (!config_reader_)
515 CreateReader();
516 config_reader_->WorkNow();
517}
518
519bool DnsConfigServiceLinux::StartWatching() {
520 CreateReader();
521 watcher_ = std::make_unique<Watcher>(*this);
522 return watcher_->Watch();
523}
524
525void DnsConfigServiceLinux::CreateReader() {
526 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
527 DCHECK(!config_reader_);
528 DCHECK(resolv_reader_);
529 DCHECK(nsswitch_reader_);
Eric Orth8ec1b8e2021-10-11 19:33:47530 config_reader_ = std::make_unique<ConfigReader>(
Eric Orthc30a1b72021-04-07 22:32:37531 *this, std::move(resolv_reader_), std::move(nsswitch_reader_));
532}
533
Eric Orthf79bc2e2021-04-02 22:23:06534} // namespace internal
535
536// static
537std::unique_ptr<DnsConfigService> DnsConfigService::CreateSystemService() {
538 return std::make_unique<internal::DnsConfigServiceLinux>();
539}
540
541} // namespace net