1 // Copyright (c) 2010 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_win.h"
6
7 #include <iphlpapi.h>
8 #include <winsock2.h>
9
10 #include "base/logging.h"
11 #include "base/time.h"
12 #include "net/base/winsock_init.h"
13
14 #pragma comment(lib, "iphlpapi.lib")
15
16 namespace net {
17
NetworkChangeNotifierWin()18 NetworkChangeNotifierWin::NetworkChangeNotifierWin() {
19 memset(&addr_overlapped_, 0, sizeof addr_overlapped_);
20 addr_overlapped_.hEvent = WSACreateEvent();
21 WatchForAddressChange();
22 }
23
~NetworkChangeNotifierWin()24 NetworkChangeNotifierWin::~NetworkChangeNotifierWin() {
25 CancelIPChangeNotify(&addr_overlapped_);
26 addr_watcher_.StopWatching();
27 WSACloseEvent(addr_overlapped_.hEvent);
28 }
29
30 // Conceptually we would like to tell whether the user is "online" verus
31 // "offline". This is challenging since the only thing we can test with
32 // certainty is whether a *particular* host is reachable.
33 //
34 // While we can't conclusively determine when a user is "online", we can at
35 // least reliably recognize some of the situtations when they are clearly
36 // "offline". For example, if the user's laptop is not plugged into an ethernet
37 // network and is not connected to any wireless networks, it must be offline.
38 //
39 // There are a number of different ways to implement this on Windows, each with
40 // their pros and cons. Here is a comparison of various techniques considered:
41 //
42 // (1) Use InternetGetConnectedState (wininet.dll). This function is really easy
43 // to use (literally a one-liner), and runs quickly. The drawback is it adds a
44 // dependency on the wininet DLL.
45 //
46 // (2) Enumerate all of the network interfaces using GetAdaptersAddresses
47 // (iphlpapi.dll), and assume we are "online" if there is at least one interface
48 // that is connected, and that interface is not a loopback or tunnel.
49 //
50 // Safari on Windows has a fairly simple implementation that does this:
51 // http://trac.webkit.org/browser/trunk/WebCore/platform/network/win/NetworkStateNotifierWin.cpp.
52 //
53 // Mozilla similarly uses this approach:
54 // http://mxr.mozilla.org/mozilla1.9.2/source/netwerk/system/win32/nsNotifyAddrListener.cpp
55 //
56 // The biggest drawback to this approach is it is quite complicated.
57 // WebKit's implementation for example doesn't seem to test for ICS gateways
58 // (internet connection sharing), whereas Mozilla's implementation has extra
59 // code to guess that.
60 //
61 // (3) The method used in this file comes from google talk, and is similar to
62 // method (2). The main difference is it enumerates the winsock namespace
63 // providers rather than the actual adapters.
64 //
65 // I ran some benchmarks comparing the performance of each on my Windows 7
66 // workstation. Here is what I found:
67 // * Approach (1) was pretty much zero-cost after the initial call.
68 // * Approach (2) took an average of 3.25 milliseconds to enumerate the
69 // adapters.
70 // * Approach (3) took an average of 0.8 ms to enumerate the providers.
71 //
72 // In terms of correctness, all three approaches were comparable for the simple
73 // experiments I ran... However none of them correctly returned "offline" when
74 // executing 'ipconfig /release'.
75 //
IsCurrentlyOffline() const76 bool NetworkChangeNotifierWin::IsCurrentlyOffline() const {
77
78 // TODO(eroman): We could cache this value, and only re-calculate it on
79 // network changes. For now we recompute it each time asked,
80 // since it is relatively fast (sub 1ms) and not called often.
81
82 EnsureWinsockInit();
83
84 // The following code was adapted from:
85 // http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/net/notifier/base/win/async_network_alive_win32.cc?view=markup&pathrev=47343
86 // The main difference is we only call WSALookupServiceNext once, whereas
87 // the earlier code would traverse the entire list and pass LUP_FLUSHPREVIOUS
88 // to skip past the large results.
89
90 HANDLE ws_handle;
91 WSAQUERYSET query_set = {0};
92 query_set.dwSize = sizeof(WSAQUERYSET);
93 query_set.dwNameSpace = NS_NLA;
94 // Initiate a client query to iterate through the
95 // currently connected networks.
96 if (0 != WSALookupServiceBegin(&query_set, LUP_RETURN_ALL,
97 &ws_handle)) {
98 LOG(ERROR) << "WSALookupServiceBegin failed with: " << WSAGetLastError();
99 return false;
100 }
101
102 bool found_connection = false;
103
104 // Retrieve the first available network. In this function, we only
105 // need to know whether or not there is network connection.
106 // Allocate 256 bytes for name, it should be enough for most cases.
107 // If the name is longer, it is OK as we will check the code returned and
108 // set correct network status.
109 char result_buffer[sizeof(WSAQUERYSET) + 256] = {0};
110 DWORD length = sizeof(result_buffer);
111 reinterpret_cast<WSAQUERYSET*>(&result_buffer[0])->dwSize =
112 sizeof(WSAQUERYSET);
113 int result = WSALookupServiceNext(
114 ws_handle,
115 LUP_RETURN_NAME,
116 &length,
117 reinterpret_cast<WSAQUERYSET*>(&result_buffer[0]));
118
119 if (result == 0) {
120 // Found a connection!
121 found_connection = true;
122 } else {
123 DCHECK_EQ(SOCKET_ERROR, result);
124 result = WSAGetLastError();
125
126 // Error code WSAEFAULT means there is a network connection but the
127 // result_buffer size is too small to contain the results. The
128 // variable "length" returned from WSALookupServiceNext is the minimum
129 // number of bytes required. We do not need to retrieve detail info,
130 // it is enough knowing there was a connection.
131 if (result == WSAEFAULT) {
132 found_connection = true;
133 } else if (result == WSA_E_NO_MORE || result == WSAENOMORE) {
134 // There was nothing to iterate over!
135 } else {
136 LOG(WARNING) << "WSALookupServiceNext() failed with:" << result;
137 }
138 }
139
140 result = WSALookupServiceEnd(ws_handle);
141 LOG_IF(ERROR, result != 0)
142 << "WSALookupServiceEnd() failed with: " << result;
143
144 return !found_connection;
145 }
146
OnObjectSignaled(HANDLE object)147 void NetworkChangeNotifierWin::OnObjectSignaled(HANDLE object) {
148 NotifyObserversOfIPAddressChange();
149
150 // Calling IsOffline() at this very moment is likely to give
151 // the wrong result, so we delay that until a little bit later.
152 //
153 // The one second delay chosen here was determined experimentally
154 // by adamk on Windows 7.
155 timer_.Stop(); // cancel any already waiting notification
156 timer_.Start(base::TimeDelta::FromSeconds(1), this,
157 &NetworkChangeNotifierWin::NotifyParentOfOnlineStateChange);
158
159 // Start watching for the next address change.
160 WatchForAddressChange();
161 }
162
WatchForAddressChange()163 void NetworkChangeNotifierWin::WatchForAddressChange() {
164 HANDLE handle = NULL;
165 DWORD ret = NotifyAddrChange(&handle, &addr_overlapped_);
166 CHECK(ret == ERROR_IO_PENDING);
167 addr_watcher_.StartWatching(addr_overlapped_.hEvent, this);
168 }
169
NotifyParentOfOnlineStateChange()170 void NetworkChangeNotifierWin::NotifyParentOfOnlineStateChange() {
171 NotifyObserversOfOnlineStateChange();
172 }
173
174 } // namespace net
175