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 ~DnsConfigServiceThread() override
{ Stop(); }
48 void Init() override
{
49 service_
= DnsConfigService::CreateSystemService();
50 service_
->WatchConfig(base::Bind(&NetworkChangeNotifier::SetDnsConfig
));
53 void CleanUp() override
{ service_
.reset(); }
56 scoped_ptr
<DnsConfigService
> service_
;
58 DISALLOW_COPY_AND_ASSIGN(DnsConfigServiceThread
);
61 NetworkChangeNotifierMac::NetworkChangeNotifierMac()
62 : NetworkChangeNotifier(NetworkChangeCalculatorParamsMac()),
63 connection_type_(CONNECTION_UNKNOWN
),
64 connection_type_initialized_(false),
65 initial_connection_type_cv_(&connection_type_lock_
),
67 dns_config_service_thread_(new DnsConfigServiceThread()) {
68 // Must be initialized after the rest of this object, as it may call back into
69 // SetInitialConnectionType().
70 config_watcher_
.reset(new NetworkConfigWatcherMac(&forwarder_
));
71 dns_config_service_thread_
->StartWithOptions(
72 base::Thread::Options(base::MessageLoop::TYPE_IO
, 0));
75 NetworkChangeNotifierMac::~NetworkChangeNotifierMac() {
76 // Delete the ConfigWatcher to join the notifier thread, ensuring that
77 // StartReachabilityNotifications() has an opportunity to run to completion.
78 config_watcher_
.reset();
80 // Now that StartReachabilityNotifications() has either run to completion or
81 // never run at all, unschedule reachability_ if it was previously scheduled.
82 if (reachability_
.get() && run_loop_
.get()) {
83 SCNetworkReachabilityUnscheduleFromRunLoop(reachability_
.get(),
85 kCFRunLoopCommonModes
);
90 NetworkChangeNotifier::NetworkChangeCalculatorParams
91 NetworkChangeNotifierMac::NetworkChangeCalculatorParamsMac() {
92 NetworkChangeCalculatorParams params
;
93 // Delay values arrived at by simple experimentation and adjusted so as to
94 // produce a single signal when switching between network connections.
95 params
.ip_address_offline_delay_
= base::TimeDelta::FromMilliseconds(500);
96 params
.ip_address_online_delay_
= base::TimeDelta::FromMilliseconds(500);
97 params
.connection_type_offline_delay_
=
98 base::TimeDelta::FromMilliseconds(1000);
99 params
.connection_type_online_delay_
= base::TimeDelta::FromMilliseconds(500);
103 NetworkChangeNotifier::ConnectionType
104 NetworkChangeNotifierMac::GetCurrentConnectionType() const {
105 base::AutoLock
lock(connection_type_lock_
);
106 // Make sure the initial connection type is set before returning.
107 while (!connection_type_initialized_
) {
108 initial_connection_type_cv_
.Wait();
110 return connection_type_
;
113 void NetworkChangeNotifierMac::Forwarder::Init() {
114 net_config_watcher_
->SetInitialConnectionType();
117 void NetworkChangeNotifierMac::Forwarder::StartReachabilityNotifications() {
118 net_config_watcher_
->StartReachabilityNotifications();
121 void NetworkChangeNotifierMac::Forwarder::SetDynamicStoreNotificationKeys(
122 SCDynamicStoreRef store
) {
123 net_config_watcher_
->SetDynamicStoreNotificationKeys(store
);
126 void NetworkChangeNotifierMac::Forwarder::OnNetworkConfigChange(
127 CFArrayRef changed_keys
) {
128 net_config_watcher_
->OnNetworkConfigChange(changed_keys
);
131 void NetworkChangeNotifierMac::SetInitialConnectionType() {
132 // Called on notifier thread.
134 // Try to reach 0.0.0.0. This is the approach taken by Firefox:
136 // http://mxr.mozilla.org/mozilla2.0/source/netwerk/system/mac/nsNetworkLinkService.mm
138 // From my (adamk) testing on Snow Leopard, 0.0.0.0
139 // seems to be reachable if any network connection is available.
140 struct sockaddr_in addr
= {0};
141 addr
.sin_len
= sizeof(addr
);
142 addr
.sin_family
= AF_INET
;
143 reachability_
.reset(SCNetworkReachabilityCreateWithAddress(
144 kCFAllocatorDefault
, reinterpret_cast<struct sockaddr
*>(&addr
)));
146 SCNetworkConnectionFlags flags
;
147 ConnectionType connection_type
= CONNECTION_UNKNOWN
;
148 if (SCNetworkReachabilityGetFlags(reachability_
, &flags
)) {
149 connection_type
= CalculateConnectionType(flags
);
151 LOG(ERROR
) << "Could not get initial network connection type,"
152 << "assuming online.";
155 base::AutoLock
lock(connection_type_lock_
);
156 connection_type_
= connection_type
;
157 connection_type_initialized_
= true;
158 initial_connection_type_cv_
.Signal();
162 void NetworkChangeNotifierMac::StartReachabilityNotifications() {
163 // Called on notifier thread.
164 run_loop_
.reset(CFRunLoopGetCurrent());
165 CFRetain(run_loop_
.get());
167 DCHECK(reachability_
);
168 SCNetworkReachabilityContext reachability_context
= {
175 if (!SCNetworkReachabilitySetCallback(
177 &NetworkChangeNotifierMac::ReachabilityCallback
,
178 &reachability_context
)) {
179 LOG(DFATAL
) << "Could not set network reachability callback";
180 reachability_
.reset();
181 } else if (!SCNetworkReachabilityScheduleWithRunLoop(reachability_
,
183 kCFRunLoopCommonModes
)) {
184 LOG(DFATAL
) << "Could not schedule network reachability on run loop";
185 reachability_
.reset();
189 void NetworkChangeNotifierMac::SetDynamicStoreNotificationKeys(
190 SCDynamicStoreRef store
) {
192 // SCDynamicStore API does not exist on iOS.
195 base::ScopedCFTypeRef
<CFMutableArrayRef
> notification_keys(
196 CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
));
197 base::ScopedCFTypeRef
<CFStringRef
> key(
198 SCDynamicStoreKeyCreateNetworkGlobalEntity(
199 NULL
, kSCDynamicStoreDomainState
, kSCEntNetInterface
));
200 CFArrayAppendValue(notification_keys
.get(), key
.get());
201 key
.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
202 NULL
, kSCDynamicStoreDomainState
, kSCEntNetIPv4
));
203 CFArrayAppendValue(notification_keys
.get(), key
.get());
204 key
.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
205 NULL
, kSCDynamicStoreDomainState
, kSCEntNetIPv6
));
206 CFArrayAppendValue(notification_keys
.get(), key
.get());
208 // Set the notification keys. This starts us receiving notifications.
209 bool ret
= SCDynamicStoreSetNotificationKeys(
210 store
, notification_keys
.get(), NULL
);
211 // TODO(willchan): Figure out a proper way to handle this rather than crash.
213 #endif // defined(OS_IOS)
216 void NetworkChangeNotifierMac::OnNetworkConfigChange(CFArrayRef changed_keys
) {
218 // SCDynamicStore API does not exist on iOS.
221 DCHECK_EQ(run_loop_
.get(), CFRunLoopGetCurrent());
223 for (CFIndex i
= 0; i
< CFArrayGetCount(changed_keys
); ++i
) {
224 CFStringRef key
= static_cast<CFStringRef
>(
225 CFArrayGetValueAtIndex(changed_keys
, i
));
226 if (CFStringHasSuffix(key
, kSCEntNetIPv4
) ||
227 CFStringHasSuffix(key
, kSCEntNetIPv6
)) {
228 NotifyObserversOfIPAddressChange();
231 if (CFStringHasSuffix(key
, kSCEntNetInterface
)) {
232 // TODO(willchan): Does not appear to be working. Look into this.
233 // Perhaps this isn't needed anyway.
238 #endif // defined(OS_IOS)
242 void NetworkChangeNotifierMac::ReachabilityCallback(
243 SCNetworkReachabilityRef target
,
244 SCNetworkConnectionFlags flags
,
246 NetworkChangeNotifierMac
* notifier_mac
=
247 static_cast<NetworkChangeNotifierMac
*>(notifier
);
249 DCHECK_EQ(notifier_mac
->run_loop_
.get(), CFRunLoopGetCurrent());
251 ConnectionType new_type
= CalculateConnectionType(flags
);
252 ConnectionType old_type
;
254 base::AutoLock
lock(notifier_mac
->connection_type_lock_
);
255 old_type
= notifier_mac
->connection_type_
;
256 notifier_mac
->connection_type_
= new_type
;
258 if (old_type
!= new_type
)
259 NotifyObserversOfConnectionTypeChange();
262 // On iOS, the SCDynamicStore API does not exist, and we use the reachability
263 // API to detect IP address changes instead.
264 NotifyObserversOfIPAddressChange();
265 #endif // defined(OS_IOS)