| // 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 |