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_apple.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 NetworkConfigWatcherApple::Delegate* net_config_delegate =
35 static_cast<NetworkConfigWatcherApple::Delegate*>(config_delegate);
36 net_config_delegate->OnNetworkConfigChange(changed_keys);
37 }
38 #endif // !BUILDFLAG(IS_IOS)
39
40 } // namespace
41
42 class NetworkConfigWatcherAppleThread : public base::Thread {
43 public:
44 explicit NetworkConfigWatcherAppleThread(
45 NetworkConfigWatcherApple::Delegate* delegate);
46 NetworkConfigWatcherAppleThread(const NetworkConfigWatcherAppleThread&) = delete;
47 NetworkConfigWatcherAppleThread& operator=(
48 const NetworkConfigWatcherAppleThread&) = delete;
49 ~NetworkConfigWatcherAppleThread() 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::apple::ScopedCFTypeRef<CFRunLoopSourceRef> run_loop_source_;
65 const raw_ptr<NetworkConfigWatcherApple::Delegate> delegate_;
66 #if !BUILDFLAG(IS_IOS)
67 int num_retry_ = 0;
68 #endif // !BUILDFLAG(IS_IOS)
69 base::WeakPtrFactory<NetworkConfigWatcherAppleThread> weak_factory_;
70 };
71
NetworkConfigWatcherAppleThread(NetworkConfigWatcherApple::Delegate * delegate)72 NetworkConfigWatcherAppleThread::NetworkConfigWatcherAppleThread(
73 NetworkConfigWatcherApple::Delegate* delegate)
74 : base::Thread("NetworkConfigWatcher"),
75 delegate_(delegate),
76 weak_factory_(this) {}
77
~NetworkConfigWatcherAppleThread()78 NetworkConfigWatcherAppleThread::~NetworkConfigWatcherAppleThread() {
79 // This is expected to be invoked during shutdown.
80 base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_thread_join;
81 Stop();
82 }
83
Init()84 void NetworkConfigWatcherAppleThread::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(&NetworkConfigWatcherAppleThread::InitNotifications,
93 weak_factory_.GetWeakPtr()),
94 kInitializationDelay);
95 }
96
CleanUp()97 void NetworkConfigWatcherAppleThread::CleanUp() {
98 if (!run_loop_source_.get())
99 return;
100 delegate_->CleanUpOnNotifierThread();
101
102 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), run_loop_source_.get(),
103 kCFRunLoopCommonModes);
104 run_loop_source_.reset();
105 }
106
InitNotifications()107 void NetworkConfigWatcherAppleThread::InitNotifications() {
108 // If initialization fails, retry after a 1s delay.
109 bool success = InitNotificationsHelper();
110
111 #if !BUILDFLAG(IS_IOS)
112 if (!success && num_retry_ < kMaxRetry) {
113 LOG(ERROR) << "Retrying SystemConfiguration registration in 1 second.";
114 task_runner()->PostDelayedTask(
115 FROM_HERE,
116 base::BindOnce(&NetworkConfigWatcherAppleThread::InitNotifications,
117 weak_factory_.GetWeakPtr()),
118 kRetryInterval);
119 num_retry_++;
120 return;
121 }
122
123 #else
124 DCHECK(success);
125 #endif // !BUILDFLAG(IS_IOS)
126 }
127
InitNotificationsHelper()128 bool NetworkConfigWatcherAppleThread::InitNotificationsHelper() {
129 #if !BUILDFLAG(IS_IOS)
130 // SCDynamicStore API does not exist on iOS.
131 // Add a run loop source for a dynamic store to the current run loop.
132 SCDynamicStoreContext context = {
133 0, // Version 0.
134 delegate_, // User data.
135 nullptr, // This is not reference counted. No retain function.
136 nullptr, // This is not reference counted. No release function.
137 nullptr, // No description for this.
138 };
139 base::apple::ScopedCFTypeRef<SCDynamicStoreRef> store(SCDynamicStoreCreate(
140 nullptr, CFSTR("org.chromium"), DynamicStoreCallback, &context));
141 if (!store) {
142 int error = SCError();
143 LOG(ERROR) << "SCDynamicStoreCreate failed with Error: " << error << " - "
144 << SCErrorString(error);
145 return false;
146 }
147 run_loop_source_.reset(
148 SCDynamicStoreCreateRunLoopSource(nullptr, store.get(), 0));
149 if (!run_loop_source_) {
150 int error = SCError();
151 LOG(ERROR) << "SCDynamicStoreCreateRunLoopSource failed with Error: "
152 << error << " - " << SCErrorString(error);
153 return false;
154 }
155 CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source_.get(),
156 kCFRunLoopCommonModes);
157 #endif // !BUILDFLAG(IS_IOS)
158
159 // Set up notifications for interface and IP address changes.
160 delegate_->StartReachabilityNotifications();
161 #if !BUILDFLAG(IS_IOS)
162 delegate_->SetDynamicStoreNotificationKeys(std::move(store));
163 #endif // !BUILDFLAG(IS_IOS)
164 return true;
165 }
166
NetworkConfigWatcherApple(Delegate * delegate)167 NetworkConfigWatcherApple::NetworkConfigWatcherApple(Delegate* delegate)
168 : notifier_thread_(
169 std::make_unique<NetworkConfigWatcherAppleThread>(delegate)) {
170 // We create this notifier thread because the notification implementation
171 // needs a thread with a CFRunLoop, and there's no guarantee that
172 // CurrentThread::Get() meets that criterion.
173 base::Thread::Options thread_options(base::MessagePumpType::UI, 0);
174 notifier_thread_->StartWithOptions(std::move(thread_options));
175 }
176
177 NetworkConfigWatcherApple::~NetworkConfigWatcherApple() = default;
178
GetNotifierThreadForTest()179 base::Thread* NetworkConfigWatcherApple::GetNotifierThreadForTest() {
180 return notifier_thread_.get();
181 }
182
183 } // namespace net
184