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 "base/threading/thread_restrictions.h"
13 #include "net/dns/dns_config_service.h"
17 static bool CalculateReachability(SCNetworkConnectionFlags flags
) {
18 bool reachable
= flags
& kSCNetworkFlagsReachable
;
19 bool connection_required
= flags
& kSCNetworkFlagsConnectionRequired
;
20 return reachable
&& !connection_required
;
23 NetworkChangeNotifier::ConnectionType
CalculateConnectionType(
24 SCNetworkConnectionFlags flags
) {
25 bool reachable
= CalculateReachability(flags
);
28 return (flags
& kSCNetworkReachabilityFlagsIsWWAN
) ?
29 NetworkChangeNotifier::CONNECTION_3G
:
30 NetworkChangeNotifier::CONNECTION_WIFI
;
32 // TODO(droger): Get something more detailed than CONNECTION_UNKNOWN.
33 // http://crbug.com/112937
34 return NetworkChangeNotifier::CONNECTION_UNKNOWN
;
35 #endif // defined(OS_IOS)
37 return NetworkChangeNotifier::CONNECTION_NONE
;
41 // Thread on which we can run DnsConfigService, which requires a TYPE_IO
43 class NetworkChangeNotifierMac::DnsConfigServiceThread
: public base::Thread
{
45 DnsConfigServiceThread() : base::Thread("DnsConfigService") {}
47 ~DnsConfigServiceThread() override
{ Stop(); }
49 void Init() override
{
50 service_
= DnsConfigService::CreateSystemService();
51 service_
->WatchConfig(base::Bind(&NetworkChangeNotifier::SetDnsConfig
));
54 void CleanUp() override
{ service_
.reset(); }
57 scoped_ptr
<DnsConfigService
> service_
;
59 DISALLOW_COPY_AND_ASSIGN(DnsConfigServiceThread
);
62 NetworkChangeNotifierMac::NetworkChangeNotifierMac()
63 : NetworkChangeNotifier(NetworkChangeCalculatorParamsMac()),
64 connection_type_(CONNECTION_UNKNOWN
),
65 connection_type_initialized_(false),
66 initial_connection_type_cv_(&connection_type_lock_
),
68 dns_config_service_thread_(new DnsConfigServiceThread()) {
69 // Must be initialized after the rest of this object, as it may call back into
70 // SetInitialConnectionType().
71 config_watcher_
.reset(new NetworkConfigWatcherMac(&forwarder_
));
72 dns_config_service_thread_
->StartWithOptions(
73 base::Thread::Options(base::MessageLoop::TYPE_IO
, 0));
76 NetworkChangeNotifierMac::~NetworkChangeNotifierMac() {
77 // Delete the ConfigWatcher to join the notifier thread, ensuring that
78 // StartReachabilityNotifications() has an opportunity to run to completion.
79 config_watcher_
.reset();
81 // Now that StartReachabilityNotifications() has either run to completion or
82 // never run at all, unschedule reachability_ if it was previously scheduled.
83 if (reachability_
.get() && run_loop_
.get()) {
84 SCNetworkReachabilityUnscheduleFromRunLoop(reachability_
.get(),
86 kCFRunLoopCommonModes
);
91 NetworkChangeNotifier::NetworkChangeCalculatorParams
92 NetworkChangeNotifierMac::NetworkChangeCalculatorParamsMac() {
93 NetworkChangeCalculatorParams params
;
94 // Delay values arrived at by simple experimentation and adjusted so as to
95 // produce a single signal when switching between network connections.
96 params
.ip_address_offline_delay_
= base::TimeDelta::FromMilliseconds(500);
97 params
.ip_address_online_delay_
= base::TimeDelta::FromMilliseconds(500);
98 params
.connection_type_offline_delay_
=
99 base::TimeDelta::FromMilliseconds(1000);
100 params
.connection_type_online_delay_
= base::TimeDelta::FromMilliseconds(500);
104 NetworkChangeNotifier::ConnectionType
105 NetworkChangeNotifierMac::GetCurrentConnectionType() const {
106 base::ThreadRestrictions::ScopedAllowWait allow_wait
;
107 base::AutoLock
lock(connection_type_lock_
);
108 // Make sure the initial connection type is set before returning.
109 while (!connection_type_initialized_
) {
110 initial_connection_type_cv_
.Wait();
112 return connection_type_
;
115 void NetworkChangeNotifierMac::Forwarder::Init() {
116 net_config_watcher_
->SetInitialConnectionType();
119 void NetworkChangeNotifierMac::Forwarder::StartReachabilityNotifications() {
120 net_config_watcher_
->StartReachabilityNotifications();
123 void NetworkChangeNotifierMac::Forwarder::SetDynamicStoreNotificationKeys(
124 SCDynamicStoreRef store
) {
125 net_config_watcher_
->SetDynamicStoreNotificationKeys(store
);
128 void NetworkChangeNotifierMac::Forwarder::OnNetworkConfigChange(
129 CFArrayRef changed_keys
) {
130 net_config_watcher_
->OnNetworkConfigChange(changed_keys
);
133 void NetworkChangeNotifierMac::SetInitialConnectionType() {
134 // Called on notifier thread.
136 // Try to reach 0.0.0.0. This is the approach taken by Firefox:
138 // http://mxr.mozilla.org/mozilla2.0/source/netwerk/system/mac/nsNetworkLinkService.mm
140 // From my (adamk) testing on Snow Leopard, 0.0.0.0
141 // seems to be reachable if any network connection is available.
142 struct sockaddr_in addr
= {0};
143 addr
.sin_len
= sizeof(addr
);
144 addr
.sin_family
= AF_INET
;
145 reachability_
.reset(SCNetworkReachabilityCreateWithAddress(
146 kCFAllocatorDefault
, reinterpret_cast<struct sockaddr
*>(&addr
)));
148 SCNetworkConnectionFlags flags
;
149 ConnectionType connection_type
= CONNECTION_UNKNOWN
;
150 if (SCNetworkReachabilityGetFlags(reachability_
, &flags
)) {
151 connection_type
= CalculateConnectionType(flags
);
153 LOG(ERROR
) << "Could not get initial network connection type,"
154 << "assuming online.";
157 base::AutoLock
lock(connection_type_lock_
);
158 connection_type_
= connection_type
;
159 connection_type_initialized_
= true;
160 initial_connection_type_cv_
.Signal();
164 void NetworkChangeNotifierMac::StartReachabilityNotifications() {
165 // Called on notifier thread.
166 run_loop_
.reset(CFRunLoopGetCurrent());
167 CFRetain(run_loop_
.get());
169 DCHECK(reachability_
);
170 SCNetworkReachabilityContext reachability_context
= {
177 if (!SCNetworkReachabilitySetCallback(
179 &NetworkChangeNotifierMac::ReachabilityCallback
,
180 &reachability_context
)) {
181 LOG(DFATAL
) << "Could not set network reachability callback";
182 reachability_
.reset();
183 } else if (!SCNetworkReachabilityScheduleWithRunLoop(reachability_
,
185 kCFRunLoopCommonModes
)) {
186 LOG(DFATAL
) << "Could not schedule network reachability on run loop";
187 reachability_
.reset();
191 void NetworkChangeNotifierMac::SetDynamicStoreNotificationKeys(
192 SCDynamicStoreRef store
) {
194 // SCDynamicStore API does not exist on iOS.
197 base::ScopedCFTypeRef
<CFMutableArrayRef
> notification_keys(
198 CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
));
199 base::ScopedCFTypeRef
<CFStringRef
> key(
200 SCDynamicStoreKeyCreateNetworkGlobalEntity(
201 NULL
, kSCDynamicStoreDomainState
, kSCEntNetInterface
));
202 CFArrayAppendValue(notification_keys
.get(), key
.get());
203 key
.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
204 NULL
, kSCDynamicStoreDomainState
, kSCEntNetIPv4
));
205 CFArrayAppendValue(notification_keys
.get(), key
.get());
206 key
.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
207 NULL
, kSCDynamicStoreDomainState
, kSCEntNetIPv6
));
208 CFArrayAppendValue(notification_keys
.get(), key
.get());
210 // Set the notification keys. This starts us receiving notifications.
211 bool ret
= SCDynamicStoreSetNotificationKeys(
212 store
, notification_keys
.get(), NULL
);
213 // TODO(willchan): Figure out a proper way to handle this rather than crash.
215 #endif // defined(OS_IOS)
218 void NetworkChangeNotifierMac::OnNetworkConfigChange(CFArrayRef changed_keys
) {
220 // SCDynamicStore API does not exist on iOS.
223 DCHECK_EQ(run_loop_
.get(), CFRunLoopGetCurrent());
225 for (CFIndex i
= 0; i
< CFArrayGetCount(changed_keys
); ++i
) {
226 CFStringRef key
= static_cast<CFStringRef
>(
227 CFArrayGetValueAtIndex(changed_keys
, i
));
228 if (CFStringHasSuffix(key
, kSCEntNetIPv4
) ||
229 CFStringHasSuffix(key
, kSCEntNetIPv6
)) {
230 NotifyObserversOfIPAddressChange();
233 if (CFStringHasSuffix(key
, kSCEntNetInterface
)) {
234 // TODO(willchan): Does not appear to be working. Look into this.
235 // Perhaps this isn't needed anyway.
240 #endif // defined(OS_IOS)
244 void NetworkChangeNotifierMac::ReachabilityCallback(
245 SCNetworkReachabilityRef target
,
246 SCNetworkConnectionFlags flags
,
248 NetworkChangeNotifierMac
* notifier_mac
=
249 static_cast<NetworkChangeNotifierMac
*>(notifier
);
251 DCHECK_EQ(notifier_mac
->run_loop_
.get(), CFRunLoopGetCurrent());
253 ConnectionType new_type
= CalculateConnectionType(flags
);
254 ConnectionType old_type
;
256 base::AutoLock
lock(notifier_mac
->connection_type_lock_
);
257 old_type
= notifier_mac
->connection_type_
;
258 notifier_mac
->connection_type_
= new_type
;
260 if (old_type
!= new_type
) {
261 NotifyObserversOfConnectionTypeChange();
262 double max_bandwidth_mbps
=
263 NetworkChangeNotifier::GetMaxBandwidthForConnectionSubtype(
264 new_type
== CONNECTION_NONE
? SUBTYPE_NONE
: SUBTYPE_UNKNOWN
);
265 NotifyObserversOfMaxBandwidthChange(max_bandwidth_mbps
, new_type
);
269 // On iOS, the SCDynamicStore API does not exist, and we use the reachability
270 // API to detect IP address changes instead.
271 NotifyObserversOfIPAddressChange();
272 #endif // defined(OS_IOS)