// Copyright 2012 The Chromium Authors // 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 #include "base/compiler_specific.h" #include "base/functional/bind.h" #include "base/logging.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/message_loop/message_pump_type.h" #include "base/metrics/histogram_macros.h" #include "base/task/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 !BUILDFLAG(IS_IOS) const base::TimeDelta kRetryInterval = base::Seconds(1); const int kMaxRetry = 5; // Called back by OS. Calls OnNetworkConfigChange(). void DynamicStoreCallback(SCDynamicStoreRef /* store */, CFArrayRef changed_keys, void* config_delegate) { NetworkConfigWatcherMac::Delegate* net_config_delegate = static_cast(config_delegate); net_config_delegate->OnNetworkConfigChange(changed_keys); } #endif // !BUILDFLAG(IS_IOS) } // namespace class NetworkConfigWatcherMacThread : public base::Thread { public: explicit NetworkConfigWatcherMacThread( NetworkConfigWatcherMac::Delegate* delegate); NetworkConfigWatcherMacThread(const NetworkConfigWatcherMacThread&) = delete; NetworkConfigWatcherMacThread& operator=( const NetworkConfigWatcherMacThread&) = delete; ~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::apple::ScopedCFTypeRef run_loop_source_; const raw_ptr delegate_; #if !BUILDFLAG(IS_IOS) int num_retry_ = 0; #endif // !BUILDFLAG(IS_IOS) base::WeakPtrFactory weak_factory_; }; NetworkConfigWatcherMacThread::NetworkConfigWatcherMacThread( NetworkConfigWatcherMac::Delegate* delegate) : base::Thread("NetworkConfigWatcher"), delegate_(delegate), weak_factory_(this) {} NetworkConfigWatcherMacThread::~NetworkConfigWatcherMacThread() { // This is expected to be invoked during shutdown. base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_thread_join; Stop(); } void NetworkConfigWatcherMacThread::Init() { 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::Seconds(1); task_runner()->PostDelayedTask( FROM_HERE, base::BindOnce(&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 !BUILDFLAG(IS_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; } #else DCHECK(success); #endif // !BUILDFLAG(IS_IOS) } bool NetworkConfigWatcherMacThread::InitNotificationsHelper() { #if !BUILDFLAG(IS_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. nullptr, // This is not reference counted. No retain function. nullptr, // This is not reference counted. No release function. nullptr, // No description for this. }; base::apple::ScopedCFTypeRef store(SCDynamicStoreCreate( nullptr, CFSTR("org.chromium"), DynamicStoreCallback, &context)); if (!store) { int error = SCError(); LOG(ERROR) << "SCDynamicStoreCreate failed with Error: " << error << " - " << SCErrorString(error); return false; } run_loop_source_.reset( SCDynamicStoreCreateRunLoopSource(nullptr, store.get(), 0)); if (!run_loop_source_) { int error = SCError(); LOG(ERROR) << "SCDynamicStoreCreateRunLoopSource failed with Error: " << error << " - " << SCErrorString(error); return false; } CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source_.get(), kCFRunLoopCommonModes); #endif // !BUILDFLAG(IS_IOS) // Set up notifications for interface and IP address changes. delegate_->StartReachabilityNotifications(); #if !BUILDFLAG(IS_IOS) delegate_->SetDynamicStoreNotificationKeys(store.get()); #endif // !BUILDFLAG(IS_IOS) return true; } NetworkConfigWatcherMac::NetworkConfigWatcherMac(Delegate* delegate) : notifier_thread_( std::make_unique(delegate)) { // We create this notifier thread because the notification implementation // needs a thread with a CFRunLoop, and there's no guarantee that // CurrentThread::Get() meets that criterion. base::Thread::Options thread_options(base::MessagePumpType::UI, 0); notifier_thread_->StartWithOptions(std::move(thread_options)); } NetworkConfigWatcherMac::~NetworkConfigWatcherMac() = default; } // namespace net