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