1 // Copyright (c) 2009 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.
5 // There are three classes involved here. There's NetworkChangeNotifierMac,
6 // which is the Mac specific implementation of NetworkChangeNotifier. It is the
7 // class with which clients can register themselves as network change
8 // observers. There's NetworkChangeNotifierThread, which is a base::Thread
9 // subclass of MessageLoop::TYPE_UI (since it needs a CFRunLoop) that contains
10 // the NetworkChangeNotifierImpl. NetworkChangeNotifierImpl is the object
11 // that receives the actual OS X notifications and posts them to the
12 // NetworkChangeNotifierMac's message loop, so that NetworkChangeNotifierMac
13 // can notify all its observers.
15 // When NetworkChangeNotifierMac is being deleted, it will delete the
16 // NetworkChangeNotifierThread, which will Stop() it and also delete the
17 // NetworkChangeNotifierImpl. Therefore, NetworkChangeNotifierImpl and
18 // NetworkChangeNotifierThread's lifetimes generally begin after and end before
19 // NetworkChangeNotifierMac. There is an edge case where a notification task
20 // gets posted to the IO thread, thereby maintaining a reference to
21 // NetworkChangeNotifierImpl beyond the lifetime of NetworkChangeNotifierThread.
22 // In this case, the notification is cancelled, and NetworkChangeNotifierImpl
23 // will be deleted once all notification tasks that reference it have been run.
25 #include "net/base/network_change_notifier_mac.h"
26 #include <SystemConfiguration/SCDynamicStore.h>
27 #include <SystemConfiguration/SCDynamicStoreKey.h>
28 #include <SystemConfiguration/SCSchemaDefinitions.h>
30 #include "base/logging.h"
31 #include "base/message_loop.h"
32 #include "base/scoped_cftyperef.h"
33 #include "base/thread.h"
39 // NetworkChangeNotifierImpl should be created on a thread with a CFRunLoop,
40 // since it requires one to pump notifications. However, it also runs some
41 // methods on |notifier_loop_|, because it cannot post calls to |notifier_|
42 // since NetworkChangeNotifier is not ref counted in a thread safe manner.
43 class NetworkChangeNotifierImpl
44 : public base::RefCountedThreadSafe
<NetworkChangeNotifierImpl
> {
46 NetworkChangeNotifierImpl(MessageLoop
* notifier_loop
,
47 NetworkChangeNotifierMac
* notifier
);
52 friend class base::RefCountedThreadSafe
<NetworkChangeNotifierImpl
>;
53 ~NetworkChangeNotifierImpl();
55 static void DynamicStoreCallback(SCDynamicStoreRef
/* store */,
56 CFArrayRef changed_keys
,
59 void OnNetworkConfigChange(CFArrayRef changed_keys
);
61 // Runs on |notifier_loop_|.
62 void OnIPAddressChanged();
64 // Raw pointers. Note that |notifier_| _must_ outlive the
65 // NetworkChangeNotifierImpl. For lifecycle management details, read the
66 // comment at the top of the file.
67 MessageLoop
* const notifier_loop_
;
68 NetworkChangeNotifierMac
* notifier_
;
70 scoped_cftyperef
<CFRunLoopSourceRef
> source_
;
72 DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierImpl
);
75 NetworkChangeNotifierImpl::NetworkChangeNotifierImpl(
76 MessageLoop
* notifier_loop
, NetworkChangeNotifierMac
* notifier
)
77 : notifier_loop_(notifier_loop
),
79 DCHECK_EQ(MessageLoop::TYPE_UI
, MessageLoop::current()->type());
80 SCDynamicStoreContext context
= {
83 NULL
, // This is not reference counted. No retain function.
84 NULL
, // This is not reference counted. No release function.
85 NULL
, // No description for this.
88 // Get a reference to the dynamic store.
89 scoped_cftyperef
<SCDynamicStoreRef
> store(
90 SCDynamicStoreCreate(NULL
/* use default allocator */,
91 CFSTR("org.chromium"),
92 DynamicStoreCallback
, &context
));
94 // Create a run loop source for the dynamic store.
95 source_
.reset(SCDynamicStoreCreateRunLoopSource(
96 NULL
/* use default allocator */,
98 0 /* 0 sounds like a fine source order to me! */));
100 // Add the run loop source to the current run loop.
101 CFRunLoopAddSource(CFRunLoopGetCurrent(),
103 kCFRunLoopCommonModes
);
105 // Set up the notification keys.
106 scoped_cftyperef
<CFMutableArrayRef
> notification_keys(
107 CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
));
109 // Monitor interface changes.
110 scoped_cftyperef
<CFStringRef
> key(
111 SCDynamicStoreKeyCreateNetworkGlobalEntity(
112 NULL
/* default allocator */, kSCDynamicStoreDomainState
,
113 kSCEntNetInterface
));
114 CFArrayAppendValue(notification_keys
.get(), key
.get());
116 // Monitor IP address changes.
118 key
.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
119 NULL
/* default allocator */, kSCDynamicStoreDomainState
,
121 CFArrayAppendValue(notification_keys
.get(), key
.get());
123 key
.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
124 NULL
/* default allocator */, kSCDynamicStoreDomainState
,
126 CFArrayAppendValue(notification_keys
.get(), key
.get());
128 // Ok, let's ask for notifications!
129 // TODO(willchan): Figure out a proper way to handle this rather than crash.
130 CHECK(SCDynamicStoreSetNotificationKeys(
131 store
.get(), notification_keys
.get(), NULL
));
134 NetworkChangeNotifierImpl::~NetworkChangeNotifierImpl() {
135 CFRunLoopRemoveSource(CFRunLoopGetCurrent(),
137 kCFRunLoopCommonModes
);
140 void NetworkChangeNotifierImpl::Shutdown() {
146 void NetworkChangeNotifierImpl::DynamicStoreCallback(
147 SCDynamicStoreRef
/* store */,
148 CFArrayRef changed_keys
,
150 NetworkChangeNotifierImpl
* net_config
=
151 static_cast<NetworkChangeNotifierImpl
*>(config
);
152 net_config
->OnNetworkConfigChange(changed_keys
);
155 void NetworkChangeNotifierImpl::OnNetworkConfigChange(CFArrayRef changed_keys
) {
156 for (CFIndex i
= 0; i
< CFArrayGetCount(changed_keys
); ++i
) {
157 CFStringRef key
= static_cast<CFStringRef
>(
158 CFArrayGetValueAtIndex(changed_keys
, i
));
159 if (CFStringHasSuffix(key
, kSCEntNetIPv4
) ||
160 CFStringHasSuffix(key
, kSCEntNetIPv6
)) {
161 notifier_loop_
->PostTask(
165 &NetworkChangeNotifierImpl::OnIPAddressChanged
));
166 } else if (CFStringHasSuffix(key
, kSCEntNetInterface
)) {
167 // TODO(willchan): Does not appear to be working. Look into this.
168 // Perhaps this isn't needed anyway.
175 void NetworkChangeNotifierImpl::OnIPAddressChanged() {
176 // If |notifier_| doesn't exist, then that means we're shutting down, so
177 // notifications are all cancelled.
179 notifier_
->OnIPAddressChanged();
182 class NetworkChangeNotifierThread
: public base::Thread
{
184 NetworkChangeNotifierThread(MessageLoop
* notifier_loop
,
185 NetworkChangeNotifierMac
* notifier
);
186 ~NetworkChangeNotifierThread();
192 MessageLoop
* const notifier_loop_
;
193 NetworkChangeNotifierMac
* const notifier_
;
194 scoped_refptr
<NetworkChangeNotifierImpl
> notifier_impl_
;
196 DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierThread
);
199 NetworkChangeNotifierThread::NetworkChangeNotifierThread(
200 MessageLoop
* notifier_loop
, NetworkChangeNotifierMac
* notifier
)
201 : base::Thread("NetworkChangeNotifier"),
202 notifier_loop_(notifier_loop
),
203 notifier_(notifier
) {}
205 NetworkChangeNotifierThread::~NetworkChangeNotifierThread() {
206 notifier_impl_
->Shutdown();
210 // Note that |notifier_impl_| is initialized on the network change
211 // notifier thread, not whatever thread constructs the
212 // NetworkChangeNotifierThread object. This is important, because this thread
213 // is the one that has a CFRunLoop.
214 void NetworkChangeNotifierThread::Init() {
216 new NetworkChangeNotifierImpl(notifier_loop_
, notifier_
);
221 NetworkChangeNotifierMac::NetworkChangeNotifierMac()
222 : notifier_thread_(NULL
),
223 method_factory_(this) {
224 // TODO(willchan): Look to see if there's a better signal for when it's ok to
225 // initialize this, rather than just delaying it by a fixed time.
226 const int kNotifierThreadInitializationDelayMS
= 1000;
227 MessageLoop
* loop
= MessageLoop::current();
228 loop
->PostDelayedTask(
230 method_factory_
.NewRunnableMethod(
231 &NetworkChangeNotifierMac::InitializeNotifierThread
, loop
),
232 kNotifierThreadInitializationDelayMS
);
235 NetworkChangeNotifierMac::~NetworkChangeNotifierMac() {}
237 void NetworkChangeNotifierMac::InitializeNotifierThread(MessageLoop
* loop
) {
238 notifier_thread_
.reset(new NetworkChangeNotifierThread(loop
, this));
239 base::Thread::Options thread_options
;
240 thread_options
.message_loop_type
= MessageLoop::TYPE_UI
;
241 notifier_thread_
->StartWithOptions(thread_options
);