Adding the orphaned options pages to the navigation
[chromium-blink-merge.git] / net / base / network_change_notifier_mac.cc
blob14ec5542c51cfa0f479e65192f8ab217f0c344c3
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 "net/dns/dns_config_service.h"
14 namespace net {
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);
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;
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:
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(); }
55 private:
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_),
66 forwarder_(this),
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(),
84 run_loop_.get(),
85 kCFRunLoopCommonModes);
89 // static
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);
100 return params;
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);
150 } else {
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 = {
169 0, // version
170 this, // user data
171 NULL, // retain
172 NULL, // release
173 NULL // description
175 if (!SCNetworkReachabilitySetCallback(
176 reachability_,
177 &NetworkChangeNotifierMac::ReachabilityCallback,
178 &reachability_context)) {
179 LOG(DFATAL) << "Could not set network reachability callback";
180 reachability_.reset();
181 } else if (!SCNetworkReachabilityScheduleWithRunLoop(reachability_,
182 run_loop_,
183 kCFRunLoopCommonModes)) {
184 LOG(DFATAL) << "Could not schedule network reachability on run loop";
185 reachability_.reset();
189 void NetworkChangeNotifierMac::SetDynamicStoreNotificationKeys(
190 SCDynamicStoreRef store) {
191 #if defined(OS_IOS)
192 // SCDynamicStore API does not exist on iOS.
193 NOTREACHED();
194 #else
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.
212 CHECK(ret);
213 #endif // defined(OS_IOS)
216 void NetworkChangeNotifierMac::OnNetworkConfigChange(CFArrayRef changed_keys) {
217 #if defined(OS_IOS)
218 // SCDynamicStore API does not exist on iOS.
219 NOTREACHED();
220 #else
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();
229 return;
231 if (CFStringHasSuffix(key, kSCEntNetInterface)) {
232 // TODO(willchan): Does not appear to be working. Look into this.
233 // Perhaps this isn't needed anyway.
234 } else {
235 NOTREACHED();
238 #endif // defined(OS_IOS)
241 // static
242 void NetworkChangeNotifierMac::ReachabilityCallback(
243 SCNetworkReachabilityRef target,
244 SCNetworkConnectionFlags flags,
245 void* notifier) {
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();
261 #if defined(OS_IOS)
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)
268 } // namespace net