Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / net / base / network_change_notifier_mac.cc
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   ~DnsConfigServiceThread() override { Stop(); }
47
48   void Init() override {
49     service_ = DnsConfigService::CreateSystemService();
50     service_->WatchConfig(base::Bind(&NetworkChangeNotifier::SetDnsConfig));
51   }
52
53   void CleanUp() override { service_.reset(); }
54
55  private:
56   scoped_ptr<DnsConfigService> service_;
57
58   DISALLOW_COPY_AND_ASSIGN(DnsConfigServiceThread);
59 };
60
61 NetworkChangeNotifierMac::NetworkChangeNotifierMac()
62     : NetworkChangeNotifier(NetworkChangeCalculatorParamsMac()),
63       connection_type_(CONNECTION_UNKNOWN),
64       connection_type_initialized_(false),
65       initial_connection_type_cv_(&connection_type_lock_),
66       forwarder_(this),
67       dns_config_service_thread_(new DnsConfigServiceThread()) {
68   // Must be initialized after the rest of this object, as it may call back into
69   // SetInitialConnectionType().
70   config_watcher_.reset(new NetworkConfigWatcherMac(&forwarder_));
71   dns_config_service_thread_->StartWithOptions(
72       base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
73 }
74
75 NetworkChangeNotifierMac::~NetworkChangeNotifierMac() {
76   // Delete the ConfigWatcher to join the notifier thread, ensuring that
77   // StartReachabilityNotifications() has an opportunity to run to completion.
78   config_watcher_.reset();
79
80   // Now that StartReachabilityNotifications() has either run to completion or
81   // never run at all, unschedule reachability_ if it was previously scheduled.
82   if (reachability_.get() && run_loop_.get()) {
83     SCNetworkReachabilityUnscheduleFromRunLoop(reachability_.get(),
84                                                run_loop_.get(),
85                                                kCFRunLoopCommonModes);
86   }
87 }
88
89 // static
90 NetworkChangeNotifier::NetworkChangeCalculatorParams
91 NetworkChangeNotifierMac::NetworkChangeCalculatorParamsMac() {
92   NetworkChangeCalculatorParams params;
93   // Delay values arrived at by simple experimentation and adjusted so as to
94   // produce a single signal when switching between network connections.
95   params.ip_address_offline_delay_ = base::TimeDelta::FromMilliseconds(500);
96   params.ip_address_online_delay_ = base::TimeDelta::FromMilliseconds(500);
97   params.connection_type_offline_delay_ =
98       base::TimeDelta::FromMilliseconds(1000);
99   params.connection_type_online_delay_ = base::TimeDelta::FromMilliseconds(500);
100   return params;
101 }
102
103 NetworkChangeNotifier::ConnectionType
104 NetworkChangeNotifierMac::GetCurrentConnectionType() const {
105   base::AutoLock lock(connection_type_lock_);
106   // Make sure the initial connection type is set before returning.
107   while (!connection_type_initialized_) {
108     initial_connection_type_cv_.Wait();
109   }
110   return connection_type_;
111 }
112
113 void NetworkChangeNotifierMac::Forwarder::Init()  {
114   net_config_watcher_->SetInitialConnectionType();
115 }
116
117 void NetworkChangeNotifierMac::Forwarder::StartReachabilityNotifications() {
118   net_config_watcher_->StartReachabilityNotifications();
119 }
120
121 void NetworkChangeNotifierMac::Forwarder::SetDynamicStoreNotificationKeys(
122     SCDynamicStoreRef store)  {
123   net_config_watcher_->SetDynamicStoreNotificationKeys(store);
124 }
125
126 void NetworkChangeNotifierMac::Forwarder::OnNetworkConfigChange(
127     CFArrayRef changed_keys)  {
128   net_config_watcher_->OnNetworkConfigChange(changed_keys);
129 }
130
131 void NetworkChangeNotifierMac::SetInitialConnectionType() {
132   // Called on notifier thread.
133
134   // Try to reach 0.0.0.0. This is the approach taken by Firefox:
135   //
136   // http://mxr.mozilla.org/mozilla2.0/source/netwerk/system/mac/nsNetworkLinkService.mm
137   //
138   // From my (adamk) testing on Snow Leopard, 0.0.0.0
139   // seems to be reachable if any network connection is available.
140   struct sockaddr_in addr = {0};
141   addr.sin_len = sizeof(addr);
142   addr.sin_family = AF_INET;
143   reachability_.reset(SCNetworkReachabilityCreateWithAddress(
144       kCFAllocatorDefault, reinterpret_cast<struct sockaddr*>(&addr)));
145
146   SCNetworkConnectionFlags flags;
147   ConnectionType connection_type = CONNECTION_UNKNOWN;
148   if (SCNetworkReachabilityGetFlags(reachability_, &flags)) {
149     connection_type = CalculateConnectionType(flags);
150   } else {
151     LOG(ERROR) << "Could not get initial network connection type,"
152                << "assuming online.";
153   }
154   {
155     base::AutoLock lock(connection_type_lock_);
156     connection_type_ = connection_type;
157     connection_type_initialized_ = true;
158     initial_connection_type_cv_.Signal();
159   }
160 }
161
162 void NetworkChangeNotifierMac::StartReachabilityNotifications() {
163   // Called on notifier thread.
164   run_loop_.reset(CFRunLoopGetCurrent());
165   CFRetain(run_loop_.get());
166
167   DCHECK(reachability_);
168   SCNetworkReachabilityContext reachability_context = {
169     0,     // version
170     this,  // user data
171     NULL,  // retain
172     NULL,  // release
173     NULL   // description
174   };
175   if (!SCNetworkReachabilitySetCallback(
176           reachability_,
177           &NetworkChangeNotifierMac::ReachabilityCallback,
178           &reachability_context)) {
179     LOG(DFATAL) << "Could not set network reachability callback";
180     reachability_.reset();
181   } else if (!SCNetworkReachabilityScheduleWithRunLoop(reachability_,
182                                                        run_loop_,
183                                                        kCFRunLoopCommonModes)) {
184     LOG(DFATAL) << "Could not schedule network reachability on run loop";
185     reachability_.reset();
186   }
187 }
188
189 void NetworkChangeNotifierMac::SetDynamicStoreNotificationKeys(
190     SCDynamicStoreRef store) {
191 #if defined(OS_IOS)
192   // SCDynamicStore API does not exist on iOS.
193   NOTREACHED();
194 #else
195   base::ScopedCFTypeRef<CFMutableArrayRef> notification_keys(
196       CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks));
197   base::ScopedCFTypeRef<CFStringRef> key(
198       SCDynamicStoreKeyCreateNetworkGlobalEntity(
199           NULL, kSCDynamicStoreDomainState, kSCEntNetInterface));
200   CFArrayAppendValue(notification_keys.get(), key.get());
201   key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
202       NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4));
203   CFArrayAppendValue(notification_keys.get(), key.get());
204   key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
205       NULL, kSCDynamicStoreDomainState, kSCEntNetIPv6));
206   CFArrayAppendValue(notification_keys.get(), key.get());
207
208   // Set the notification keys.  This starts us receiving notifications.
209   bool ret = SCDynamicStoreSetNotificationKeys(
210       store, notification_keys.get(), NULL);
211   // TODO(willchan): Figure out a proper way to handle this rather than crash.
212   CHECK(ret);
213 #endif  // defined(OS_IOS)
214 }
215
216 void NetworkChangeNotifierMac::OnNetworkConfigChange(CFArrayRef changed_keys) {
217 #if defined(OS_IOS)
218   // SCDynamicStore API does not exist on iOS.
219   NOTREACHED();
220 #else
221   DCHECK_EQ(run_loop_.get(), CFRunLoopGetCurrent());
222
223   for (CFIndex i = 0; i < CFArrayGetCount(changed_keys); ++i) {
224     CFStringRef key = static_cast<CFStringRef>(
225         CFArrayGetValueAtIndex(changed_keys, i));
226     if (CFStringHasSuffix(key, kSCEntNetIPv4) ||
227         CFStringHasSuffix(key, kSCEntNetIPv6)) {
228       NotifyObserversOfIPAddressChange();
229       return;
230     }
231     if (CFStringHasSuffix(key, kSCEntNetInterface)) {
232       // TODO(willchan): Does not appear to be working.  Look into this.
233       // Perhaps this isn't needed anyway.
234     } else {
235       NOTREACHED();
236     }
237   }
238 #endif  // defined(OS_IOS)
239 }
240
241 // static
242 void NetworkChangeNotifierMac::ReachabilityCallback(
243     SCNetworkReachabilityRef target,
244     SCNetworkConnectionFlags flags,
245     void* notifier) {
246   NetworkChangeNotifierMac* notifier_mac =
247       static_cast<NetworkChangeNotifierMac*>(notifier);
248
249   DCHECK_EQ(notifier_mac->run_loop_.get(), CFRunLoopGetCurrent());
250
251   ConnectionType new_type = CalculateConnectionType(flags);
252   ConnectionType old_type;
253   {
254     base::AutoLock lock(notifier_mac->connection_type_lock_);
255     old_type = notifier_mac->connection_type_;
256     notifier_mac->connection_type_ = new_type;
257   }
258   if (old_type != new_type)
259     NotifyObserversOfConnectionTypeChange();
260
261 #if defined(OS_IOS)
262   // On iOS, the SCDynamicStore API does not exist, and we use the reachability
263   // API to detect IP address changes instead.
264   NotifyObserversOfIPAddressChange();
265 #endif  // defined(OS_IOS)
266 }
267
268 }  // namespace net