Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / net / base / network_change_notifier_mac.cc
blobf8caee80f6584ca5730d9c84d8deb16879d1ce9b
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>
8 #include <resolv.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"
15 namespace net {
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);
26 if (reachable) {
27 #if defined(OS_IOS)
28 return (flags & kSCNetworkReachabilityFlagsIsWWAN) ?
29 NetworkChangeNotifier::CONNECTION_3G :
30 NetworkChangeNotifier::CONNECTION_WIFI;
31 #else
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)
36 } else {
37 return NetworkChangeNotifier::CONNECTION_NONE;
41 // Thread on which we can run DnsConfigService, which requires a TYPE_IO
42 // message loop.
43 class NetworkChangeNotifierMac::DnsConfigServiceThread : public base::Thread {
44 public:
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(); }
56 private:
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_),
67 forwarder_(this),
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(),
85 run_loop_.get(),
86 kCFRunLoopCommonModes);
90 // static
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);
101 return params;
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);
152 } else {
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 = {
171 0, // version
172 this, // user data
173 NULL, // retain
174 NULL, // release
175 NULL // description
177 if (!SCNetworkReachabilitySetCallback(
178 reachability_,
179 &NetworkChangeNotifierMac::ReachabilityCallback,
180 &reachability_context)) {
181 LOG(DFATAL) << "Could not set network reachability callback";
182 reachability_.reset();
183 } else if (!SCNetworkReachabilityScheduleWithRunLoop(reachability_,
184 run_loop_,
185 kCFRunLoopCommonModes)) {
186 LOG(DFATAL) << "Could not schedule network reachability on run loop";
187 reachability_.reset();
191 void NetworkChangeNotifierMac::SetDynamicStoreNotificationKeys(
192 SCDynamicStoreRef store) {
193 #if defined(OS_IOS)
194 // SCDynamicStore API does not exist on iOS.
195 NOTREACHED();
196 #else
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.
214 CHECK(ret);
215 #endif // defined(OS_IOS)
218 void NetworkChangeNotifierMac::OnNetworkConfigChange(CFArrayRef changed_keys) {
219 #if defined(OS_IOS)
220 // SCDynamicStore API does not exist on iOS.
221 NOTREACHED();
222 #else
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();
231 return;
233 if (CFStringHasSuffix(key, kSCEntNetInterface)) {
234 // TODO(willchan): Does not appear to be working. Look into this.
235 // Perhaps this isn't needed anyway.
236 } else {
237 NOTREACHED();
240 #endif // defined(OS_IOS)
243 // static
244 void NetworkChangeNotifierMac::ReachabilityCallback(
245 SCNetworkReachabilityRef target,
246 SCNetworkConnectionFlags flags,
247 void* notifier) {
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();
263 #if defined(OS_IOS)
264 // On iOS, the SCDynamicStore API does not exist, and we use the reachability
265 // API to detect IP address changes instead.
266 NotifyObserversOfIPAddressChange();
267 #endif // defined(OS_IOS)
270 } // namespace net