| OLD | NEW |
| (Empty) |
| 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 | |
| 16 static bool CalculateReachability(SCNetworkConnectionFlags flags) { | |
| 17 bool reachable = flags & kSCNetworkFlagsReachable; | |
| 18 bool connection_required = flags & kSCNetworkFlagsConnectionRequired; | |
| 19 return reachable && !connection_required; | |
| 20 } | |
| 21 | |
| 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: | |
| 44 DnsConfigServiceThread() : base::Thread("DnsConfigService") {} | |
| 45 | |
| 46 ~DnsConfigServiceThread() override { Stop(); } | |
| 47 | |
| 48 void Init() override { | |
| 49 service_ = DnsConfigService::CreateSystemService(); | |
| 50 service_->WatchConfig(base::Bind(&NetworkChangeNotifier::SetDnsConfig)); | |
| 51 } | |
| 52 | |
| 53 void CleanUp() override { service_.reset(); } | |
| 54 | |
| 55 private: | |
| 56 scoped_ptr<DnsConfigService> service_; | |
| 57 | |
| 58 DISALLOW_COPY_AND_ASSIGN(DnsConfigServiceThread); | |
| 59 }; | |
| 60 | |
| 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)); | |
| 73 } | |
| 74 | |
| 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(); | |
| 79 | |
| 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); | |
| 86 } | |
| 87 } | |
| 88 | |
| 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; | |
| 101 } | |
| 102 | |
| 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(); | |
| 109 } | |
| 110 return connection_type_; | |
| 111 } | |
| 112 | |
| 113 void NetworkChangeNotifierMac::Forwarder::Init() { | |
| 114 net_config_watcher_->SetInitialConnectionType(); | |
| 115 } | |
| 116 | |
| 117 void NetworkChangeNotifierMac::Forwarder::StartReachabilityNotifications() { | |
| 118 net_config_watcher_->StartReachabilityNotifications(); | |
| 119 } | |
| 120 | |
| 121 void NetworkChangeNotifierMac::Forwarder::SetDynamicStoreNotificationKeys( | |
| 122 SCDynamicStoreRef store) { | |
| 123 net_config_watcher_->SetDynamicStoreNotificationKeys(store); | |
| 124 } | |
| 125 | |
| 126 void NetworkChangeNotifierMac::Forwarder::OnNetworkConfigChange( | |
| 127 CFArrayRef changed_keys) { | |
| 128 net_config_watcher_->OnNetworkConfigChange(changed_keys); | |
| 129 } | |
| 130 | |
| 131 void NetworkChangeNotifierMac::SetInitialConnectionType() { | |
| 132 // Called on notifier thread. | |
| 133 | |
| 134 // Try to reach 0.0.0.0. This is the approach taken by Firefox: | |
| 135 // | |
| 136 // http://mxr.mozilla.org/mozilla2.0/source/netwerk/system/mac/nsNetworkLinkSe
rvice.mm | |
| 137 // | |
| 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))); | |
| 145 | |
| 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."; | |
| 153 } | |
| 154 { | |
| 155 base::AutoLock lock(connection_type_lock_); | |
| 156 connection_type_ = connection_type; | |
| 157 connection_type_initialized_ = true; | |
| 158 initial_connection_type_cv_.Signal(); | |
| 159 } | |
| 160 } | |
| 161 | |
| 162 void NetworkChangeNotifierMac::StartReachabilityNotifications() { | |
| 163 // Called on notifier thread. | |
| 164 run_loop_.reset(CFRunLoopGetCurrent()); | |
| 165 CFRetain(run_loop_.get()); | |
| 166 | |
| 167 DCHECK(reachability_); | |
| 168 SCNetworkReachabilityContext reachability_context = { | |
| 169 0, // version | |
| 170 this, // user data | |
| 171 NULL, // retain | |
| 172 NULL, // release | |
| 173 NULL // description | |
| 174 }; | |
| 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(); | |
| 186 } | |
| 187 } | |
| 188 | |
| 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()); | |
| 207 | |
| 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) | |
| 214 } | |
| 215 | |
| 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()); | |
| 222 | |
| 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; | |
| 230 } | |
| 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(); | |
| 236 } | |
| 237 } | |
| 238 #endif // defined(OS_IOS) | |
| 239 } | |
| 240 | |
| 241 // static | |
| 242 void NetworkChangeNotifierMac::ReachabilityCallback( | |
| 243 SCNetworkReachabilityRef target, | |
| 244 SCNetworkConnectionFlags flags, | |
| 245 void* notifier) { | |
| 246 NetworkChangeNotifierMac* notifier_mac = | |
| 247 static_cast<NetworkChangeNotifierMac*>(notifier); | |
| 248 | |
| 249 DCHECK_EQ(notifier_mac->run_loop_.get(), CFRunLoopGetCurrent()); | |
| 250 | |
| 251 ConnectionType new_type = CalculateConnectionType(flags); | |
| 252 ConnectionType old_type; | |
| 253 { | |
| 254 base::AutoLock lock(notifier_mac->connection_type_lock_); | |
| 255 old_type = notifier_mac->connection_type_; | |
| 256 notifier_mac->connection_type_ = new_type; | |
| 257 } | |
| 258 if (old_type != new_type) | |
| 259 NotifyObserversOfConnectionTypeChange(); | |
| 260 | |
| 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) | |
| 266 } | |
| 267 | |
| 268 } // namespace net | |
| OLD | NEW |