Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 1 | // 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 Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 7 | #include <netdb.h> |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 8 | #include <netinet/in.h> |
| 9 | #include <resolv.h> |
| 10 | #include <sys/socket.h> |
| 11 | #include <sys/types.h> |
| 12 | |
Eric Orth | 0704a01d | 2021-04-08 17:03:19 | [diff] [blame] | 13 | #include <map> |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 14 | #include <memory> |
| 15 | #include <string> |
| 16 | #include <type_traits> |
| 17 | #include <utility> |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 18 | #include <vector> |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 19 | |
| 20 | #include "base/bind.h" |
| 21 | #include "base/callback.h" |
| 22 | #include "base/check.h" |
Tsuyoshi Horo | ea5c57b | 2022-08-10 02:15:01 | [diff] [blame^] | 23 | #include "base/containers/contains.h" |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 24 | #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 Hattori | f28f4f8 | 2022-06-21 11:32:15 | [diff] [blame] | 28 | #include "base/memory/raw_ptr.h" |
Tsuyoshi Horo | ea5c57b | 2022-08-10 02:15:01 | [diff] [blame^] | 29 | #include "base/metrics/histogram_functions.h" |
Eric Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 30 | #include "base/metrics/histogram_macros.h" |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 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" |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 36 | #include "net/dns/nsswitch_reader.h" |
Lina Ismail | 32c5ad4 | 2021-08-14 03:59:34 | [diff] [blame] | 37 | #include "net/dns/public/resolv_reader.h" |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 38 | #include "net/dns/serial_worker.h" |
Anton Bikineev | 068d291 | 2021-05-15 20:43:52 | [diff] [blame] | 39 | #include "third_party/abseil-cpp/absl/types/optional.h" |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 40 | |
| 41 | namespace net { |
| 42 | |
| 43 | namespace internal { |
| 44 | |
| 45 | namespace { |
| 46 | |
| 47 | const base::FilePath::CharType kFilePathHosts[] = |
| 48 | FILE_PATH_LITERAL("/etc/hosts"); |
| 49 | |
| 50 | #ifndef _PATH_RESCONF // Normally defined in <resolv.h> |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 51 | #define _PATH_RESCONF FILE_PATH_LITERAL("/etc/resolv.conf") |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 52 | #endif |
| 53 | |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 54 | constexpr base::FilePath::CharType kFilePathResolv[] = _PATH_RESCONF; |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 55 | |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 56 | #ifndef _PATH_NSSWITCH_CONF // Normally defined in <netdb.h> |
| 57 | #define _PATH_NSSWITCH_CONF FILE_PATH_LITERAL("/etc/nsswitch.conf") |
| 58 | #endif |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 59 | |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 60 | constexpr base::FilePath::CharType kFilePathNsswitch[] = _PATH_NSSWITCH_CONF; |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 61 | |
Anton Bikineev | 068d291 | 2021-05-15 20:43:52 | [diff] [blame] | 62 | absl::optional<DnsConfig> ConvertResStateToDnsConfig( |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 63 | const struct __res_state& res) { |
Lina Ismail | 32c5ad4 | 2021-08-14 03:59:34 | [diff] [blame] | 64 | absl::optional<std::vector<net::IPEndPoint>> nameservers = |
| 65 | GetNameservers(res); |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 66 | DnsConfig dns_config; |
| 67 | dns_config.unhandled_options = false; |
| 68 | |
Lina Ismail | 32c5ad4 | 2021-08-14 03:59:34 | [diff] [blame] | 69 | if (!nameservers.has_value()) |
Anton Bikineev | 068d291 | 2021-05-15 20:43:52 | [diff] [blame] | 70 | return absl::nullopt; |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 71 | |
Lina Ismail | 32c5ad4 | 2021-08-14 03:59:34 | [diff] [blame] | 72 | // Expected to be validated by GetNameservers() |
| 73 | DCHECK(res.options & RES_INIT); |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 74 | |
Lina Ismail | 32c5ad4 | 2021-08-14 03:59:34 | [diff] [blame] | 75 | dns_config.nameservers = std::move(nameservers.value()); |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 76 | 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 Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 82 | dns_config.fallback_period = base::Seconds(res.retrans); |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 83 | 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 Bikineev | 068d291 | 2021-05-15 20:43:52 | [diff] [blame] | 108 | return absl::nullopt; |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 109 | |
| 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 Bikineev | 068d291 | 2021-05-15 20:43:52 | [diff] [blame] | 113 | return absl::nullopt; |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 114 | } |
| 115 | return dns_config; |
| 116 | } |
| 117 | |
Eric Orth | 0704a01d | 2021-04-08 17:03:19 | [diff] [blame] | 118 | // 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). |
| 121 | bool 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`. |
| 151 | bool 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 Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 190 | // 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 Orth | 0704a01d | 2021-04-08 17:03:19 | [diff] [blame] | 193 | enum class IncompatibleNsswitchReason { |
| 194 | kFilesMissing = 0, |
Eric Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 195 | 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 Orth | 0704a01d | 2021-04-08 17:03:19 | [diff] [blame] | 204 | }; |
| 205 | |
Eric Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 206 | void RecordIncompatibleNsswitchReason( |
| 207 | IncompatibleNsswitchReason reason, |
Anton Bikineev | 068d291 | 2021-05-15 20:43:52 | [diff] [blame] | 208 | absl::optional<NsswitchReader::Service> service_token) { |
Tsuyoshi Horo | ea5c57b | 2022-08-10 02:15:01 | [diff] [blame^] | 209 | base::UmaHistogramEnumeration("Net.DNS.DnsConfig.Nsswitch.IncompatibleReason", |
| 210 | reason); |
Eric Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 211 | if (service_token) { |
Tsuyoshi Horo | ea5c57b | 2022-08-10 02:15:01 | [diff] [blame^] | 212 | base::UmaHistogramEnumeration( |
| 213 | "Net.DNS.DnsConfig.Nsswitch.IncompatibleService", |
| 214 | service_token.value()); |
Eric Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 215 | } |
Eric Orth | 0704a01d | 2021-04-08 17:03:19 | [diff] [blame] | 216 | } |
| 217 | |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 218 | bool IsNsswitchConfigCompatible( |
| 219 | const std::vector<NsswitchReader::ServiceSpecification>& nsswitch_hosts) { |
Eric Orth | 0704a01d | 2021-04-08 17:03:19 | [diff] [blame] | 220 | 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 Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 226 | IncompatibleNsswitchReason::kUnknownService, specification.service); |
Eric Orth | 0704a01d | 2021-04-08 17:03:19 | [diff] [blame] | 227 | return false; |
| 228 | |
| 229 | case NsswitchReader::Service::kFiles: |
| 230 | if (files_found) { |
| 231 | RecordIncompatibleNsswitchReason( |
Eric Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 232 | IncompatibleNsswitchReason::kMultipleFiles, |
| 233 | specification.service); |
Eric Orth | 0704a01d | 2021-04-08 17:03:19 | [diff] [blame] | 234 | 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 Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 249 | IncompatibleNsswitchReason::kBadFilesActions, |
| 250 | specification.service); |
Eric Orth | 0704a01d | 2021-04-08 17:03:19 | [diff] [blame] | 251 | return false; |
| 252 | } |
| 253 | break; |
| 254 | |
| 255 | case NsswitchReader::Service::kDns: |
| 256 | if (!files_found) { |
| 257 | RecordIncompatibleNsswitchReason( |
Eric Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 258 | IncompatibleNsswitchReason::kFilesMissing, |
Anton Bikineev | 068d291 | 2021-05-15 20:43:52 | [diff] [blame] | 259 | /*service_token=*/absl::nullopt); |
Eric Orth | 0704a01d | 2021-04-08 17:03:19 | [diff] [blame] | 260 | 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 Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 270 | IncompatibleNsswitchReason::kBadDnsActions, |
| 271 | specification.service); |
Eric Orth | 0704a01d | 2021-04-08 17:03:19 | [diff] [blame] | 272 | 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 Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 284 | IncompatibleNsswitchReason::kIncompatibleService, |
| 285 | specification.service); |
Eric Orth | 0704a01d | 2021-04-08 17:03:19 | [diff] [blame] | 286 | 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 Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 299 | IncompatibleNsswitchReason::kBadMdnsMinimalActions, |
| 300 | specification.service); |
Eric Orth | 0704a01d | 2021-04-08 17:03:19 | [diff] [blame] | 301 | 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 Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 318 | IncompatibleNsswitchReason::kBadOtherServiceActions, |
| 319 | specification.service); |
Eric Orth | 0704a01d | 2021-04-08 17:03:19 | [diff] [blame] | 320 | return false; |
| 321 | } |
| 322 | break; |
| 323 | } |
| 324 | } |
| 325 | |
Eric Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 326 | RecordIncompatibleNsswitchReason(IncompatibleNsswitchReason::kDnsMissing, |
Anton Bikineev | 068d291 | 2021-05-15 20:43:52 | [diff] [blame] | 327 | /*service_token=*/absl::nullopt); |
Eric Orth | 0704a01d | 2021-04-08 17:03:19 | [diff] [blame] | 328 | return false; |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 329 | } |
| 330 | |
| 331 | } // namespace |
| 332 | |
| 333 | class 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 Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 349 | base::BindRepeating(&Watcher::OnResolvFilePathWatcherChange, |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 350 | 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 Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 358 | base::BindRepeating(&Watcher::OnNsswitchFilePathWatcherChange, |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 359 | 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 Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 376 | void OnResolvFilePathWatcherChange(const base::FilePath& path, bool error) { |
Tsuyoshi Horo | ea5c57b | 2022-08-10 02:15:01 | [diff] [blame^] | 377 | base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.FileChange", true); |
Eric Orth | a4b7ca0 | 2021-04-09 22:28:58 | [diff] [blame] | 378 | OnConfigChanged(!error); |
| 379 | } |
| 380 | |
| 381 | void OnNsswitchFilePathWatcherChange(const base::FilePath& path, bool error) { |
Tsuyoshi Horo | ea5c57b | 2022-08-10 02:15:01 | [diff] [blame^] | 382 | base::UmaHistogramBoolean("Net.DNS.DnsConfig.Nsswitch.FileChange", true); |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 383 | 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. |
| 397 | class 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 Orth | 8ec1b8e | 2021-10-11 19:33:47 | [diff] [blame] | 403 | work_item_(std::make_unique<WorkItem>(std::move(resolv_reader), |
| 404 | std::move(nsswitch_reader))) { |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 405 | // Allow execution on another thread; nothing thread-specific about |
| 406 | // constructor. |
| 407 | DETACH_FROM_SEQUENCE(sequence_checker_); |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 408 | } |
| 409 | |
Eric Orth | 8ec1b8e | 2021-10-11 19:33:47 | [diff] [blame] | 410 | ~ConfigReader() override = default; |
| 411 | |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 412 | ConfigReader(const ConfigReader&) = delete; |
| 413 | ConfigReader& operator=(const ConfigReader&) = delete; |
| 414 | |
Eric Orth | 8ec1b8e | 2021-10-11 19:33:47 | [diff] [blame] | 415 | 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 Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 419 | } |
| 420 | |
Erik Anderson | d328a54 | 2022-03-04 17:35:30 | [diff] [blame] | 421 | bool OnWorkFinished(std::unique_ptr<SerialWorker::WorkItem> |
Eric Orth | 8ec1b8e | 2021-10-11 19:33:47 | [diff] [blame] | 422 | serial_worker_work_item) override { |
| 423 | DCHECK(serial_worker_work_item); |
| 424 | DCHECK(!work_item_); |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 425 | DCHECK(!IsCancelled()); |
Eric Orth | 8ec1b8e | 2021-10-11 19:33:47 | [diff] [blame] | 426 | |
| 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 Anderson | d328a54 | 2022-03-04 17:35:30 | [diff] [blame] | 430 | return true; |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 431 | } else { |
| 432 | LOG(WARNING) << "Failed to read DnsConfig."; |
Erik Anderson | d328a54 | 2022-03-04 17:35:30 | [diff] [blame] | 433 | return false; |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 434 | } |
| 435 | } |
| 436 | |
| 437 | private: |
Eric Orth | 8ec1b8e | 2021-10-11 19:33:47 | [diff] [blame] | 438 | 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 Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 447 | |
Eric Orth | 8ec1b8e | 2021-10-11 19:33:47 | [diff] [blame] | 448 | void DoWork() override { |
| 449 | base::ScopedBlockingCall scoped_blocking_call( |
| 450 | FROM_HERE, base::BlockingType::MAY_BLOCK); |
| 451 | |
Dominique Fauteux-Chapleau | e08899a | 2021-12-15 21:03:40 | [diff] [blame] | 452 | { |
| 453 | std::unique_ptr<ScopedResState> res = resolv_reader_->GetResState(); |
| 454 | if (res) { |
| 455 | dns_config_ = ConvertResStateToDnsConfig(res->state()); |
| 456 | } |
Eric Orth | 8ec1b8e | 2021-10-11 19:33:47 | [diff] [blame] | 457 | } |
| 458 | |
Tsuyoshi Horo | ea5c57b | 2022-08-10 02:15:01 | [diff] [blame^] | 459 | base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.Read", |
| 460 | dns_config_.has_value()); |
Eric Orth | 8ec1b8e | 2021-10-11 19:33:47 | [diff] [blame] | 461 | if (!dns_config_.has_value()) |
| 462 | return; |
Tsuyoshi Horo | ea5c57b | 2022-08-10 02:15:01 | [diff] [blame^] | 463 | 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 Orth | 8ec1b8e | 2021-10-11 19:33:47 | [diff] [blame] | 467 | |
| 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 Horo | ea5c57b | 2022-08-10 02:15:01 | [diff] [blame^] | 475 | base::UmaHistogramCounts100("Net.DNS.DnsConfig.Nsswitch.NumServices", |
| 476 | nsswitch_hosts.size()); |
Eric Orth | 8ec1b8e | 2021-10-11 19:33:47 | [diff] [blame] | 477 | dns_config_->unhandled_options = |
| 478 | !IsNsswitchConfigCompatible(nsswitch_hosts); |
Tsuyoshi Horo | ea5c57b | 2022-08-10 02:15:01 | [diff] [blame^] | 479 | 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 Orth | 8ec1b8e | 2021-10-11 19:33:47 | [diff] [blame] | 485 | } |
| 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 Hattori | f28f4f8 | 2022-06-21 11:32:15 | [diff] [blame] | 496 | const raw_ptr<DnsConfigServiceLinux> service_; |
Eric Orth | 8ec1b8e | 2021-10-11 19:33:47 | [diff] [blame] | 497 | |
| 498 | // Null while the `WorkItem` is running on the `ThreadPool`. |
| 499 | std::unique_ptr<WorkItem> work_item_; |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 500 | }; |
| 501 | |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 502 | DnsConfigServiceLinux::DnsConfigServiceLinux() |
| 503 | : DnsConfigService(kFilePathHosts) { |
| 504 | // Allow constructing on one thread and living on another. |
| 505 | DETACH_FROM_SEQUENCE(sequence_checker_); |
| 506 | } |
| 507 | |
| 508 | DnsConfigServiceLinux::~DnsConfigServiceLinux() { |
| 509 | if (config_reader_) |
| 510 | config_reader_->Cancel(); |
| 511 | } |
| 512 | |
| 513 | void DnsConfigServiceLinux::ReadConfigNow() { |
| 514 | if (!config_reader_) |
| 515 | CreateReader(); |
| 516 | config_reader_->WorkNow(); |
| 517 | } |
| 518 | |
| 519 | bool DnsConfigServiceLinux::StartWatching() { |
| 520 | CreateReader(); |
| 521 | watcher_ = std::make_unique<Watcher>(*this); |
| 522 | return watcher_->Watch(); |
| 523 | } |
| 524 | |
| 525 | void DnsConfigServiceLinux::CreateReader() { |
| 526 | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 527 | DCHECK(!config_reader_); |
| 528 | DCHECK(resolv_reader_); |
| 529 | DCHECK(nsswitch_reader_); |
Eric Orth | 8ec1b8e | 2021-10-11 19:33:47 | [diff] [blame] | 530 | config_reader_ = std::make_unique<ConfigReader>( |
Eric Orth | c30a1b7 | 2021-04-07 22:32:37 | [diff] [blame] | 531 | *this, std::move(resolv_reader_), std::move(nsswitch_reader_)); |
| 532 | } |
| 533 | |
Eric Orth | f79bc2e | 2021-04-02 22:23:06 | [diff] [blame] | 534 | } // namespace internal |
| 535 | |
| 536 | // static |
| 537 | std::unique_ptr<DnsConfigService> DnsConfigService::CreateSystemService() { |
| 538 | return std::make_unique<internal::DnsConfigServiceLinux>(); |
| 539 | } |
| 540 | |
| 541 | } // namespace net |