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.
5 #include "net/base/network_change_notifier_win.h"
10 #include "base/bind.h"
11 #include "base/logging.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "base/threading/thread.h"
14 #include "base/time/time.h"
15 #include "net/base/winsock_init.h"
16 #include "net/dns/dns_config_service.h"
18 #pragma comment(lib, "iphlpapi.lib")
24 // Time between NotifyAddrChange retries, on failure.
25 const int kWatchForAddressChangeRetryIntervalMs
= 500;
29 // Thread on which we can run DnsConfigService, which requires AssertIOAllowed
30 // to open registry keys and to handle FilePathWatcher updates.
31 class NetworkChangeNotifierWin::DnsConfigServiceThread
: public base::Thread
{
33 DnsConfigServiceThread() : base::Thread("DnsConfigService") {}
35 ~DnsConfigServiceThread() override
{ Stop(); }
37 void Init() override
{
38 service_
= DnsConfigService::CreateSystemService();
39 service_
->WatchConfig(base::Bind(&NetworkChangeNotifier::SetDnsConfig
));
42 void CleanUp() override
{ service_
.reset(); }
45 scoped_ptr
<DnsConfigService
> service_
;
47 DISALLOW_COPY_AND_ASSIGN(DnsConfigServiceThread
);
50 NetworkChangeNotifierWin::NetworkChangeNotifierWin()
51 : NetworkChangeNotifier(NetworkChangeCalculatorParamsWin()),
53 sequential_failures_(0),
54 dns_config_service_thread_(new DnsConfigServiceThread()),
55 last_computed_connection_type_(RecomputeCurrentConnectionType()),
56 last_announced_offline_(last_computed_connection_type_
==
59 memset(&addr_overlapped_
, 0, sizeof addr_overlapped_
);
60 addr_overlapped_
.hEvent
= WSACreateEvent();
63 NetworkChangeNotifierWin::~NetworkChangeNotifierWin() {
65 CancelIPChangeNotify(&addr_overlapped_
);
66 addr_watcher_
.StopWatching();
68 WSACloseEvent(addr_overlapped_
.hEvent
);
72 NetworkChangeNotifier::NetworkChangeCalculatorParams
73 NetworkChangeNotifierWin::NetworkChangeCalculatorParamsWin() {
74 NetworkChangeCalculatorParams params
;
75 // Delay values arrived at by simple experimentation and adjusted so as to
76 // produce a single signal when switching between network connections.
77 params
.ip_address_offline_delay_
= base::TimeDelta::FromMilliseconds(1500);
78 params
.ip_address_online_delay_
= base::TimeDelta::FromMilliseconds(1500);
79 params
.connection_type_offline_delay_
=
80 base::TimeDelta::FromMilliseconds(1500);
81 params
.connection_type_online_delay_
= base::TimeDelta::FromMilliseconds(500);
85 // This implementation does not return the actual connection type but merely
86 // determines if the user is "online" (in which case it returns
87 // CONNECTION_UNKNOWN) or "offline" (and then it returns CONNECTION_NONE).
88 // This is challenging since the only thing we can test with certainty is
89 // whether a *particular* host is reachable.
91 // While we can't conclusively determine when a user is "online", we can at
92 // least reliably recognize some of the situtations when they are clearly
93 // "offline". For example, if the user's laptop is not plugged into an ethernet
94 // network and is not connected to any wireless networks, it must be offline.
96 // There are a number of different ways to implement this on Windows, each with
97 // their pros and cons. Here is a comparison of various techniques considered:
99 // (1) Use InternetGetConnectedState (wininet.dll). This function is really easy
100 // to use (literally a one-liner), and runs quickly. The drawback is it adds a
101 // dependency on the wininet DLL.
103 // (2) Enumerate all of the network interfaces using GetAdaptersAddresses
104 // (iphlpapi.dll), and assume we are "online" if there is at least one interface
105 // that is connected, and that interface is not a loopback or tunnel.
107 // Safari on Windows has a fairly simple implementation that does this:
108 // http://trac.webkit.org/browser/trunk/WebCore/platform/network/win/NetworkStateNotifierWin.cpp.
110 // Mozilla similarly uses this approach:
111 // http://mxr.mozilla.org/mozilla1.9.2/source/netwerk/system/win32/nsNotifyAddrListener.cpp
113 // The biggest drawback to this approach is it is quite complicated.
114 // WebKit's implementation for example doesn't seem to test for ICS gateways
115 // (internet connection sharing), whereas Mozilla's implementation has extra
116 // code to guess that.
118 // (3) The method used in this file comes from google talk, and is similar to
119 // method (2). The main difference is it enumerates the winsock namespace
120 // providers rather than the actual adapters.
122 // I ran some benchmarks comparing the performance of each on my Windows 7
123 // workstation. Here is what I found:
124 // * Approach (1) was pretty much zero-cost after the initial call.
125 // * Approach (2) took an average of 3.25 milliseconds to enumerate the
127 // * Approach (3) took an average of 0.8 ms to enumerate the providers.
129 // In terms of correctness, all three approaches were comparable for the simple
130 // experiments I ran... However none of them correctly returned "offline" when
131 // executing 'ipconfig /release'.
133 NetworkChangeNotifier::ConnectionType
134 NetworkChangeNotifierWin::RecomputeCurrentConnectionType() const {
135 DCHECK(CalledOnValidThread());
139 // The following code was adapted from:
140 // http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/net/notifier/base/win/async_network_alive_win32.cc?view=markup&pathrev=47343
141 // The main difference is we only call WSALookupServiceNext once, whereas
142 // the earlier code would traverse the entire list and pass LUP_FLUSHPREVIOUS
143 // to skip past the large results.
146 WSAQUERYSET query_set
= {0};
147 query_set
.dwSize
= sizeof(WSAQUERYSET
);
148 query_set
.dwNameSpace
= NS_NLA
;
149 // Initiate a client query to iterate through the
150 // currently connected networks.
151 if (0 != WSALookupServiceBegin(&query_set
, LUP_RETURN_ALL
,
153 LOG(ERROR
) << "WSALookupServiceBegin failed with: " << WSAGetLastError();
154 return NetworkChangeNotifier::CONNECTION_UNKNOWN
;
157 bool found_connection
= false;
159 // Retrieve the first available network. In this function, we only
160 // need to know whether or not there is network connection.
161 // Allocate 256 bytes for name, it should be enough for most cases.
162 // If the name is longer, it is OK as we will check the code returned and
163 // set correct network status.
164 char result_buffer
[sizeof(WSAQUERYSET
) + 256] = {0};
165 DWORD length
= sizeof(result_buffer
);
166 reinterpret_cast<WSAQUERYSET
*>(&result_buffer
[0])->dwSize
=
168 int result
= WSALookupServiceNext(
172 reinterpret_cast<WSAQUERYSET
*>(&result_buffer
[0]));
175 // Found a connection!
176 found_connection
= true;
178 DCHECK_EQ(SOCKET_ERROR
, result
);
179 result
= WSAGetLastError();
181 // Error code WSAEFAULT means there is a network connection but the
182 // result_buffer size is too small to contain the results. The
183 // variable "length" returned from WSALookupServiceNext is the minimum
184 // number of bytes required. We do not need to retrieve detail info,
185 // it is enough knowing there was a connection.
186 if (result
== WSAEFAULT
) {
187 found_connection
= true;
188 } else if (result
== WSA_E_NO_MORE
|| result
== WSAENOMORE
) {
189 // There was nothing to iterate over!
191 LOG(WARNING
) << "WSALookupServiceNext() failed with:" << result
;
195 result
= WSALookupServiceEnd(ws_handle
);
196 LOG_IF(ERROR
, result
!= 0)
197 << "WSALookupServiceEnd() failed with: " << result
;
199 // TODO(droger): Return something more detailed than CONNECTION_UNKNOWN.
200 return found_connection
? NetworkChangeNotifier::CONNECTION_UNKNOWN
:
201 NetworkChangeNotifier::CONNECTION_NONE
;
204 NetworkChangeNotifier::ConnectionType
205 NetworkChangeNotifierWin::GetCurrentConnectionType() const {
206 base::AutoLock
auto_lock(last_computed_connection_type_lock_
);
207 return last_computed_connection_type_
;
210 void NetworkChangeNotifierWin::SetCurrentConnectionType(
211 ConnectionType connection_type
) {
212 base::AutoLock
auto_lock(last_computed_connection_type_lock_
);
213 last_computed_connection_type_
= connection_type
;
216 void NetworkChangeNotifierWin::OnObjectSignaled(HANDLE object
) {
217 DCHECK(CalledOnValidThread());
218 DCHECK(is_watching_
);
219 is_watching_
= false;
221 // Start watching for the next address change.
222 WatchForAddressChange();
227 void NetworkChangeNotifierWin::NotifyObservers() {
228 DCHECK(CalledOnValidThread());
229 SetCurrentConnectionType(RecomputeCurrentConnectionType());
230 NotifyObserversOfIPAddressChange();
232 // Calling GetConnectionType() at this very moment is likely to give
233 // the wrong result, so we delay that until a little bit later.
235 // The one second delay chosen here was determined experimentally
236 // by adamk on Windows 7.
237 // If after one second we determine we are still offline, we will
240 timer_
.Start(FROM_HERE
, base::TimeDelta::FromSeconds(1), this,
241 &NetworkChangeNotifierWin::NotifyParentOfConnectionTypeChange
);
244 void NetworkChangeNotifierWin::WatchForAddressChange() {
245 DCHECK(CalledOnValidThread());
246 DCHECK(!is_watching_
);
248 // NotifyAddrChange occasionally fails with ERROR_OPEN_FAILED for unknown
249 // reasons. More rarely, it's also been observed failing with
250 // ERROR_NO_SYSTEM_RESOURCES. When either of these happens, we retry later.
251 if (!WatchForAddressChangeInternal()) {
252 ++sequential_failures_
;
254 // TODO(mmenke): If the UMA histograms indicate that this fixes
255 // http://crbug.com/69198, remove this histogram and consider reducing the
257 if (sequential_failures_
== 2000) {
258 UMA_HISTOGRAM_COUNTS_10000("Net.NotifyAddrChangeFailures",
259 sequential_failures_
);
262 base::MessageLoop::current()->PostDelayedTask(
264 base::Bind(&NetworkChangeNotifierWin::WatchForAddressChange
,
265 weak_factory_
.GetWeakPtr()),
266 base::TimeDelta::FromMilliseconds(
267 kWatchForAddressChangeRetryIntervalMs
));
271 // Treat the transition from NotifyAddrChange failing to succeeding as a
272 // network change event, since network changes were not being observed in
274 if (sequential_failures_
> 0)
277 if (sequential_failures_
< 2000) {
278 UMA_HISTOGRAM_COUNTS_10000("Net.NotifyAddrChangeFailures",
279 sequential_failures_
);
283 sequential_failures_
= 0;
286 bool NetworkChangeNotifierWin::WatchForAddressChangeInternal() {
287 DCHECK(CalledOnValidThread());
289 if (!dns_config_service_thread_
->IsRunning()) {
290 dns_config_service_thread_
->StartWithOptions(
291 base::Thread::Options(base::MessageLoop::TYPE_IO
, 0));
294 HANDLE handle
= NULL
;
295 DWORD ret
= NotifyAddrChange(&handle
, &addr_overlapped_
);
296 if (ret
!= ERROR_IO_PENDING
)
299 addr_watcher_
.StartWatching(addr_overlapped_
.hEvent
, this);
303 void NetworkChangeNotifierWin::NotifyParentOfConnectionTypeChange() {
304 SetCurrentConnectionType(RecomputeCurrentConnectionType());
305 bool current_offline
= IsOffline();
307 // If we continue to appear offline, delay sending out the notification in
308 // case we appear to go online within 20 seconds. UMA histogram data shows
309 // we may not detect the transition to online state after 1 second but within
310 // 20 seconds we generally do.
311 if (last_announced_offline_
&& current_offline
&& offline_polls_
<= 20) {
312 timer_
.Start(FROM_HERE
, base::TimeDelta::FromSeconds(1), this,
313 &NetworkChangeNotifierWin::NotifyParentOfConnectionTypeChange
);
316 if (last_announced_offline_
)
317 UMA_HISTOGRAM_CUSTOM_COUNTS("NCN.OfflinePolls", offline_polls_
, 1, 50, 50);
318 last_announced_offline_
= current_offline
;
320 NotifyObserversOfConnectionTypeChange();