• 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 #include "net/base/network_config_watcher_mac.h"
6 
7 #include <algorithm>
8 
9 #include "base/compiler_specific.h"
10 #include "base/functional/bind.h"
11 #include "base/logging.h"
12 #include "base/memory/raw_ptr.h"
13 #include "base/memory/weak_ptr.h"
14 #include "base/message_loop/message_pump_type.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "base/task/single_thread_task_runner.h"
17 #include "base/threading/thread.h"
18 #include "base/threading/thread_restrictions.h"
19 #include "build/build_config.h"
20 
21 namespace net {
22 
23 namespace {
24 
25 // SCDynamicStore API does not exist on iOS.
26 #if !BUILDFLAG(IS_IOS)
27 const base::TimeDelta kRetryInterval = base::Seconds(1);
28 const int kMaxRetry = 5;
29 
30 // Called back by OS.  Calls OnNetworkConfigChange().
DynamicStoreCallback(SCDynamicStoreRef,CFArrayRef changed_keys,void * config_delegate)31 void DynamicStoreCallback(SCDynamicStoreRef /* store */,
32                           CFArrayRef changed_keys,
33                           void* config_delegate) {
34   NetworkConfigWatcherMac::Delegate* net_config_delegate =
35       static_cast<NetworkConfigWatcherMac::Delegate*>(config_delegate);
36   net_config_delegate->OnNetworkConfigChange(changed_keys);
37 }
38 #endif  // !BUILDFLAG(IS_IOS)
39 
40 }  // namespace
41 
42 class NetworkConfigWatcherMacThread : public base::Thread {
43  public:
44   explicit NetworkConfigWatcherMacThread(
45       NetworkConfigWatcherMac::Delegate* delegate);
46   NetworkConfigWatcherMacThread(const NetworkConfigWatcherMacThread&) = delete;
47   NetworkConfigWatcherMacThread& operator=(
48       const NetworkConfigWatcherMacThread&) = delete;
49   ~NetworkConfigWatcherMacThread() override;
50 
51  protected:
52   // base::Thread
53   void Init() override;
54   void CleanUp() override;
55 
56  private:
57   // The SystemConfiguration calls in this function can lead to contention early
58   // on, so we invoke this function later on in startup to keep it fast.
59   void InitNotifications();
60 
61   // Returns whether initializing notifications has succeeded.
62   bool InitNotificationsHelper();
63 
64   base::ScopedCFTypeRef<CFRunLoopSourceRef> run_loop_source_;
65   const raw_ptr<NetworkConfigWatcherMac::Delegate> delegate_;
66 #if !BUILDFLAG(IS_IOS)
67   int num_retry_ = 0;
68 #endif  // !BUILDFLAG(IS_IOS)
69   base::WeakPtrFactory<NetworkConfigWatcherMacThread> weak_factory_;
70 };
71 
NetworkConfigWatcherMacThread(NetworkConfigWatcherMac::Delegate * delegate)72 NetworkConfigWatcherMacThread::NetworkConfigWatcherMacThread(
73     NetworkConfigWatcherMac::Delegate* delegate)
74     : base::Thread("NetworkConfigWatcher"),
75       delegate_(delegate),
76       weak_factory_(this) {}
77 
~NetworkConfigWatcherMacThread()78 NetworkConfigWatcherMacThread::~NetworkConfigWatcherMacThread() {
79   // This is expected to be invoked during shutdown.
80   base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_thread_join;
81   Stop();
82 }
83 
Init()84 void NetworkConfigWatcherMacThread::Init() {
85   delegate_->Init();
86 
87   // TODO(willchan): Look to see if there's a better signal for when it's ok to
88   // initialize this, rather than just delaying it by a fixed time.
89   const base::TimeDelta kInitializationDelay = base::Seconds(1);
90   task_runner()->PostDelayedTask(
91       FROM_HERE,
92       base::BindOnce(&NetworkConfigWatcherMacThread::InitNotifications,
93                      weak_factory_.GetWeakPtr()),
94       kInitializationDelay);
95 }
96 
CleanUp()97 void NetworkConfigWatcherMacThread::CleanUp() {
98   if (!run_loop_source_.get())
99     return;
100 
101   CFRunLoopRemoveSource(CFRunLoopGetCurrent(), run_loop_source_.get(),
102                         kCFRunLoopCommonModes);
103   run_loop_source_.reset();
104 }
105 
InitNotifications()106 void NetworkConfigWatcherMacThread::InitNotifications() {
107   // If initialization fails, retry after a 1s delay.
108   bool success = InitNotificationsHelper();
109 
110 #if !BUILDFLAG(IS_IOS)
111   if (!success && num_retry_ < kMaxRetry) {
112     LOG(ERROR) << "Retrying SystemConfiguration registration in 1 second.";
113     task_runner()->PostDelayedTask(
114         FROM_HERE,
115         base::BindOnce(&NetworkConfigWatcherMacThread::InitNotifications,
116                        weak_factory_.GetWeakPtr()),
117         kRetryInterval);
118     num_retry_++;
119     return;
120   }
121 
122 #else
123   DCHECK(success);
124 #endif  // !BUILDFLAG(IS_IOS)
125 }
126 
InitNotificationsHelper()127 bool NetworkConfigWatcherMacThread::InitNotificationsHelper() {
128 #if !BUILDFLAG(IS_IOS)
129   // SCDynamicStore API does not exist on iOS.
130   // Add a run loop source for a dynamic store to the current run loop.
131   SCDynamicStoreContext context = {
132       0,          // Version 0.
133       delegate_,  // User data.
134       nullptr,    // This is not reference counted.  No retain function.
135       nullptr,    // This is not reference counted.  No release function.
136       nullptr,    // No description for this.
137   };
138   base::ScopedCFTypeRef<SCDynamicStoreRef> store(SCDynamicStoreCreate(
139       nullptr, CFSTR("org.chromium"), DynamicStoreCallback, &context));
140   if (!store) {
141     int error = SCError();
142     LOG(ERROR) << "SCDynamicStoreCreate failed with Error: " << error << " - "
143                << SCErrorString(error);
144     return false;
145   }
146   run_loop_source_.reset(
147       SCDynamicStoreCreateRunLoopSource(nullptr, store.get(), 0));
148   if (!run_loop_source_) {
149     int error = SCError();
150     LOG(ERROR) << "SCDynamicStoreCreateRunLoopSource failed with Error: "
151                << error << " - " << SCErrorString(error);
152     return false;
153   }
154   CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source_.get(),
155                      kCFRunLoopCommonModes);
156 #endif  // !BUILDFLAG(IS_IOS)
157 
158   // Set up notifications for interface and IP address changes.
159   delegate_->StartReachabilityNotifications();
160 #if !BUILDFLAG(IS_IOS)
161   delegate_->SetDynamicStoreNotificationKeys(store.get());
162 #endif  // !BUILDFLAG(IS_IOS)
163   return true;
164 }
165 
NetworkConfigWatcherMac(Delegate * delegate)166 NetworkConfigWatcherMac::NetworkConfigWatcherMac(Delegate* delegate)
167     : notifier_thread_(
168           std::make_unique<NetworkConfigWatcherMacThread>(delegate)) {
169   // We create this notifier thread because the notification implementation
170   // needs a thread with a CFRunLoop, and there's no guarantee that
171   // CurrentThread::Get() meets that criterion.
172   base::Thread::Options thread_options(base::MessagePumpType::UI, 0);
173   notifier_thread_->StartWithOptions(std::move(thread_options));
174 }
175 
176 NetworkConfigWatcherMac::~NetworkConfigWatcherMac() = default;
177 
178 }  // namespace net
179