| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/base/network_config_watcher_mac.h" |
| |
| #include <algorithm> |
| |
| #include "base/bind.h" |
| #include "base/compiler_specific.h" |
| #include "base/macros.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/threading/thread.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "build/build_config.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| // SCDynamicStore API does not exist on iOS. |
| #if !defined(OS_IOS) |
| const base::TimeDelta kRetryInterval = base::TimeDelta::FromSeconds(1); |
| const int kMaxRetry = 5; |
| |
| // Maps SCError to an enum for UMA logging. These values are persisted to logs, |
| // and should not be renumbered. Added to investigate https://crbug.com/547877. |
| enum class SCStatusCode { |
| // Unmapped error codes. |
| SC_UNKNOWN = 0, |
| |
| // These map to the corresponding SCError. |
| SC_OK = 1, |
| SC_FAILED = 2, |
| SC_INVALID_ARGUMENT = 3, |
| SC_ACCESS_ERROR = 4, |
| SC_NO_KEY = 5, |
| SC_KEY_EXISTS = 6, |
| SC_LOCKED = 7, |
| SC_NEED_LOCK = 8, |
| SC_NO_STORE_SESSION = 9, |
| SC_NO_STORE_SERVER = 10, |
| SC_NOTIFIER_ACTIVE = 11, |
| SC_NO_PREFS_SESSION = 12, |
| SC_PREFS_BUSY = 13, |
| SC_NO_CONFIG_FILE = 14, |
| SC_NO_LINK = 15, |
| SC_STALE = 16, |
| SC_MAX_LINK = 17, |
| SC_REACHABILITY_UNKNOWN = 18, |
| SC_CONNECTION_NO_SERVICE = 19, |
| SC_CONNECTION_IGNORE = 20, |
| |
| // Maximum value for histogram bucket. |
| SC_COUNT, |
| }; |
| |
| SCStatusCode ConvertToSCStatusCode(int sc_error) { |
| switch (sc_error) { |
| case kSCStatusOK: |
| return SCStatusCode::SC_OK; |
| case kSCStatusFailed: |
| return SCStatusCode::SC_FAILED; |
| case kSCStatusInvalidArgument: |
| return SCStatusCode::SC_INVALID_ARGUMENT; |
| case kSCStatusAccessError: |
| return SCStatusCode::SC_ACCESS_ERROR; |
| case kSCStatusNoKey: |
| return SCStatusCode::SC_NO_KEY; |
| case kSCStatusKeyExists: |
| return SCStatusCode::SC_KEY_EXISTS; |
| case kSCStatusLocked: |
| return SCStatusCode::SC_LOCKED; |
| case kSCStatusNeedLock: |
| return SCStatusCode::SC_NEED_LOCK; |
| case kSCStatusNoStoreSession: |
| return SCStatusCode::SC_NO_STORE_SESSION; |
| case kSCStatusNoStoreServer: |
| return SCStatusCode::SC_NO_STORE_SERVER; |
| case kSCStatusNotifierActive: |
| return SCStatusCode::SC_NOTIFIER_ACTIVE; |
| case kSCStatusNoPrefsSession: |
| return SCStatusCode::SC_NO_PREFS_SESSION; |
| case kSCStatusPrefsBusy: |
| return SCStatusCode::SC_PREFS_BUSY; |
| case kSCStatusNoConfigFile: |
| return SCStatusCode::SC_NO_CONFIG_FILE; |
| case kSCStatusNoLink: |
| return SCStatusCode::SC_NO_LINK; |
| case kSCStatusStale: |
| return SCStatusCode::SC_STALE; |
| case kSCStatusMaxLink: |
| return SCStatusCode::SC_MAX_LINK; |
| case kSCStatusReachabilityUnknown: |
| return SCStatusCode::SC_REACHABILITY_UNKNOWN; |
| case kSCStatusConnectionNoService: |
| return SCStatusCode::SC_CONNECTION_NO_SERVICE; |
| case kSCStatusConnectionIgnore: |
| return SCStatusCode::SC_CONNECTION_IGNORE; |
| default: |
| return SCStatusCode::SC_UNKNOWN; |
| } |
| } |
| |
| // Called back by OS. Calls OnNetworkConfigChange(). |
| void DynamicStoreCallback(SCDynamicStoreRef /* store */, |
| CFArrayRef changed_keys, |
| void* config_delegate) { |
| NetworkConfigWatcherMac::Delegate* net_config_delegate = |
| static_cast<NetworkConfigWatcherMac::Delegate*>(config_delegate); |
| net_config_delegate->OnNetworkConfigChange(changed_keys); |
| } |
| #endif // !defined(OS_IOS) |
| |
| class NetworkConfigWatcherMacThread : public base::Thread { |
| public: |
| NetworkConfigWatcherMacThread(NetworkConfigWatcherMac::Delegate* delegate); |
| ~NetworkConfigWatcherMacThread() override; |
| |
| protected: |
| // base::Thread |
| void Init() override; |
| void CleanUp() override; |
| |
| private: |
| // The SystemConfiguration calls in this function can lead to contention early |
| // on, so we invoke this function later on in startup to keep it fast. |
| void InitNotifications(); |
| |
| // Returns whether initializing notifications has succeeded. |
| bool InitNotificationsHelper(); |
| |
| base::ScopedCFTypeRef<CFRunLoopSourceRef> run_loop_source_; |
| NetworkConfigWatcherMac::Delegate* const delegate_; |
| #if !defined(OS_IOS) |
| int num_retry_; |
| #endif // !defined(OS_IOS) |
| base::WeakPtrFactory<NetworkConfigWatcherMacThread> weak_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(NetworkConfigWatcherMacThread); |
| }; |
| |
| NetworkConfigWatcherMacThread::NetworkConfigWatcherMacThread( |
| NetworkConfigWatcherMac::Delegate* delegate) |
| : base::Thread("NetworkConfigWatcher"), |
| delegate_(delegate), |
| #if !defined(OS_IOS) |
| num_retry_(0), |
| #endif // !defined(OS_IOS) |
| weak_factory_(this) { |
| } |
| |
| NetworkConfigWatcherMacThread::~NetworkConfigWatcherMacThread() { |
| // Allow IO because Stop() calls PlatformThread::Join(), which is a blocking |
| // operation. This is expected during shutdown. |
| base::ThreadRestrictions::ScopedAllowIO allow_io; |
| |
| Stop(); |
| } |
| |
| void NetworkConfigWatcherMacThread::Init() { |
| base::ThreadRestrictions::SetIOAllowed(true); |
| delegate_->Init(); |
| |
| // TODO(willchan): Look to see if there's a better signal for when it's ok to |
| // initialize this, rather than just delaying it by a fixed time. |
| const base::TimeDelta kInitializationDelay = base::TimeDelta::FromSeconds(1); |
| task_runner()->PostDelayedTask( |
| FROM_HERE, base::Bind(&NetworkConfigWatcherMacThread::InitNotifications, |
| weak_factory_.GetWeakPtr()), |
| kInitializationDelay); |
| } |
| |
| void NetworkConfigWatcherMacThread::CleanUp() { |
| if (!run_loop_source_.get()) |
| return; |
| |
| CFRunLoopRemoveSource(CFRunLoopGetCurrent(), run_loop_source_.get(), |
| kCFRunLoopCommonModes); |
| run_loop_source_.reset(); |
| } |
| |
| void NetworkConfigWatcherMacThread::InitNotifications() { |
| // If initialization fails, retry after a 1s delay. |
| bool success = InitNotificationsHelper(); |
| |
| #if !defined(OS_IOS) |
| if (!success && num_retry_ < kMaxRetry) { |
| LOG(ERROR) << "Retrying SystemConfiguration registration in 1 second."; |
| task_runner()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&NetworkConfigWatcherMacThread::InitNotifications, |
| weak_factory_.GetWeakPtr()), |
| kRetryInterval); |
| num_retry_++; |
| return; |
| } |
| |
| // There are kMaxRetry + 2 buckets. The 0 bucket is where no retry is |
| // performed. The kMaxRetry + 1 bucket is where all retries have failed. |
| int histogram_bucket = num_retry_; |
| if (!success) { |
| DCHECK_EQ(kMaxRetry, num_retry_); |
| histogram_bucket = kMaxRetry + 1; |
| } |
| UMA_HISTOGRAM_EXACT_LINEAR( |
| "Net.NetworkConfigWatcherMac.SCDynamicStore.NumRetry", histogram_bucket, |
| kMaxRetry + 2); |
| #else |
| DCHECK(success); |
| #endif // !defined(OS_IOS) |
| } |
| |
| bool NetworkConfigWatcherMacThread::InitNotificationsHelper() { |
| #if !defined(OS_IOS) |
| // SCDynamicStore API does not exist on iOS. |
| // Add a run loop source for a dynamic store to the current run loop. |
| SCDynamicStoreContext context = { |
| 0, // Version 0. |
| delegate_, // User data. |
| NULL, // This is not reference counted. No retain function. |
| NULL, // This is not reference counted. No release function. |
| NULL, // No description for this. |
| }; |
| base::ScopedCFTypeRef<SCDynamicStoreRef> store(SCDynamicStoreCreate( |
| NULL, CFSTR("org.chromium"), DynamicStoreCallback, &context)); |
| if (!store) { |
| int error = SCError(); |
| LOG(ERROR) << "SCDynamicStoreCreate failed with Error: " << error << " - " |
| << SCErrorString(error); |
| UMA_HISTOGRAM_ENUMERATION( |
| "Net.NetworkConfigWatcherMac.SCDynamicStore.Create", |
| ConvertToSCStatusCode(error), SCStatusCode::SC_COUNT); |
| return false; |
| } |
| run_loop_source_.reset(SCDynamicStoreCreateRunLoopSource( |
| NULL, store.get(), 0)); |
| if (!run_loop_source_) { |
| int error = SCError(); |
| LOG(ERROR) << "SCDynamicStoreCreateRunLoopSource failed with Error: " |
| << error << " - " << SCErrorString(error); |
| UMA_HISTOGRAM_ENUMERATION( |
| "Net.NetworkConfigWatcherMac.SCDynamicStore.Create.RunLoopSource", |
| ConvertToSCStatusCode(error), SCStatusCode::SC_COUNT); |
| return false; |
| } |
| CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source_.get(), |
| kCFRunLoopCommonModes); |
| #endif // !defined(OS_IOS) |
| |
| // Set up notifications for interface and IP address changes. |
| delegate_->StartReachabilityNotifications(); |
| #if !defined(OS_IOS) |
| delegate_->SetDynamicStoreNotificationKeys(store.get()); |
| #endif // !defined(OS_IOS) |
| return true; |
| } |
| |
| } // namespace |
| |
| NetworkConfigWatcherMac::NetworkConfigWatcherMac(Delegate* delegate) |
| : notifier_thread_(new NetworkConfigWatcherMacThread(delegate)) { |
| // We create this notifier thread because the notification implementation |
| // needs a thread with a CFRunLoop, and there's no guarantee that |
| // MessageLoopCurrent::Get() meets that criterion. |
| base::Thread::Options thread_options(base::MessageLoop::TYPE_UI, 0); |
| notifier_thread_->StartWithOptions(thread_options); |
| } |
| |
| NetworkConfigWatcherMac::~NetworkConfigWatcherMac() {} |
| |
| } // namespace net |