• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 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 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9 
10 #include "net/dns/dns_config_service_posix.h"
11 
12 #include <memory>
13 #include <optional>
14 #include <string>
15 #include <type_traits>
16 #include <utility>
17 
18 #include "base/files/file.h"
19 #include "base/files/file_path.h"
20 #include "base/files/file_path_watcher.h"
21 #include "base/functional/bind.h"
22 #include "base/lazy_instance.h"
23 #include "base/location.h"
24 #include "base/logging.h"
25 #include "base/memory/raw_ptr.h"
26 #include "base/sequence_checker.h"
27 #include "base/task/single_thread_task_runner.h"
28 #include "base/threading/scoped_blocking_call.h"
29 #include "base/time/time.h"
30 #include "build/build_config.h"
31 #include "net/base/ip_endpoint.h"
32 #include "net/dns/dns_config.h"
33 #include "net/dns/dns_hosts.h"
34 #include "net/dns/notify_watcher_mac.h"
35 #include "net/dns/public/resolv_reader.h"
36 #include "net/dns/serial_worker.h"
37 
38 #if BUILDFLAG(IS_MAC)
39 #include "net/dns/dns_config_watcher_mac.h"
40 #endif
41 
42 namespace net {
43 
44 namespace internal {
45 
46 namespace {
47 
48 const base::FilePath::CharType kFilePathHosts[] =
49     FILE_PATH_LITERAL("/etc/hosts");
50 
51 #if BUILDFLAG(IS_IOS)
52 // There is no public API to watch the DNS configuration on iOS.
53 class DnsConfigWatcher {
54  public:
55   using CallbackType = base::RepeatingCallback<void(bool succeeded)>;
56 
Watch(const CallbackType & callback)57   bool Watch(const CallbackType& callback) {
58     return false;
59   }
60 };
61 
62 #elif BUILDFLAG(IS_MAC)
63 
64 // DnsConfigWatcher for OS_MAC is in dns_config_watcher_mac.{hh,cc}.
65 
66 #else  // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_MAC)
67 
68 #ifndef _PATH_RESCONF  // Normally defined in <resolv.h>
69 #define _PATH_RESCONF "/etc/resolv.conf"
70 #endif
71 
72 const base::FilePath::CharType kFilePathConfig[] =
73     FILE_PATH_LITERAL(_PATH_RESCONF);
74 
75 class DnsConfigWatcher {
76  public:
77   using CallbackType = base::RepeatingCallback<void(bool succeeded)>;
78 
Watch(const CallbackType & callback)79   bool Watch(const CallbackType& callback) {
80     callback_ = callback;
81     return watcher_.Watch(base::FilePath(kFilePathConfig),
82                           base::FilePathWatcher::Type::kNonRecursive,
83                           base::BindRepeating(&DnsConfigWatcher::OnCallback,
84                                               base::Unretained(this)));
85   }
86 
87  private:
OnCallback(const base::FilePath & path,bool error)88   void OnCallback(const base::FilePath& path, bool error) {
89     callback_.Run(!error);
90   }
91 
92   base::FilePathWatcher watcher_;
93   CallbackType callback_;
94 };
95 #endif  // BUILDFLAG(IS_IOS)
96 
ReadDnsConfig()97 std::optional<DnsConfig> ReadDnsConfig() {
98   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
99                                                 base::BlockingType::MAY_BLOCK);
100 
101   std::optional<DnsConfig> dns_config;
102   {
103     std::unique_ptr<ScopedResState> scoped_res_state =
104         ResolvReader().GetResState();
105     if (scoped_res_state) {
106       dns_config = ConvertResStateToDnsConfig(scoped_res_state->state());
107     }
108   }
109 
110   if (!dns_config.has_value())
111     return dns_config;
112 
113 #if BUILDFLAG(IS_MAC)
114   if (!DnsConfigWatcher::CheckDnsConfig(
115           dns_config->unhandled_options /* out_unhandled_options */)) {
116     return std::nullopt;
117   }
118 #endif  // BUILDFLAG(IS_MAC)
119   // Override |fallback_period| value to match default setting on Windows.
120   dns_config->fallback_period = kDnsDefaultFallbackPeriod;
121   return dns_config;
122 }
123 
124 }  // namespace
125 
126 class DnsConfigServicePosix::Watcher : public DnsConfigService::Watcher {
127  public:
Watcher(DnsConfigServicePosix & service)128   explicit Watcher(DnsConfigServicePosix& service)
129       : DnsConfigService::Watcher(service) {}
130 
131   Watcher(const Watcher&) = delete;
132   Watcher& operator=(const Watcher&) = delete;
133 
134   ~Watcher() override = default;
135 
Watch()136   bool Watch() override {
137     CheckOnCorrectSequence();
138 
139     bool success = true;
140     if (!config_watcher_.Watch(base::BindRepeating(&Watcher::OnConfigChanged,
141                                                    base::Unretained(this)))) {
142       LOG(ERROR) << "DNS config watch failed to start.";
143       success = false;
144     }
145 // Hosts file should never change on iOS, so don't watch it there.
146 #if !BUILDFLAG(IS_IOS)
147     if (!hosts_watcher_.Watch(
148             base::FilePath(kFilePathHosts),
149             base::FilePathWatcher::Type::kNonRecursive,
150             base::BindRepeating(&Watcher::OnHostsFilePathWatcherChange,
151                                 base::Unretained(this)))) {
152       LOG(ERROR) << "DNS hosts watch failed to start.";
153       success = false;
154     }
155 #endif  // !BUILDFLAG(IS_IOS)
156     return success;
157   }
158 
159  private:
160 #if !BUILDFLAG(IS_IOS)
OnHostsFilePathWatcherChange(const base::FilePath & path,bool error)161   void OnHostsFilePathWatcherChange(const base::FilePath& path, bool error) {
162     OnHostsChanged(!error);
163   }
164 #endif  // !BUILDFLAG(IS_IOS)
165 
166   DnsConfigWatcher config_watcher_;
167 #if !BUILDFLAG(IS_IOS)
168   base::FilePathWatcher hosts_watcher_;
169 #endif  // !BUILDFLAG(IS_IOS)
170 };
171 
172 // A SerialWorker that uses libresolv to initialize res_state and converts
173 // it to DnsConfig.
174 class DnsConfigServicePosix::ConfigReader : public SerialWorker {
175  public:
ConfigReader(DnsConfigServicePosix & service)176   explicit ConfigReader(DnsConfigServicePosix& service) : service_(&service) {
177     // Allow execution on another thread; nothing thread-specific about
178     // constructor.
179     DETACH_FROM_SEQUENCE(sequence_checker_);
180   }
181 
182   ~ConfigReader() override = default;
183 
184   ConfigReader(const ConfigReader&) = delete;
185   ConfigReader& operator=(const ConfigReader&) = delete;
186 
CreateWorkItem()187   std::unique_ptr<SerialWorker::WorkItem> CreateWorkItem() override {
188     return std::make_unique<WorkItem>();
189   }
190 
OnWorkFinished(std::unique_ptr<SerialWorker::WorkItem> serial_worker_work_item)191   bool OnWorkFinished(std::unique_ptr<SerialWorker::WorkItem>
192                           serial_worker_work_item) override {
193     DCHECK(serial_worker_work_item);
194     DCHECK(!IsCancelled());
195 
196     WorkItem* work_item = static_cast<WorkItem*>(serial_worker_work_item.get());
197     if (work_item->dns_config_.has_value()) {
198       service_->OnConfigRead(std::move(work_item->dns_config_).value());
199       return true;
200     } else {
201       LOG(WARNING) << "Failed to read DnsConfig.";
202       return false;
203     }
204   }
205 
206  private:
207   class WorkItem : public SerialWorker::WorkItem {
208    public:
DoWork()209     void DoWork() override { dns_config_ = ReadDnsConfig(); }
210 
211    private:
212     friend class ConfigReader;
213     std::optional<DnsConfig> dns_config_;
214   };
215 
216   // Raw pointer to owning DnsConfigService.
217   const raw_ptr<DnsConfigServicePosix> service_;
218 };
219 
DnsConfigServicePosix()220 DnsConfigServicePosix::DnsConfigServicePosix()
221     : DnsConfigService(kFilePathHosts) {
222   // Allow constructing on one thread and living on another.
223   DETACH_FROM_SEQUENCE(sequence_checker_);
224 }
225 
~DnsConfigServicePosix()226 DnsConfigServicePosix::~DnsConfigServicePosix() {
227   if (config_reader_)
228     config_reader_->Cancel();
229 }
230 
RefreshConfig()231 void DnsConfigServicePosix::RefreshConfig() {
232   InvalidateConfig();
233   InvalidateHosts();
234   ReadConfigNow();
235   ReadHostsNow();
236 }
237 
ReadConfigNow()238 void DnsConfigServicePosix::ReadConfigNow() {
239   if (!config_reader_)
240     CreateReader();
241   config_reader_->WorkNow();
242 }
243 
StartWatching()244 bool DnsConfigServicePosix::StartWatching() {
245   CreateReader();
246   // TODO(szym): re-start watcher if that makes sense. http://crbug.com/116139
247   watcher_ = std::make_unique<Watcher>(*this);
248   return watcher_->Watch();
249 }
250 
CreateReader()251 void DnsConfigServicePosix::CreateReader() {
252   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
253   DCHECK(!config_reader_);
254   config_reader_ = std::make_unique<ConfigReader>(*this);
255 }
256 
ConvertResStateToDnsConfig(const struct __res_state & res)257 std::optional<DnsConfig> ConvertResStateToDnsConfig(
258     const struct __res_state& res) {
259   DnsConfig dns_config;
260   dns_config.unhandled_options = false;
261 
262   if (!(res.options & RES_INIT))
263     return std::nullopt;
264 
265   std::optional<std::vector<IPEndPoint>> nameservers = GetNameservers(res);
266   if (!nameservers)
267     return std::nullopt;
268 
269   dns_config.nameservers = std::move(*nameservers);
270   dns_config.search.clear();
271   for (int i = 0; (i < MAXDNSRCH) && res.dnsrch[i]; ++i) {
272     dns_config.search.emplace_back(res.dnsrch[i]);
273   }
274 
275   dns_config.ndots = res.ndots;
276   dns_config.fallback_period = base::Seconds(res.retrans);
277   dns_config.attempts = res.retry;
278 #if defined(RES_ROTATE)
279   dns_config.rotate = res.options & RES_ROTATE;
280 #endif
281 #if !defined(RES_USE_DNSSEC)
282   // Some versions of libresolv don't have support for the DO bit. In this
283   // case, we proceed without it.
284   static const int RES_USE_DNSSEC = 0;
285 #endif
286 
287   // The current implementation assumes these options are set. They normally
288   // cannot be overwritten by /etc/resolv.conf
289   const unsigned kRequiredOptions = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
290   if ((res.options & kRequiredOptions) != kRequiredOptions) {
291     dns_config.unhandled_options = true;
292     return dns_config;
293   }
294 
295   const unsigned kUnhandledOptions = RES_USEVC | RES_IGNTC | RES_USE_DNSSEC;
296   if (res.options & kUnhandledOptions) {
297     dns_config.unhandled_options = true;
298     return dns_config;
299   }
300 
301   if (dns_config.nameservers.empty())
302     return std::nullopt;
303 
304   // If any name server is 0.0.0.0, assume the configuration is invalid.
305   // TODO(szym): Measure how often this happens. http://crbug.com/125599
306   for (const IPEndPoint& nameserver : dns_config.nameservers) {
307     if (nameserver.address().IsZero())
308       return std::nullopt;
309   }
310   return dns_config;
311 }
312 
313 }  // namespace internal
314 
315 // static
CreateSystemService()316 std::unique_ptr<DnsConfigService> DnsConfigService::CreateSystemService() {
317   // DnsConfigService on iOS doesn't watch the config so its result can become
318   // inaccurate at any time.  Disable it to prevent promulgation of inaccurate
319   // DnsConfigs.
320 #if BUILDFLAG(IS_IOS)
321   return nullptr;
322 #else   // BUILDFLAG(IS_IOS)
323   return std::make_unique<internal::DnsConfigServicePosix>();
324 #endif  // BUILDFLAG(IS_IOS)
325 }
326 
327 }  // namespace net
328