// Copyright (c) 2009 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. // There are three classes involved here. There's NetworkChangeNotifierMac, // which is the Mac specific implementation of NetworkChangeNotifier. It is the // class with which clients can register themselves as network change // observers. There's NetworkChangeNotifierThread, which is a base::Thread // subclass of MessageLoop::TYPE_UI (since it needs a CFRunLoop) that contains // the NetworkChangeNotifierImpl. NetworkChangeNotifierImpl is the object // that receives the actual OS X notifications and posts them to the // NetworkChangeNotifierMac's message loop, so that NetworkChangeNotifierMac // can notify all its observers. // // When NetworkChangeNotifierMac is being deleted, it will delete the // NetworkChangeNotifierThread, which will Stop() it and also delete the // NetworkChangeNotifierImpl. Therefore, NetworkChangeNotifierImpl and // NetworkChangeNotifierThread's lifetimes generally begin after and end before // NetworkChangeNotifierMac. There is an edge case where a notification task // gets posted to the IO thread, thereby maintaining a reference to // NetworkChangeNotifierImpl beyond the lifetime of NetworkChangeNotifierThread. // In this case, the notification is cancelled, and NetworkChangeNotifierImpl // will be deleted once all notification tasks that reference it have been run. #include "net/base/network_change_notifier_mac.h" #include <SystemConfiguration/SCDynamicStore.h> #include <SystemConfiguration/SCDynamicStoreKey.h> #include <SystemConfiguration/SCSchemaDefinitions.h> #include <algorithm> #include "base/logging.h" #include "base/scoped_cftyperef.h" #include "base/thread.h" namespace net { namespace { // NetworkChangeNotifierImpl should be created on a thread with a CFRunLoop, // since it requires one to pump notifications. However, it also runs some // methods on |notifier_loop_|, because it cannot post calls to |notifier_| // since NetworkChangeNotifier is not ref counted in a thread safe manner. class NetworkChangeNotifierImpl : public base::RefCountedThreadSafe<NetworkChangeNotifierImpl> { public: NetworkChangeNotifierImpl(MessageLoop* notifier_loop, NetworkChangeNotifierMac* notifier); void Shutdown(); private: friend class base::RefCountedThreadSafe<NetworkChangeNotifierImpl>; ~NetworkChangeNotifierImpl(); static void DynamicStoreCallback(SCDynamicStoreRef /* store */, CFArrayRef changed_keys, void* config); void OnNetworkConfigChange(CFArrayRef changed_keys); // Runs on |notifier_loop_|. void OnIPAddressChanged(); // Raw pointers. Note that |notifier_| _must_ outlive the // NetworkChangeNotifierImpl. For lifecycle management details, read the // comment at the top of the file. MessageLoop* const notifier_loop_; NetworkChangeNotifierMac* notifier_; scoped_cftyperef<CFRunLoopSourceRef> source_; DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierImpl); }; NetworkChangeNotifierImpl::NetworkChangeNotifierImpl( MessageLoop* notifier_loop, NetworkChangeNotifierMac* notifier) : notifier_loop_(notifier_loop), notifier_(notifier) { DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type()); SCDynamicStoreContext context = { 0, // Version 0. this, // 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. }; // Get a reference to the dynamic store. scoped_cftyperef<SCDynamicStoreRef> store( SCDynamicStoreCreate(NULL /* use default allocator */, CFSTR("org.chromium"), DynamicStoreCallback, &context)); // Create a run loop source for the dynamic store. source_.reset(SCDynamicStoreCreateRunLoopSource( NULL /* use default allocator */, store.get(), 0 /* 0 sounds like a fine source order to me! */)); // Add the run loop source to the current run loop. CFRunLoopAddSource(CFRunLoopGetCurrent(), source_.get(), kCFRunLoopCommonModes); // Set up the notification keys. scoped_cftyperef<CFMutableArrayRef> notification_keys( CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)); // Monitor interface changes. scoped_cftyperef<CFStringRef> key( SCDynamicStoreKeyCreateNetworkGlobalEntity( NULL /* default allocator */, kSCDynamicStoreDomainState, kSCEntNetInterface)); CFArrayAppendValue(notification_keys.get(), key.get()); // Monitor IP address changes. key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity( NULL /* default allocator */, kSCDynamicStoreDomainState, kSCEntNetIPv4)); CFArrayAppendValue(notification_keys.get(), key.get()); key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity( NULL /* default allocator */, kSCDynamicStoreDomainState, kSCEntNetIPv6)); CFArrayAppendValue(notification_keys.get(), key.get()); // Ok, let's ask for notifications! // TODO(willchan): Figure out a proper way to handle this rather than crash. CHECK(SCDynamicStoreSetNotificationKeys( store.get(), notification_keys.get(), NULL)); } NetworkChangeNotifierImpl::~NetworkChangeNotifierImpl() { CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source_.get(), kCFRunLoopCommonModes); } void NetworkChangeNotifierImpl::Shutdown() { CHECK(notifier_); notifier_ = NULL; } // static void NetworkChangeNotifierImpl::DynamicStoreCallback( SCDynamicStoreRef /* store */, CFArrayRef changed_keys, void* config) { NetworkChangeNotifierImpl* net_config = static_cast<NetworkChangeNotifierImpl*>(config); net_config->OnNetworkConfigChange(changed_keys); } void NetworkChangeNotifierImpl::OnNetworkConfigChange(CFArrayRef changed_keys) { for (CFIndex i = 0; i < CFArrayGetCount(changed_keys); ++i) { CFStringRef key = static_cast<CFStringRef>( CFArrayGetValueAtIndex(changed_keys, i)); if (CFStringHasSuffix(key, kSCEntNetIPv4) || CFStringHasSuffix(key, kSCEntNetIPv6)) { notifier_loop_->PostTask( FROM_HERE, NewRunnableMethod( this, &NetworkChangeNotifierImpl::OnIPAddressChanged)); } else if (CFStringHasSuffix(key, kSCEntNetInterface)) { // TODO(willchan): Does not appear to be working. Look into this. // Perhaps this isn't needed anyway. } else { NOTREACHED(); } } } void NetworkChangeNotifierImpl::OnIPAddressChanged() { // If |notifier_| doesn't exist, then that means we're shutting down, so // notifications are all cancelled. if (notifier_) notifier_->OnIPAddressChanged(); } class NetworkChangeNotifierThread : public base::Thread { public: NetworkChangeNotifierThread(MessageLoop* notifier_loop, NetworkChangeNotifierMac* notifier); ~NetworkChangeNotifierThread(); protected: virtual void Init(); private: MessageLoop* const notifier_loop_; NetworkChangeNotifierMac* const notifier_; scoped_refptr<NetworkChangeNotifierImpl> notifier_impl_; DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierThread); }; NetworkChangeNotifierThread::NetworkChangeNotifierThread( MessageLoop* notifier_loop, NetworkChangeNotifierMac* notifier) : base::Thread("NetworkChangeNotifier"), notifier_loop_(notifier_loop), notifier_(notifier) {} NetworkChangeNotifierThread::~NetworkChangeNotifierThread() { notifier_impl_->Shutdown(); Stop(); } // Note that |notifier_impl_| is initialized on the network change // notifier thread, not whatever thread constructs the // NetworkChangeNotifierThread object. This is important, because this thread // is the one that has a CFRunLoop. void NetworkChangeNotifierThread::Init() { notifier_impl_ = new NetworkChangeNotifierImpl(notifier_loop_, notifier_); } } // namespace NetworkChangeNotifierMac::NetworkChangeNotifierMac() : notifier_thread_( new NetworkChangeNotifierThread(MessageLoop::current(), this)) { base::Thread::Options thread_options; thread_options.message_loop_type = MessageLoop::TYPE_UI; notifier_thread_->StartWithOptions(thread_options); } NetworkChangeNotifierMac::~NetworkChangeNotifierMac() {} } // namespace net