1 // Copyright (c) 2012 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 #include "net/base/network_change_notifier_mac.h"
7 #include <netinet/in.h>
10 #include "base/basictypes.h"
11 #include "base/threading/thread.h"
12 #include "net/dns/dns_config_service.h"
16 static bool CalculateReachability(SCNetworkConnectionFlags flags
) {
17 bool reachable
= flags
& kSCNetworkFlagsReachable
;
18 bool connection_required
= flags
& kSCNetworkFlagsConnectionRequired
;
19 return reachable
&& !connection_required
;
22 NetworkChangeNotifier::ConnectionType
CalculateConnectionType(
23 SCNetworkConnectionFlags flags
) {
24 bool reachable
= CalculateReachability(flags
);
27 return (flags
& kSCNetworkReachabilityFlagsIsWWAN
) ?
28 NetworkChangeNotifier::CONNECTION_3G
:
29 NetworkChangeNotifier::CONNECTION_WIFI
;
31 // TODO(droger): Get something more detailed than CONNECTION_UNKNOWN.
32 // http://crbug.com/112937
33 return NetworkChangeNotifier::CONNECTION_UNKNOWN
;
34 #endif // defined(OS_IOS)
36 return NetworkChangeNotifier::CONNECTION_NONE
;
40 // Thread on which we can run DnsConfigService, which requires a TYPE_IO
42 class NetworkChangeNotifierMac::DnsConfigServiceThread
: public base::Thread
{
44 DnsConfigServiceThread() : base::Thread("DnsConfigService") {}
46 virtual ~DnsConfigServiceThread() {
50 virtual void Init() override
{
51 service_
= DnsConfigService::CreateSystemService();
52 service_
->WatchConfig(base::Bind(&NetworkChangeNotifier::SetDnsConfig
));
55 virtual void CleanUp() override
{
60 scoped_ptr
<DnsConfigService
> service_
;
62 DISALLOW_COPY_AND_ASSIGN(DnsConfigServiceThread
);
65 NetworkChangeNotifierMac::NetworkChangeNotifierMac()
66 : NetworkChangeNotifier(NetworkChangeCalculatorParamsMac()),
67 connection_type_(CONNECTION_UNKNOWN
),
68 connection_type_initialized_(false),
69 initial_connection_type_cv_(&connection_type_lock_
),
71 dns_config_service_thread_(new DnsConfigServiceThread()) {
72 // Must be initialized after the rest of this object, as it may call back into
73 // SetInitialConnectionType().
74 config_watcher_
.reset(new NetworkConfigWatcherMac(&forwarder_
));
75 dns_config_service_thread_
->StartWithOptions(
76 base::Thread::Options(base::MessageLoop::TYPE_IO
, 0));
79 NetworkChangeNotifierMac::~NetworkChangeNotifierMac() {
80 // Delete the ConfigWatcher to join the notifier thread, ensuring that
81 // StartReachabilityNotifications() has an opportunity to run to completion.
82 config_watcher_
.reset();
84 // Now that StartReachabilityNotifications() has either run to completion or
85 // never run at all, unschedule reachability_ if it was previously scheduled.
86 if (reachability_
.get() && run_loop_
.get()) {
87 SCNetworkReachabilityUnscheduleFromRunLoop(reachability_
.get(),
89 kCFRunLoopCommonModes
);
94 NetworkChangeNotifier::NetworkChangeCalculatorParams
95 NetworkChangeNotifierMac::NetworkChangeCalculatorParamsMac() {
96 NetworkChangeCalculatorParams params
;
97 // Delay values arrived at by simple experimentation and adjusted so as to
98 // produce a single signal when switching between network connections.
99 params
.ip_address_offline_delay_
= base::TimeDelta::FromMilliseconds(500);
100 params
.ip_address_online_delay_
= base::TimeDelta::FromMilliseconds(500);
101 params
.connection_type_offline_delay_
=
102 base::TimeDelta::FromMilliseconds(1000);
103 params
.connection_type_online_delay_
= base::TimeDelta::FromMilliseconds(500);
107 NetworkChangeNotifier::ConnectionType
108 NetworkChangeNotifierMac::GetCurrentConnectionType() const {
109 base::AutoLock
lock(connection_type_lock_
);
110 // Make sure the initial connection type is set before returning.
111 while (!connection_type_initialized_
) {
112 initial_connection_type_cv_
.Wait();
114 return connection_type_
;
117 void NetworkChangeNotifierMac::Forwarder::Init() {
118 net_config_watcher_
->SetInitialConnectionType();
121 void NetworkChangeNotifierMac::Forwarder::StartReachabilityNotifications() {
122 net_config_watcher_
->StartReachabilityNotifications();
125 void NetworkChangeNotifierMac::Forwarder::SetDynamicStoreNotificationKeys(
126 SCDynamicStoreRef store
) {
127 net_config_watcher_
->SetDynamicStoreNotificationKeys(store
);
130 void NetworkChangeNotifierMac::Forwarder::OnNetworkConfigChange(
131 CFArrayRef changed_keys
) {
132 net_config_watcher_
->OnNetworkConfigChange(changed_keys
);
135 void NetworkChangeNotifierMac::SetInitialConnectionType() {
136 // Called on notifier thread.
138 // Try to reach 0.0.0.0. This is the approach taken by Firefox:
140 // http://mxr.mozilla.org/mozilla2.0/source/netwerk/system/mac/nsNetworkLinkService.mm
142 // From my (adamk) testing on Snow Leopard, 0.0.0.0
143 // seems to be reachable if any network connection is available.
144 struct sockaddr_in addr
= {0};
145 addr
.sin_len
= sizeof(addr
);
146 addr
.sin_family
= AF_INET
;
147 reachability_
.reset(SCNetworkReachabilityCreateWithAddress(
148 kCFAllocatorDefault
, reinterpret_cast<struct sockaddr
*>(&addr
)));
150 SCNetworkConnectionFlags flags
;
151 ConnectionType connection_type
= CONNECTION_UNKNOWN
;
152 if (SCNetworkReachabilityGetFlags(reachability_
, &flags
)) {
153 connection_type
= CalculateConnectionType(flags
);
155 LOG(ERROR
) << "Could not get initial network connection type,"
156 << "assuming online.";
159 base::AutoLock
lock(connection_type_lock_
);
160 connection_type_
= connection_type
;
161 connection_type_initialized_
= true;
162 initial_connection_type_cv_
.Signal();
166 void NetworkChangeNotifierMac::StartReachabilityNotifications() {
167 // Called on notifier thread.
168 run_loop_
.reset(CFRunLoopGetCurrent());
169 CFRetain(run_loop_
.get());
171 DCHECK(reachability_
);
172 SCNetworkReachabilityContext reachability_context
= {
179 if (!SCNetworkReachabilitySetCallback(
181 &NetworkChangeNotifierMac::ReachabilityCallback
,
182 &reachability_context
)) {
183 LOG(DFATAL
) << "Could not set network reachability callback";
184 reachability_
.reset();
185 } else if (!SCNetworkReachabilityScheduleWithRunLoop(reachability_
,
187 kCFRunLoopCommonModes
)) {
188 LOG(DFATAL
) << "Could not schedule network reachability on run loop";
189 reachability_
.reset();
193 void NetworkChangeNotifierMac::SetDynamicStoreNotificationKeys(
194 SCDynamicStoreRef store
) {
196 // SCDynamicStore API does not exist on iOS.
199 base::ScopedCFTypeRef
<CFMutableArrayRef
> notification_keys(
200 CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
));
201 base::ScopedCFTypeRef
<CFStringRef
> key(
202 SCDynamicStoreKeyCreateNetworkGlobalEntity(
203 NULL
, kSCDynamicStoreDomainState
, kSCEntNetInterface
));
204 CFArrayAppendValue(notification_keys
.get(), key
.get());
205 key
.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
206 NULL
, kSCDynamicStoreDomainState
, kSCEntNetIPv4
));
207 CFArrayAppendValue(notification_keys
.get(), key
.get());
208 key
.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
209 NULL
, kSCDynamicStoreDomainState
, kSCEntNetIPv6
));
210 CFArrayAppendValue(notification_keys
.get(), key
.get());
212 // Set the notification keys. This starts us receiving notifications.
213 bool ret
= SCDynamicStoreSetNotificationKeys(
214 store
, notification_keys
.get(), NULL
);
215 // TODO(willchan): Figure out a proper way to handle this rather than crash.
217 #endif // defined(OS_IOS)
220 void NetworkChangeNotifierMac::OnNetworkConfigChange(CFArrayRef changed_keys
) {
222 // SCDynamicStore API does not exist on iOS.
225 DCHECK_EQ(run_loop_
.get(), CFRunLoopGetCurrent());
227 for (CFIndex i
= 0; i
< CFArrayGetCount(changed_keys
); ++i
) {
228 CFStringRef key
= static_cast<CFStringRef
>(
229 CFArrayGetValueAtIndex(changed_keys
, i
));
230 if (CFStringHasSuffix(key
, kSCEntNetIPv4
) ||
231 CFStringHasSuffix(key
, kSCEntNetIPv6
)) {
232 NotifyObserversOfIPAddressChange();
235 if (CFStringHasSuffix(key
, kSCEntNetInterface
)) {
236 // TODO(willchan): Does not appear to be working. Look into this.
237 // Perhaps this isn't needed anyway.
242 #endif // defined(OS_IOS)
246 void NetworkChangeNotifierMac::ReachabilityCallback(
247 SCNetworkReachabilityRef target
,
248 SCNetworkConnectionFlags flags
,
250 NetworkChangeNotifierMac
* notifier_mac
=
251 static_cast<NetworkChangeNotifierMac
*>(notifier
);
253 DCHECK_EQ(notifier_mac
->run_loop_
.get(), CFRunLoopGetCurrent());
255 ConnectionType new_type
= CalculateConnectionType(flags
);
256 ConnectionType old_type
;
258 base::AutoLock
lock(notifier_mac
->connection_type_lock_
);
259 old_type
= notifier_mac
->connection_type_
;
260 notifier_mac
->connection_type_
= new_type
;
262 if (old_type
!= new_type
)
263 NotifyObserversOfConnectionTypeChange();
266 // On iOS, the SCDynamicStore API does not exist, and we use the reachability
267 // API to detect IP address changes instead.
268 NotifyObserversOfIPAddressChange();
269 #endif // defined(OS_IOS)