Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(93)

Side by Side Diff: net/base/network_change_notifier_win.cc

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

Powered by Google App Engine
This is Rietveld 408576698