1 // Copyright (c) 2012 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/base/network_config_watcher_mac.h"
6
7 #include <algorithm>
8
9 #include "base/bind.h"
10 #include "base/compiler_specific.h"
11 #include "base/logging.h"
12 #include "base/memory/weak_ptr.h"
13 #include "base/message_loop/message_pump_type.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/single_thread_task_runner.h"
16 #include "base/threading/thread.h"
17 #include "base/threading/thread_restrictions.h"
18 #include "build/build_config.h"
19
20 namespace net {
21
22 namespace {
23
24 // SCDynamicStore API does not exist on iOS.
25 #if !defined(OS_IOS)
26 const base::TimeDelta kRetryInterval = base::TimeDelta::FromSeconds(1);
27 const int kMaxRetry = 5;
28
29 // Maps SCError to an enum for UMA logging. These values are persisted to logs,
30 // and should not be renumbered. Added to investigate https://crbug.com/547877.
31 enum class SCStatusCode {
32 // Unmapped error codes.
33 SC_UNKNOWN = 0,
34
35 // These map to the corresponding SCError.
36 SC_OK = 1,
37 SC_FAILED = 2,
38 SC_INVALID_ARGUMENT = 3,
39 SC_ACCESS_ERROR = 4,
40 SC_NO_KEY = 5,
41 SC_KEY_EXISTS = 6,
42 SC_LOCKED = 7,
43 SC_NEED_LOCK = 8,
44 SC_NO_STORE_SESSION = 9,
45 SC_NO_STORE_SERVER = 10,
46 SC_NOTIFIER_ACTIVE = 11,
47 SC_NO_PREFS_SESSION = 12,
48 SC_PREFS_BUSY = 13,
49 SC_NO_CONFIG_FILE = 14,
50 SC_NO_LINK = 15,
51 SC_STALE = 16,
52 SC_MAX_LINK = 17,
53 SC_REACHABILITY_UNKNOWN = 18,
54 SC_CONNECTION_NO_SERVICE = 19,
55 SC_CONNECTION_IGNORE = 20,
56
57 // Maximum value for histogram bucket.
58 SC_COUNT,
59 };
60
ConvertToSCStatusCode(int sc_error)61 SCStatusCode ConvertToSCStatusCode(int sc_error) {
62 switch (sc_error) {
63 case kSCStatusOK:
64 return SCStatusCode::SC_OK;
65 case kSCStatusFailed:
66 return SCStatusCode::SC_FAILED;
67 case kSCStatusInvalidArgument:
68 return SCStatusCode::SC_INVALID_ARGUMENT;
69 case kSCStatusAccessError:
70 return SCStatusCode::SC_ACCESS_ERROR;
71 case kSCStatusNoKey:
72 return SCStatusCode::SC_NO_KEY;
73 case kSCStatusKeyExists:
74 return SCStatusCode::SC_KEY_EXISTS;
75 case kSCStatusLocked:
76 return SCStatusCode::SC_LOCKED;
77 case kSCStatusNeedLock:
78 return SCStatusCode::SC_NEED_LOCK;
79 case kSCStatusNoStoreSession:
80 return SCStatusCode::SC_NO_STORE_SESSION;
81 case kSCStatusNoStoreServer:
82 return SCStatusCode::SC_NO_STORE_SERVER;
83 case kSCStatusNotifierActive:
84 return SCStatusCode::SC_NOTIFIER_ACTIVE;
85 case kSCStatusNoPrefsSession:
86 return SCStatusCode::SC_NO_PREFS_SESSION;
87 case kSCStatusPrefsBusy:
88 return SCStatusCode::SC_PREFS_BUSY;
89 case kSCStatusNoConfigFile:
90 return SCStatusCode::SC_NO_CONFIG_FILE;
91 case kSCStatusNoLink:
92 return SCStatusCode::SC_NO_LINK;
93 case kSCStatusStale:
94 return SCStatusCode::SC_STALE;
95 case kSCStatusMaxLink:
96 return SCStatusCode::SC_MAX_LINK;
97 case kSCStatusReachabilityUnknown:
98 return SCStatusCode::SC_REACHABILITY_UNKNOWN;
99 case kSCStatusConnectionNoService:
100 return SCStatusCode::SC_CONNECTION_NO_SERVICE;
101 case kSCStatusConnectionIgnore:
102 return SCStatusCode::SC_CONNECTION_IGNORE;
103 default:
104 return SCStatusCode::SC_UNKNOWN;
105 }
106 }
107
108 // Called back by OS. Calls OnNetworkConfigChange().
DynamicStoreCallback(SCDynamicStoreRef,CFArrayRef changed_keys,void * config_delegate)109 void DynamicStoreCallback(SCDynamicStoreRef /* store */,
110 CFArrayRef changed_keys,
111 void* config_delegate) {
112 NetworkConfigWatcherMac::Delegate* net_config_delegate =
113 static_cast<NetworkConfigWatcherMac::Delegate*>(config_delegate);
114 net_config_delegate->OnNetworkConfigChange(changed_keys);
115 }
116 #endif // !defined(OS_IOS)
117
118 } // namespace
119
120 class NetworkConfigWatcherMacThread : public base::Thread {
121 public:
122 explicit NetworkConfigWatcherMacThread(
123 NetworkConfigWatcherMac::Delegate* delegate);
124 NetworkConfigWatcherMacThread(const NetworkConfigWatcherMacThread&) = delete;
125 NetworkConfigWatcherMacThread& operator=(
126 const NetworkConfigWatcherMacThread&) = delete;
127 ~NetworkConfigWatcherMacThread() override;
128
129 protected:
130 // base::Thread
131 void Init() override;
132 void CleanUp() override;
133
134 private:
135 // The SystemConfiguration calls in this function can lead to contention early
136 // on, so we invoke this function later on in startup to keep it fast.
137 void InitNotifications();
138
139 // Returns whether initializing notifications has succeeded.
140 bool InitNotificationsHelper();
141
142 base::ScopedCFTypeRef<CFRunLoopSourceRef> run_loop_source_;
143 NetworkConfigWatcherMac::Delegate* const delegate_;
144 #if !defined(OS_IOS)
145 int num_retry_;
146 #endif // !defined(OS_IOS)
147 base::WeakPtrFactory<NetworkConfigWatcherMacThread> weak_factory_;
148 };
149
NetworkConfigWatcherMacThread(NetworkConfigWatcherMac::Delegate * delegate)150 NetworkConfigWatcherMacThread::NetworkConfigWatcherMacThread(
151 NetworkConfigWatcherMac::Delegate* delegate)
152 : base::Thread("NetworkConfigWatcher"),
153 delegate_(delegate),
154 #if !defined(OS_IOS)
155 num_retry_(0),
156 #endif // !defined(OS_IOS)
157 weak_factory_(this) {
158 }
159
~NetworkConfigWatcherMacThread()160 NetworkConfigWatcherMacThread::~NetworkConfigWatcherMacThread() {
161 // This is expected to be invoked during shutdown.
162 base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_thread_join;
163 Stop();
164 }
165
Init()166 void NetworkConfigWatcherMacThread::Init() {
167 base::ThreadRestrictions::SetIOAllowed(true);
168 delegate_->Init();
169
170 // TODO(willchan): Look to see if there's a better signal for when it's ok to
171 // initialize this, rather than just delaying it by a fixed time.
172 const base::TimeDelta kInitializationDelay = base::TimeDelta::FromSeconds(1);
173 task_runner()->PostDelayedTask(
174 FROM_HERE,
175 base::BindOnce(&NetworkConfigWatcherMacThread::InitNotifications,
176 weak_factory_.GetWeakPtr()),
177 kInitializationDelay);
178 }
179
CleanUp()180 void NetworkConfigWatcherMacThread::CleanUp() {
181 if (!run_loop_source_.get())
182 return;
183
184 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), run_loop_source_.get(),
185 kCFRunLoopCommonModes);
186 run_loop_source_.reset();
187 }
188
InitNotifications()189 void NetworkConfigWatcherMacThread::InitNotifications() {
190 // If initialization fails, retry after a 1s delay.
191 bool success = InitNotificationsHelper();
192
193 #if !defined(OS_IOS)
194 if (!success && num_retry_ < kMaxRetry) {
195 LOG(ERROR) << "Retrying SystemConfiguration registration in 1 second.";
196 task_runner()->PostDelayedTask(
197 FROM_HERE,
198 base::BindOnce(&NetworkConfigWatcherMacThread::InitNotifications,
199 weak_factory_.GetWeakPtr()),
200 kRetryInterval);
201 num_retry_++;
202 return;
203 }
204
205 // There are kMaxRetry + 2 buckets. The 0 bucket is where no retry is
206 // performed. The kMaxRetry + 1 bucket is where all retries have failed.
207 int histogram_bucket = num_retry_;
208 if (!success) {
209 DCHECK_EQ(kMaxRetry, num_retry_);
210 histogram_bucket = kMaxRetry + 1;
211 }
212 UMA_HISTOGRAM_EXACT_LINEAR(
213 "Net.NetworkConfigWatcherMac.SCDynamicStore.NumRetry", histogram_bucket,
214 kMaxRetry + 2);
215 #else
216 DCHECK(success);
217 #endif // !defined(OS_IOS)
218 }
219
InitNotificationsHelper()220 bool NetworkConfigWatcherMacThread::InitNotificationsHelper() {
221 #if !defined(OS_IOS)
222 // SCDynamicStore API does not exist on iOS.
223 // Add a run loop source for a dynamic store to the current run loop.
224 SCDynamicStoreContext context = {
225 0, // Version 0.
226 delegate_, // User data.
227 NULL, // This is not reference counted. No retain function.
228 NULL, // This is not reference counted. No release function.
229 NULL, // No description for this.
230 };
231 base::ScopedCFTypeRef<SCDynamicStoreRef> store(SCDynamicStoreCreate(
232 NULL, CFSTR("org.chromium"), DynamicStoreCallback, &context));
233 if (!store) {
234 int error = SCError();
235 LOG(ERROR) << "SCDynamicStoreCreate failed with Error: " << error << " - "
236 << SCErrorString(error);
237 UMA_HISTOGRAM_ENUMERATION(
238 "Net.NetworkConfigWatcherMac.SCDynamicStore.Create",
239 ConvertToSCStatusCode(error), SCStatusCode::SC_COUNT);
240 return false;
241 }
242 run_loop_source_.reset(SCDynamicStoreCreateRunLoopSource(
243 NULL, store.get(), 0));
244 if (!run_loop_source_) {
245 int error = SCError();
246 LOG(ERROR) << "SCDynamicStoreCreateRunLoopSource failed with Error: "
247 << error << " - " << SCErrorString(error);
248 UMA_HISTOGRAM_ENUMERATION(
249 "Net.NetworkConfigWatcherMac.SCDynamicStore.Create.RunLoopSource",
250 ConvertToSCStatusCode(error), SCStatusCode::SC_COUNT);
251 return false;
252 }
253 CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source_.get(),
254 kCFRunLoopCommonModes);
255 #endif // !defined(OS_IOS)
256
257 // Set up notifications for interface and IP address changes.
258 delegate_->StartReachabilityNotifications();
259 #if !defined(OS_IOS)
260 delegate_->SetDynamicStoreNotificationKeys(store.get());
261 #endif // !defined(OS_IOS)
262 return true;
263 }
264
NetworkConfigWatcherMac(Delegate * delegate)265 NetworkConfigWatcherMac::NetworkConfigWatcherMac(Delegate* delegate)
266 : notifier_thread_(new NetworkConfigWatcherMacThread(delegate)) {
267 // We create this notifier thread because the notification implementation
268 // needs a thread with a CFRunLoop, and there's no guarantee that
269 // CurrentThread::Get() meets that criterion.
270 base::Thread::Options thread_options(base::MessagePumpType::UI, 0);
271 notifier_thread_->StartWithOptions(thread_options);
272 }
273
~NetworkConfigWatcherMac()274 NetworkConfigWatcherMac::~NetworkConfigWatcherMac() {}
275
276 } // namespace net
277