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