Home | History | Annotate | Download | only in base
      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   NotifyObserversOfIPAddressChange();
    269 #endif  // defined(OS_IOS)
    270 }
    271 
    272 }  // namespace net
    273