| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/base/network_change_notifier_mac.h" |
| |
| #include <netinet/in.h> |
| #include <resolv.h> |
| |
| #include "base/macros.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/threading/thread.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "build/build_config.h" |
| #include "net/dns/dns_config_service.h" |
| #include "starboard/types.h" |
| |
| namespace net { |
| |
| static bool CalculateReachability(SCNetworkConnectionFlags flags) { |
| bool reachable = flags & kSCNetworkFlagsReachable; |
| bool connection_required = flags & kSCNetworkFlagsConnectionRequired; |
| return reachable && !connection_required; |
| } |
| |
| // Thread on which we can run DnsConfigService, which requires a TYPE_IO |
| // message loop. |
| class NetworkChangeNotifierMac::DnsConfigServiceThread : public base::Thread { |
| public: |
| DnsConfigServiceThread() : base::Thread("DnsConfigService") {} |
| |
| ~DnsConfigServiceThread() override { Stop(); } |
| |
| void Init() override { |
| service_ = DnsConfigService::CreateSystemService(); |
| service_->WatchConfig(base::Bind(&NetworkChangeNotifier::SetDnsConfig)); |
| } |
| |
| void CleanUp() override { service_.reset(); } |
| |
| private: |
| std::unique_ptr<DnsConfigService> service_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DnsConfigServiceThread); |
| }; |
| |
| NetworkChangeNotifierMac::NetworkChangeNotifierMac() |
| : NetworkChangeNotifier(NetworkChangeCalculatorParamsMac()), |
| connection_type_(CONNECTION_UNKNOWN), |
| connection_type_initialized_(false), |
| initial_connection_type_cv_(&connection_type_lock_), |
| forwarder_(this) { |
| // Must be initialized after the rest of this object, as it may call back into |
| // SetInitialConnectionType(). |
| config_watcher_ = std::make_unique<NetworkConfigWatcherMac>(&forwarder_); |
| #if !defined(OS_IOS) |
| // DnsConfigService on iOS doesn't watch the config so its result can become |
| // inaccurate at any time. Disable it to prevent promulgation of inaccurate |
| // DnsConfigs. |
| dns_config_service_thread_ = std::make_unique<DnsConfigServiceThread>(); |
| dns_config_service_thread_->StartWithOptions( |
| base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); |
| #endif |
| } |
| |
| NetworkChangeNotifierMac::~NetworkChangeNotifierMac() { |
| // Delete the ConfigWatcher to join the notifier thread, ensuring that |
| // StartReachabilityNotifications() has an opportunity to run to completion. |
| config_watcher_.reset(); |
| |
| // Now that StartReachabilityNotifications() has either run to completion or |
| // never run at all, unschedule reachability_ if it was previously scheduled. |
| if (reachability_.get() && run_loop_.get()) { |
| SCNetworkReachabilityUnscheduleFromRunLoop(reachability_.get(), |
| run_loop_.get(), |
| kCFRunLoopCommonModes); |
| } |
| } |
| |
| // static |
| NetworkChangeNotifier::NetworkChangeCalculatorParams |
| NetworkChangeNotifierMac::NetworkChangeCalculatorParamsMac() { |
| NetworkChangeCalculatorParams params; |
| // Delay values arrived at by simple experimentation and adjusted so as to |
| // produce a single signal when switching between network connections. |
| params.ip_address_offline_delay_ = base::TimeDelta::FromMilliseconds(500); |
| params.ip_address_online_delay_ = base::TimeDelta::FromMilliseconds(500); |
| params.connection_type_offline_delay_ = |
| base::TimeDelta::FromMilliseconds(1000); |
| params.connection_type_online_delay_ = base::TimeDelta::FromMilliseconds(500); |
| return params; |
| } |
| |
| NetworkChangeNotifier::ConnectionType |
| NetworkChangeNotifierMac::GetCurrentConnectionType() const { |
| base::ThreadRestrictions::ScopedAllowWait allow_wait; |
| base::AutoLock lock(connection_type_lock_); |
| // Make sure the initial connection type is set before returning. |
| while (!connection_type_initialized_) { |
| initial_connection_type_cv_.Wait(); |
| } |
| return connection_type_; |
| } |
| |
| void NetworkChangeNotifierMac::Forwarder::Init() { |
| net_config_watcher_->SetInitialConnectionType(); |
| } |
| |
| // static |
| NetworkChangeNotifier::ConnectionType |
| NetworkChangeNotifierMac::CalculateConnectionType( |
| SCNetworkConnectionFlags flags) { |
| bool reachable = CalculateReachability(flags); |
| if (!reachable) |
| return CONNECTION_NONE; |
| |
| #if defined(OS_IOS) |
| return (flags & kSCNetworkReachabilityFlagsIsWWAN) ? CONNECTION_3G |
| : CONNECTION_WIFI; |
| #else |
| return ConnectionTypeFromInterfaces(); |
| #endif |
| } |
| |
| void NetworkChangeNotifierMac::Forwarder::StartReachabilityNotifications() { |
| net_config_watcher_->StartReachabilityNotifications(); |
| } |
| |
| void NetworkChangeNotifierMac::Forwarder::SetDynamicStoreNotificationKeys( |
| SCDynamicStoreRef store) { |
| net_config_watcher_->SetDynamicStoreNotificationKeys(store); |
| } |
| |
| void NetworkChangeNotifierMac::Forwarder::OnNetworkConfigChange( |
| CFArrayRef changed_keys) { |
| net_config_watcher_->OnNetworkConfigChange(changed_keys); |
| } |
| |
| void NetworkChangeNotifierMac::SetInitialConnectionType() { |
| // Called on notifier thread. |
| |
| // Try to reach 0.0.0.0. This is the approach taken by Firefox: |
| // |
| // http://mxr.mozilla.org/mozilla2.0/source/netwerk/system/mac/nsNetworkLinkService.mm |
| // |
| // From my (adamk) testing on Snow Leopard, 0.0.0.0 |
| // seems to be reachable if any network connection is available. |
| struct sockaddr_in addr = {0}; |
| addr.sin_len = sizeof(addr); |
| addr.sin_family = AF_INET; |
| reachability_.reset(SCNetworkReachabilityCreateWithAddress( |
| kCFAllocatorDefault, reinterpret_cast<struct sockaddr*>(&addr))); |
| |
| SCNetworkConnectionFlags flags; |
| ConnectionType connection_type = CONNECTION_UNKNOWN; |
| if (SCNetworkReachabilityGetFlags(reachability_, &flags)) { |
| connection_type = CalculateConnectionType(flags); |
| } else { |
| LOG(ERROR) << "Could not get initial network connection type," |
| << "assuming online."; |
| } |
| { |
| base::AutoLock lock(connection_type_lock_); |
| connection_type_ = connection_type; |
| connection_type_initialized_ = true; |
| initial_connection_type_cv_.Broadcast(); |
| } |
| } |
| |
| void NetworkChangeNotifierMac::StartReachabilityNotifications() { |
| // Called on notifier thread. |
| run_loop_.reset(CFRunLoopGetCurrent()); |
| CFRetain(run_loop_.get()); |
| |
| DCHECK(reachability_); |
| SCNetworkReachabilityContext reachability_context = { |
| 0, // version |
| this, // user data |
| NULL, // retain |
| NULL, // release |
| NULL // description |
| }; |
| if (!SCNetworkReachabilitySetCallback( |
| reachability_, |
| &NetworkChangeNotifierMac::ReachabilityCallback, |
| &reachability_context)) { |
| LOG(DFATAL) << "Could not set network reachability callback"; |
| reachability_.reset(); |
| } else if (!SCNetworkReachabilityScheduleWithRunLoop(reachability_, |
| run_loop_, |
| kCFRunLoopCommonModes)) { |
| LOG(DFATAL) << "Could not schedule network reachability on run loop"; |
| reachability_.reset(); |
| } |
| } |
| |
| void NetworkChangeNotifierMac::SetDynamicStoreNotificationKeys( |
| SCDynamicStoreRef store) { |
| #if defined(OS_IOS) |
| // SCDynamicStore API does not exist on iOS. |
| NOTREACHED(); |
| #else |
| base::ScopedCFTypeRef<CFMutableArrayRef> notification_keys( |
| CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)); |
| base::ScopedCFTypeRef<CFStringRef> key( |
| SCDynamicStoreKeyCreateNetworkGlobalEntity( |
| NULL, kSCDynamicStoreDomainState, kSCEntNetInterface)); |
| CFArrayAppendValue(notification_keys.get(), key.get()); |
| key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity( |
| NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4)); |
| CFArrayAppendValue(notification_keys.get(), key.get()); |
| key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity( |
| NULL, kSCDynamicStoreDomainState, kSCEntNetIPv6)); |
| CFArrayAppendValue(notification_keys.get(), key.get()); |
| |
| // Set the notification keys. This starts us receiving notifications. |
| bool ret = SCDynamicStoreSetNotificationKeys( |
| store, notification_keys.get(), NULL); |
| // TODO(willchan): Figure out a proper way to handle this rather than crash. |
| CHECK(ret); |
| #endif // defined(OS_IOS) |
| } |
| |
| void NetworkChangeNotifierMac::OnNetworkConfigChange(CFArrayRef changed_keys) { |
| #if defined(OS_IOS) |
| // SCDynamicStore API does not exist on iOS. |
| NOTREACHED(); |
| #else |
| DCHECK_EQ(run_loop_.get(), CFRunLoopGetCurrent()); |
| |
| for (CFIndex i = 0; i < CFArrayGetCount(changed_keys); ++i) { |
| CFStringRef key = static_cast<CFStringRef>( |
| CFArrayGetValueAtIndex(changed_keys, i)); |
| if (CFStringHasSuffix(key, kSCEntNetIPv4) || |
| CFStringHasSuffix(key, kSCEntNetIPv6)) { |
| NotifyObserversOfIPAddressChange(); |
| return; |
| } |
| if (CFStringHasSuffix(key, kSCEntNetInterface)) { |
| // TODO(willchan): Does not appear to be working. Look into this. |
| // Perhaps this isn't needed anyway. |
| } else { |
| NOTREACHED(); |
| } |
| } |
| #endif // defined(OS_IOS) |
| } |
| |
| // static |
| void NetworkChangeNotifierMac::ReachabilityCallback( |
| SCNetworkReachabilityRef target, |
| SCNetworkConnectionFlags flags, |
| void* notifier) { |
| NetworkChangeNotifierMac* notifier_mac = |
| static_cast<NetworkChangeNotifierMac*>(notifier); |
| |
| DCHECK_EQ(notifier_mac->run_loop_.get(), CFRunLoopGetCurrent()); |
| |
| ConnectionType new_type = CalculateConnectionType(flags); |
| ConnectionType old_type; |
| { |
| base::AutoLock lock(notifier_mac->connection_type_lock_); |
| old_type = notifier_mac->connection_type_; |
| notifier_mac->connection_type_ = new_type; |
| } |
| if (old_type != new_type) { |
| NotifyObserversOfConnectionTypeChange(); |
| double max_bandwidth_mbps = |
| NetworkChangeNotifier::GetMaxBandwidthMbpsForConnectionSubtype( |
| new_type == CONNECTION_NONE ? SUBTYPE_NONE : SUBTYPE_UNKNOWN); |
| NotifyObserversOfMaxBandwidthChange(max_bandwidth_mbps, new_type); |
| } |
| |
| #if defined(OS_IOS) |
| // On iOS, the SCDynamicStore API does not exist, and we use the reachability |
| // API to detect IP address changes instead. |
| NotifyObserversOfIPAddressChange(); |
| #endif // defined(OS_IOS) |
| } |
| |
| } // namespace net |