• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
CalculateReachability(SCNetworkConnectionFlags flags)16 static bool CalculateReachability(SCNetworkConnectionFlags flags) {
17   bool reachable = flags & kSCNetworkFlagsReachable;
18   bool connection_required = flags & kSCNetworkFlagsConnectionRequired;
19   return reachable && !connection_required;
20 }
21 
CalculateConnectionType(SCNetworkConnectionFlags flags)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:
DnsConfigServiceThread()44   DnsConfigServiceThread() : base::Thread("DnsConfigService") {}
45 
~DnsConfigServiceThread()46   virtual ~DnsConfigServiceThread() {
47     Stop();
48   }
49 
Init()50   virtual void Init() OVERRIDE {
51     service_ = DnsConfigService::CreateSystemService();
52     service_->WatchConfig(base::Bind(&NetworkChangeNotifier::SetDnsConfig));
53   }
54 
CleanUp()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 
NetworkChangeNotifierMac()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 
~NetworkChangeNotifierMac()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
NetworkChangeCalculatorParamsMac()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
GetCurrentConnectionType() const108 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 
Init()117 void NetworkChangeNotifierMac::Forwarder::Init()  {
118   net_config_watcher_->SetInitialConnectionType();
119 }
120 
StartReachabilityNotifications()121 void NetworkChangeNotifierMac::Forwarder::StartReachabilityNotifications() {
122   net_config_watcher_->StartReachabilityNotifications();
123 }
124 
SetDynamicStoreNotificationKeys(SCDynamicStoreRef store)125 void NetworkChangeNotifierMac::Forwarder::SetDynamicStoreNotificationKeys(
126     SCDynamicStoreRef store)  {
127   net_config_watcher_->SetDynamicStoreNotificationKeys(store);
128 }
129 
OnNetworkConfigChange(CFArrayRef changed_keys)130 void NetworkChangeNotifierMac::Forwarder::OnNetworkConfigChange(
131     CFArrayRef changed_keys)  {
132   net_config_watcher_->OnNetworkConfigChange(changed_keys);
133 }
134 
SetInitialConnectionType()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 
StartReachabilityNotifications()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 
SetDynamicStoreNotificationKeys(SCDynamicStoreRef store)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 
OnNetworkConfigChange(CFArrayRef changed_keys)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
ReachabilityCallback(SCNetworkReachabilityRef target,SCNetworkConnectionFlags flags,void * notifier)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