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.
4
5 #include "net/base/network_change_notifier_mac.h"
6
7 #include <netinet/in.h>
8 #include <resolv.h>
9
10 #include "base/basictypes.h"
11 #include "base/threading/thread.h"
12 #include "net/dns/dns_config_service.h"
13
14 namespace net {
15
CalculateReachability(SCNetworkConnectionFlags flags)16 static bool CalculateReachability(SCNetworkConnectionFlags flags) {
17 bool reachable = flags & kSCNetworkFlagsReachable;
18 bool connection_required = flags & kSCNetworkFlagsConnectionRequired;
19 return reachable && !connection_required;
20 }
21
CalculateConnectionType(SCNetworkConnectionFlags flags)22 NetworkChangeNotifier::ConnectionType CalculateConnectionType(
23 SCNetworkConnectionFlags flags) {
24 bool reachable = CalculateReachability(flags);
25 if (reachable) {
26 #if defined(OS_IOS)
27 return (flags & kSCNetworkReachabilityFlagsIsWWAN) ?
28 NetworkChangeNotifier::CONNECTION_3G :
29 NetworkChangeNotifier::CONNECTION_WIFI;
30 #else
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)
35 } else {
36 return NetworkChangeNotifier::CONNECTION_NONE;
37 }
38 }
39
40 // Thread on which we can run DnsConfigService, which requires a TYPE_IO
41 // message loop.
42 class NetworkChangeNotifierMac::DnsConfigServiceThread : public base::Thread {
43 public:
DnsConfigServiceThread()44 DnsConfigServiceThread() : base::Thread("DnsConfigService") {}
45
~DnsConfigServiceThread()46 virtual ~DnsConfigServiceThread() {
47 Stop();
48 }
49
Init()50 virtual void Init() OVERRIDE {
51 service_ = DnsConfigService::CreateSystemService();
52 service_->WatchConfig(base::Bind(&NetworkChangeNotifier::SetDnsConfig));
53 }
54
CleanUp()55 virtual void CleanUp() OVERRIDE {
56 service_.reset();
57 }
58
59 private:
60 scoped_ptr<DnsConfigService> service_;
61
62 DISALLOW_COPY_AND_ASSIGN(DnsConfigServiceThread);
63 };
64
NetworkChangeNotifierMac()65 NetworkChangeNotifierMac::NetworkChangeNotifierMac()
66 : NetworkChangeNotifier(NetworkChangeCalculatorParamsMac()),
67 connection_type_(CONNECTION_UNKNOWN),
68 connection_type_initialized_(false),
69 initial_connection_type_cv_(&connection_type_lock_),
70 forwarder_(this),
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));
77 }
78
~NetworkChangeNotifierMac()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();
83
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(),
88 run_loop_.get(),
89 kCFRunLoopCommonModes);
90 }
91 }
92
93 // static
94 NetworkChangeNotifier::NetworkChangeCalculatorParams
NetworkChangeCalculatorParamsMac()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);
104 return params;
105 }
106
107 NetworkChangeNotifier::ConnectionType
GetCurrentConnectionType() const108 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();
113 }
114 return connection_type_;
115 }
116
Init()117 void NetworkChangeNotifierMac::Forwarder::Init() {
118 net_config_watcher_->SetInitialConnectionType();
119 }
120
StartReachabilityNotifications()121 void NetworkChangeNotifierMac::Forwarder::StartReachabilityNotifications() {
122 net_config_watcher_->StartReachabilityNotifications();
123 }
124
SetDynamicStoreNotificationKeys(SCDynamicStoreRef store)125 void NetworkChangeNotifierMac::Forwarder::SetDynamicStoreNotificationKeys(
126 SCDynamicStoreRef store) {
127 net_config_watcher_->SetDynamicStoreNotificationKeys(store);
128 }
129
OnNetworkConfigChange(CFArrayRef changed_keys)130 void NetworkChangeNotifierMac::Forwarder::OnNetworkConfigChange(
131 CFArrayRef changed_keys) {
132 net_config_watcher_->OnNetworkConfigChange(changed_keys);
133 }
134
SetInitialConnectionType()135 void NetworkChangeNotifierMac::SetInitialConnectionType() {
136 // Called on notifier thread.
137
138 // Try to reach 0.0.0.0. This is the approach taken by Firefox:
139 //
140 // http://mxr.mozilla.org/mozilla2.0/source/netwerk/system/mac/nsNetworkLinkService.mm
141 //
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)));
149
150 SCNetworkConnectionFlags flags;
151 ConnectionType connection_type = CONNECTION_UNKNOWN;
152 if (SCNetworkReachabilityGetFlags(reachability_, &flags)) {
153 connection_type = CalculateConnectionType(flags);
154 } else {
155 LOG(ERROR) << "Could not get initial network connection type,"
156 << "assuming online.";
157 }
158 {
159 base::AutoLock lock(connection_type_lock_);
160 connection_type_ = connection_type;
161 connection_type_initialized_ = true;
162 initial_connection_type_cv_.Signal();
163 }
164 }
165
StartReachabilityNotifications()166 void NetworkChangeNotifierMac::StartReachabilityNotifications() {
167 // Called on notifier thread.
168 run_loop_.reset(CFRunLoopGetCurrent());
169 CFRetain(run_loop_.get());
170
171 DCHECK(reachability_);
172 SCNetworkReachabilityContext reachability_context = {
173 0, // version
174 this, // user data
175 NULL, // retain
176 NULL, // release
177 NULL // description
178 };
179 if (!SCNetworkReachabilitySetCallback(
180 reachability_,
181 &NetworkChangeNotifierMac::ReachabilityCallback,
182 &reachability_context)) {
183 LOG(DFATAL) << "Could not set network reachability callback";
184 reachability_.reset();
185 } else if (!SCNetworkReachabilityScheduleWithRunLoop(reachability_,
186 run_loop_,
187 kCFRunLoopCommonModes)) {
188 LOG(DFATAL) << "Could not schedule network reachability on run loop";
189 reachability_.reset();
190 }
191 }
192
SetDynamicStoreNotificationKeys(SCDynamicStoreRef store)193 void NetworkChangeNotifierMac::SetDynamicStoreNotificationKeys(
194 SCDynamicStoreRef store) {
195 #if defined(OS_IOS)
196 // SCDynamicStore API does not exist on iOS.
197 NOTREACHED();
198 #else
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());
211
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.
216 CHECK(ret);
217 #endif // defined(OS_IOS)
218 }
219
OnNetworkConfigChange(CFArrayRef changed_keys)220 void NetworkChangeNotifierMac::OnNetworkConfigChange(CFArrayRef changed_keys) {
221 #if defined(OS_IOS)
222 // SCDynamicStore API does not exist on iOS.
223 NOTREACHED();
224 #else
225 DCHECK_EQ(run_loop_.get(), CFRunLoopGetCurrent());
226
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();
233 return;
234 }
235 if (CFStringHasSuffix(key, kSCEntNetInterface)) {
236 // TODO(willchan): Does not appear to be working. Look into this.
237 // Perhaps this isn't needed anyway.
238 } else {
239 NOTREACHED();
240 }
241 }
242 #endif // defined(OS_IOS)
243 }
244
245 // static
ReachabilityCallback(SCNetworkReachabilityRef target,SCNetworkConnectionFlags flags,void * notifier)246 void NetworkChangeNotifierMac::ReachabilityCallback(
247 SCNetworkReachabilityRef target,
248 SCNetworkConnectionFlags flags,
249 void* notifier) {
250 NetworkChangeNotifierMac* notifier_mac =
251 static_cast<NetworkChangeNotifierMac*>(notifier);
252
253 DCHECK_EQ(notifier_mac->run_loop_.get(), CFRunLoopGetCurrent());
254
255 ConnectionType new_type = CalculateConnectionType(flags);
256 ConnectionType old_type;
257 {
258 base::AutoLock lock(notifier_mac->connection_type_lock_);
259 old_type = notifier_mac->connection_type_;
260 notifier_mac->connection_type_ = new_type;
261 }
262 if (old_type != new_type)
263 NotifyObserversOfConnectionTypeChange();
264
265 #if defined(OS_IOS)
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)
270 }
271
272 } // namespace net
273