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 virtual ~DnsConfigServiceThread() { 47 Stop(); 48 } 49 50 virtual void Init() OVERRIDE { 51 service_ = DnsConfigService::CreateSystemService(); 52 service_->WatchConfig(base::Bind(&NetworkChangeNotifier::SetDnsConfig)); 53 } 54 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 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 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 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 108 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 117 void NetworkChangeNotifierMac::Forwarder::Init() { 118 net_config_watcher_->SetInitialConnectionType(); 119 } 120 121 void NetworkChangeNotifierMac::Forwarder::StartReachabilityNotifications() { 122 net_config_watcher_->StartReachabilityNotifications(); 123 } 124 125 void NetworkChangeNotifierMac::Forwarder::SetDynamicStoreNotificationKeys( 126 SCDynamicStoreRef store) { 127 net_config_watcher_->SetDynamicStoreNotificationKeys(store); 128 } 129 130 void NetworkChangeNotifierMac::Forwarder::OnNetworkConfigChange( 131 CFArrayRef changed_keys) { 132 net_config_watcher_->OnNetworkConfigChange(changed_keys); 133 } 134 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 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 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 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 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 if (new_type != CONNECTION_NONE) 269 NotifyObserversOfIPAddressChange(); 270 #endif // defined(OS_IOS) 271 } 272 273 } // namespace net 274