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