Probably broke Win7 Tests (dbg)(6). http://build.chromium.org/p/chromium.win/builders...
[chromium-blink-merge.git] / net / base / network_change_notifier_win.cc
blob77a72b0d6934150162ff5b4516c4ac6b8c7f3266
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"
7 #include <iphlpapi.h>
8 #include <winsock2.h>
10 #include "base/bind.h"
11 #include "base/logging.h"
12 #include "base/metrics/histogram.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")
20 namespace net {
22 namespace {
24 // Time between NotifyAddrChange retries, on failure.
25 const int kWatchForAddressChangeRetryIntervalMs = 500;
27 } // namespace
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 {
32 public:
33 DnsConfigServiceThread() : base::Thread("DnsConfigService") {}
35 virtual ~DnsConfigServiceThread() {
36 Stop();
39 virtual void Init() OVERRIDE {
40 service_ = DnsConfigService::CreateSystemService();
41 service_->WatchConfig(base::Bind(&NetworkChangeNotifier::SetDnsConfig));
44 virtual void CleanUp() OVERRIDE {
45 service_.reset();
48 private:
49 scoped_ptr<DnsConfigService> service_;
51 DISALLOW_COPY_AND_ASSIGN(DnsConfigServiceThread);
54 NetworkChangeNotifierWin::NetworkChangeNotifierWin()
55 : NetworkChangeNotifier(NetworkChangeCalculatorParamsWin()),
56 is_watching_(false),
57 sequential_failures_(0),
58 weak_factory_(this),
59 dns_config_service_thread_(new DnsConfigServiceThread()),
60 last_computed_connection_type_(RecomputeCurrentConnectionType()),
61 last_announced_offline_(
62 last_computed_connection_type_ == CONNECTION_NONE) {
63 memset(&addr_overlapped_, 0, sizeof addr_overlapped_);
64 addr_overlapped_.hEvent = WSACreateEvent();
67 NetworkChangeNotifierWin::~NetworkChangeNotifierWin() {
68 if (is_watching_) {
69 CancelIPChangeNotify(&addr_overlapped_);
70 addr_watcher_.StopWatching();
72 WSACloseEvent(addr_overlapped_.hEvent);
75 // static
76 NetworkChangeNotifier::NetworkChangeCalculatorParams
77 NetworkChangeNotifierWin::NetworkChangeCalculatorParamsWin() {
78 NetworkChangeCalculatorParams params;
79 // Delay values arrived at by simple experimentation and adjusted so as to
80 // produce a single signal when switching between network connections.
81 params.ip_address_offline_delay_ = base::TimeDelta::FromMilliseconds(1500);
82 params.ip_address_online_delay_ = base::TimeDelta::FromMilliseconds(1500);
83 params.connection_type_offline_delay_ =
84 base::TimeDelta::FromMilliseconds(1500);
85 params.connection_type_online_delay_ = base::TimeDelta::FromMilliseconds(500);
86 return params;
89 // This implementation does not return the actual connection type but merely
90 // determines if the user is "online" (in which case it returns
91 // CONNECTION_UNKNOWN) or "offline" (and then it returns CONNECTION_NONE).
92 // This is challenging since the only thing we can test with certainty is
93 // whether a *particular* host is reachable.
95 // While we can't conclusively determine when a user is "online", we can at
96 // least reliably recognize some of the situtations when they are clearly
97 // "offline". For example, if the user's laptop is not plugged into an ethernet
98 // network and is not connected to any wireless networks, it must be offline.
100 // There are a number of different ways to implement this on Windows, each with
101 // their pros and cons. Here is a comparison of various techniques considered:
103 // (1) Use InternetGetConnectedState (wininet.dll). This function is really easy
104 // to use (literally a one-liner), and runs quickly. The drawback is it adds a
105 // dependency on the wininet DLL.
107 // (2) Enumerate all of the network interfaces using GetAdaptersAddresses
108 // (iphlpapi.dll), and assume we are "online" if there is at least one interface
109 // that is connected, and that interface is not a loopback or tunnel.
111 // Safari on Windows has a fairly simple implementation that does this:
112 // http://trac.webkit.org/browser/trunk/WebCore/platform/network/win/NetworkStateNotifierWin.cpp.
114 // Mozilla similarly uses this approach:
115 // http://mxr.mozilla.org/mozilla1.9.2/source/netwerk/system/win32/nsNotifyAddrListener.cpp
117 // The biggest drawback to this approach is it is quite complicated.
118 // WebKit's implementation for example doesn't seem to test for ICS gateways
119 // (internet connection sharing), whereas Mozilla's implementation has extra
120 // code to guess that.
122 // (3) The method used in this file comes from google talk, and is similar to
123 // method (2). The main difference is it enumerates the winsock namespace
124 // providers rather than the actual adapters.
126 // I ran some benchmarks comparing the performance of each on my Windows 7
127 // workstation. Here is what I found:
128 // * Approach (1) was pretty much zero-cost after the initial call.
129 // * Approach (2) took an average of 3.25 milliseconds to enumerate the
130 // adapters.
131 // * Approach (3) took an average of 0.8 ms to enumerate the providers.
133 // In terms of correctness, all three approaches were comparable for the simple
134 // experiments I ran... However none of them correctly returned "offline" when
135 // executing 'ipconfig /release'.
137 NetworkChangeNotifier::ConnectionType
138 NetworkChangeNotifierWin::RecomputeCurrentConnectionType() const {
139 DCHECK(CalledOnValidThread());
141 EnsureWinsockInit();
143 // The following code was adapted from:
144 // http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/net/notifier/base/win/async_network_alive_win32.cc?view=markup&pathrev=47343
145 // The main difference is we only call WSALookupServiceNext once, whereas
146 // the earlier code would traverse the entire list and pass LUP_FLUSHPREVIOUS
147 // to skip past the large results.
149 HANDLE ws_handle;
150 WSAQUERYSET query_set = {0};
151 query_set.dwSize = sizeof(WSAQUERYSET);
152 query_set.dwNameSpace = NS_NLA;
153 // Initiate a client query to iterate through the
154 // currently connected networks.
155 if (0 != WSALookupServiceBegin(&query_set, LUP_RETURN_ALL,
156 &ws_handle)) {
157 LOG(ERROR) << "WSALookupServiceBegin failed with: " << WSAGetLastError();
158 return NetworkChangeNotifier::CONNECTION_UNKNOWN;
161 bool found_connection = false;
163 // Retrieve the first available network. In this function, we only
164 // need to know whether or not there is network connection.
165 // Allocate 256 bytes for name, it should be enough for most cases.
166 // If the name is longer, it is OK as we will check the code returned and
167 // set correct network status.
168 char result_buffer[sizeof(WSAQUERYSET) + 256] = {0};
169 DWORD length = sizeof(result_buffer);
170 reinterpret_cast<WSAQUERYSET*>(&result_buffer[0])->dwSize =
171 sizeof(WSAQUERYSET);
172 int result = WSALookupServiceNext(
173 ws_handle,
174 LUP_RETURN_NAME,
175 &length,
176 reinterpret_cast<WSAQUERYSET*>(&result_buffer[0]));
178 if (result == 0) {
179 // Found a connection!
180 found_connection = true;
181 } else {
182 DCHECK_EQ(SOCKET_ERROR, result);
183 result = WSAGetLastError();
185 // Error code WSAEFAULT means there is a network connection but the
186 // result_buffer size is too small to contain the results. The
187 // variable "length" returned from WSALookupServiceNext is the minimum
188 // number of bytes required. We do not need to retrieve detail info,
189 // it is enough knowing there was a connection.
190 if (result == WSAEFAULT) {
191 found_connection = true;
192 } else if (result == WSA_E_NO_MORE || result == WSAENOMORE) {
193 // There was nothing to iterate over!
194 } else {
195 LOG(WARNING) << "WSALookupServiceNext() failed with:" << result;
199 result = WSALookupServiceEnd(ws_handle);
200 LOG_IF(ERROR, result != 0)
201 << "WSALookupServiceEnd() failed with: " << result;
203 // TODO(droger): Return something more detailed than CONNECTION_UNKNOWN.
204 return found_connection ? NetworkChangeNotifier::CONNECTION_UNKNOWN :
205 NetworkChangeNotifier::CONNECTION_NONE;
208 NetworkChangeNotifier::ConnectionType
209 NetworkChangeNotifierWin::GetCurrentConnectionType() const {
210 base::AutoLock auto_lock(last_computed_connection_type_lock_);
211 return last_computed_connection_type_;
214 void NetworkChangeNotifierWin::SetCurrentConnectionType(
215 ConnectionType connection_type) {
216 base::AutoLock auto_lock(last_computed_connection_type_lock_);
217 last_computed_connection_type_ = connection_type;
220 void NetworkChangeNotifierWin::OnObjectSignaled(HANDLE object) {
221 DCHECK(CalledOnValidThread());
222 DCHECK(is_watching_);
223 is_watching_ = false;
225 // Start watching for the next address change.
226 WatchForAddressChange();
228 NotifyObservers();
231 void NetworkChangeNotifierWin::NotifyObservers() {
232 DCHECK(CalledOnValidThread());
233 SetCurrentConnectionType(RecomputeCurrentConnectionType());
234 NotifyObserversOfIPAddressChange();
236 // Calling GetConnectionType() at this very moment is likely to give
237 // the wrong result, so we delay that until a little bit later.
239 // The one second delay chosen here was determined experimentally
240 // by adamk on Windows 7.
241 // If after one second we determine we are still offline, we will
242 // delay again.
243 offline_polls_ = 0;
244 timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(1), this,
245 &NetworkChangeNotifierWin::NotifyParentOfConnectionTypeChange);
248 void NetworkChangeNotifierWin::WatchForAddressChange() {
249 DCHECK(CalledOnValidThread());
250 DCHECK(!is_watching_);
252 // NotifyAddrChange occasionally fails with ERROR_OPEN_FAILED for unknown
253 // reasons. More rarely, it's also been observed failing with
254 // ERROR_NO_SYSTEM_RESOURCES. When either of these happens, we retry later.
255 if (!WatchForAddressChangeInternal()) {
256 ++sequential_failures_;
258 // TODO(mmenke): If the UMA histograms indicate that this fixes
259 // http://crbug.com/69198, remove this histogram and consider reducing the
260 // retry interval.
261 if (sequential_failures_ == 2000) {
262 UMA_HISTOGRAM_COUNTS_10000("Net.NotifyAddrChangeFailures",
263 sequential_failures_);
266 base::MessageLoop::current()->PostDelayedTask(
267 FROM_HERE,
268 base::Bind(&NetworkChangeNotifierWin::WatchForAddressChange,
269 weak_factory_.GetWeakPtr()),
270 base::TimeDelta::FromMilliseconds(
271 kWatchForAddressChangeRetryIntervalMs));
272 return;
275 // Treat the transition from NotifyAddrChange failing to succeeding as a
276 // network change event, since network changes were not being observed in
277 // that interval.
278 if (sequential_failures_ > 0)
279 NotifyObservers();
281 if (sequential_failures_ < 2000) {
282 UMA_HISTOGRAM_COUNTS_10000("Net.NotifyAddrChangeFailures",
283 sequential_failures_);
286 is_watching_ = true;
287 sequential_failures_ = 0;
290 bool NetworkChangeNotifierWin::WatchForAddressChangeInternal() {
291 DCHECK(CalledOnValidThread());
293 if (!dns_config_service_thread_->IsRunning()) {
294 dns_config_service_thread_->StartWithOptions(
295 base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
298 HANDLE handle = NULL;
299 DWORD ret = NotifyAddrChange(&handle, &addr_overlapped_);
300 if (ret != ERROR_IO_PENDING)
301 return false;
303 addr_watcher_.StartWatching(addr_overlapped_.hEvent, this);
304 return true;
307 void NetworkChangeNotifierWin::NotifyParentOfConnectionTypeChange() {
308 SetCurrentConnectionType(RecomputeCurrentConnectionType());
309 bool current_offline = IsOffline();
310 offline_polls_++;
311 // If we continue to appear offline, delay sending out the notification in
312 // case we appear to go online within 20 seconds. UMA histogram data shows
313 // we may not detect the transition to online state after 1 second but within
314 // 20 seconds we generally do.
315 if (last_announced_offline_ && current_offline && offline_polls_ <= 20) {
316 timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(1), this,
317 &NetworkChangeNotifierWin::NotifyParentOfConnectionTypeChange);
318 return;
320 if (last_announced_offline_)
321 UMA_HISTOGRAM_CUSTOM_COUNTS("NCN.OfflinePolls", offline_polls_, 1, 50, 50);
322 last_announced_offline_ = current_offline;
324 NotifyObserversOfConnectionTypeChange();
327 } // namespace net