• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 The Chromium Authors
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 <utility>
11 
12 #include "base/feature_list.h"
13 #include "base/functional/bind.h"
14 #include "base/location.h"
15 #include "base/logging.h"
16 #include "base/metrics/histogram_macros.h"
17 #include "base/task/sequenced_task_runner.h"
18 #include "base/task/single_thread_task_runner.h"
19 #include "base/task/task_traits.h"
20 #include "base/task/thread_pool.h"
21 #include "base/threading/thread.h"
22 #include "base/time/time.h"
23 #include "base/win/windows_version.h"
24 #include "net/base/features.h"
25 #include "net/base/winsock_init.h"
26 #include "net/base/winsock_util.h"
27 
28 namespace net {
29 
30 namespace {
31 
32 // Time between NotifyAddrChange retries, on failure.
33 const int kWatchForAddressChangeRetryIntervalMs = 500;
34 
GetConnectionPoints(IUnknown * manager,REFIID IIDSyncInterface,IConnectionPoint ** connection_point_raw)35 HRESULT GetConnectionPoints(IUnknown* manager,
36                             REFIID IIDSyncInterface,
37                             IConnectionPoint** connection_point_raw) {
38   *connection_point_raw = nullptr;
39   Microsoft::WRL::ComPtr<IConnectionPointContainer> connection_point_container;
40   HRESULT hr =
41       manager->QueryInterface(IID_PPV_ARGS(&connection_point_container));
42   if (FAILED(hr))
43     return hr;
44 
45   // Find the interface
46   Microsoft::WRL::ComPtr<IConnectionPoint> connection_point;
47   hr = connection_point_container->FindConnectionPoint(IIDSyncInterface,
48                                                        &connection_point);
49   if (FAILED(hr))
50     return hr;
51 
52   *connection_point_raw = connection_point.Get();
53   (*connection_point_raw)->AddRef();
54 
55   return hr;
56 }
57 
58 }  // namespace
59 
60 // This class is used as an event sink to register for notifications from the
61 // INetworkCostManagerEvents interface. In particular, we are focused on getting
62 // notified when the Connection Cost changes. This is only supported on Win10+.
63 class NetworkCostManagerEventSink
64     : public Microsoft::WRL::RuntimeClass<
65           Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
66           INetworkCostManagerEvents> {
67  public:
68   using CostChangedCallback = base::RepeatingCallback<void()>;
69 
NetworkCostManagerEventSink(INetworkCostManager * cost_manager,const CostChangedCallback & callback)70   NetworkCostManagerEventSink(INetworkCostManager* cost_manager,
71                               const CostChangedCallback& callback)
72       : network_cost_manager_(cost_manager), cost_changed_callback_(callback) {}
73   ~NetworkCostManagerEventSink() override = default;
74 
75   // INetworkCostManagerEvents members
CostChanged(_In_ DWORD cost,_In_opt_ NLM_SOCKADDR *)76   IFACEMETHODIMP CostChanged(_In_ DWORD cost,
77                              _In_opt_ NLM_SOCKADDR* /*pSockAddr*/) override {
78     cost_changed_callback_.Run();
79     return S_OK;
80   }
81 
DataPlanStatusChanged(_In_opt_ NLM_SOCKADDR *)82   IFACEMETHODIMP DataPlanStatusChanged(
83       _In_opt_ NLM_SOCKADDR* /*pSockAddr*/) override {
84     return S_OK;
85   }
86 
RegisterForNotifications()87   HRESULT RegisterForNotifications() {
88     Microsoft::WRL::ComPtr<IUnknown> unknown;
89     HRESULT hr = QueryInterface(IID_PPV_ARGS(&unknown));
90     if (hr != S_OK)
91       return hr;
92 
93     hr = GetConnectionPoints(network_cost_manager_.Get(),
94                              IID_INetworkCostManagerEvents, &connection_point_);
95     if (hr != S_OK)
96       return hr;
97 
98     hr = connection_point_->Advise(unknown.Get(), &cookie_);
99     return hr;
100   }
101 
UnRegisterForNotifications()102   void UnRegisterForNotifications() {
103     if (connection_point_) {
104       connection_point_->Unadvise(cookie_);
105       connection_point_ = nullptr;
106       cookie_ = 0;
107     }
108   }
109 
110  private:
111   Microsoft::WRL::ComPtr<INetworkCostManager> network_cost_manager_;
112   Microsoft::WRL::ComPtr<IConnectionPoint> connection_point_;
113   DWORD cookie_ = 0;
114   CostChangedCallback cost_changed_callback_;
115 };
116 
NetworkChangeNotifierWin()117 NetworkChangeNotifierWin::NetworkChangeNotifierWin()
118     : NetworkChangeNotifier(NetworkChangeCalculatorParamsWin()),
119       blocking_task_runner_(
120           base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})),
121       last_computed_connection_type_(RecomputeCurrentConnectionType()),
122       last_announced_offline_(last_computed_connection_type_ ==
123                               CONNECTION_NONE),
124       sequence_runner_for_registration_(
125           base::SequencedTaskRunner::GetCurrentDefault()) {
126   memset(&addr_overlapped_, 0, sizeof addr_overlapped_);
127   addr_overlapped_.hEvent = WSACreateEvent();
128 }
129 
~NetworkChangeNotifierWin()130 NetworkChangeNotifierWin::~NetworkChangeNotifierWin() {
131   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
132   ClearGlobalPointer();
133   if (is_watching_) {
134     CancelIPChangeNotify(&addr_overlapped_);
135     addr_watcher_.StopWatching();
136   }
137   WSACloseEvent(addr_overlapped_.hEvent);
138 
139   if (network_cost_manager_event_sink_) {
140     network_cost_manager_event_sink_->UnRegisterForNotifications();
141     network_cost_manager_event_sink_ = nullptr;
142   }
143 }
144 
145 // static
146 NetworkChangeNotifier::NetworkChangeCalculatorParams
NetworkChangeCalculatorParamsWin()147 NetworkChangeNotifierWin::NetworkChangeCalculatorParamsWin() {
148   NetworkChangeCalculatorParams params;
149   // Delay values arrived at by simple experimentation and adjusted so as to
150   // produce a single signal when switching between network connections.
151   params.ip_address_offline_delay_ = base::Milliseconds(1500);
152   params.ip_address_online_delay_ = base::Milliseconds(1500);
153   params.connection_type_offline_delay_ = base::Milliseconds(1500);
154   params.connection_type_online_delay_ = base::Milliseconds(500);
155   return params;
156 }
157 
158 // static
159 NetworkChangeNotifier::ConnectionType
RecomputeCurrentConnectionTypeModern()160 NetworkChangeNotifierWin::RecomputeCurrentConnectionTypeModern() {
161   using GetNetworkConnectivityHintType =
162       decltype(&::GetNetworkConnectivityHint);
163 
164   // This API is only available on Windows 10 Build 19041. However, it works
165   // inside the Network Service Sandbox, so is preferred. See
166   GetNetworkConnectivityHintType get_network_connectivity_hint =
167       reinterpret_cast<GetNetworkConnectivityHintType>(::GetProcAddress(
168           ::GetModuleHandleA("iphlpapi.dll"), "GetNetworkConnectivityHint"));
169   if (!get_network_connectivity_hint) {
170     return NetworkChangeNotifier::CONNECTION_UNKNOWN;
171   }
172   NL_NETWORK_CONNECTIVITY_HINT hint;
173   // https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-getnetworkconnectivityhint.
174   auto ret = get_network_connectivity_hint(&hint);
175   if (ret != NO_ERROR) {
176     return NetworkChangeNotifier::CONNECTION_UNKNOWN;
177   }
178 
179   switch (hint.ConnectivityLevel) {
180     case NetworkConnectivityLevelHintUnknown:
181       return NetworkChangeNotifier::CONNECTION_UNKNOWN;
182     case NetworkConnectivityLevelHintNone:
183     case NetworkConnectivityLevelHintHidden:
184       return NetworkChangeNotifier::CONNECTION_NONE;
185     case NetworkConnectivityLevelHintLocalAccess:
186     case NetworkConnectivityLevelHintInternetAccess:
187     case NetworkConnectivityLevelHintConstrainedInternetAccess:
188       // TODO(droger): Return something more detailed than CONNECTION_UNKNOWN.
189       return ConnectionTypeFromInterfaces();
190   }
191 
192   NOTREACHED_NORETURN();
193 }
194 
195 // This implementation does not return the actual connection type but merely
196 // determines if the user is "online" (in which case it returns
197 // CONNECTION_UNKNOWN) or "offline" (and then it returns CONNECTION_NONE).
198 // This is challenging since the only thing we can test with certainty is
199 // whether a *particular* host is reachable.
200 //
201 // While we can't conclusively determine when a user is "online", we can at
202 // least reliably recognize some of the situtations when they are clearly
203 // "offline". For example, if the user's laptop is not plugged into an ethernet
204 // network and is not connected to any wireless networks, it must be offline.
205 //
206 // There are a number of different ways to implement this on Windows, each with
207 // their pros and cons. Here is a comparison of various techniques considered:
208 //
209 // (1) Use InternetGetConnectedState (wininet.dll). This function is really easy
210 // to use (literally a one-liner), and runs quickly. The drawback is it adds a
211 // dependency on the wininet DLL.
212 //
213 // (2) Enumerate all of the network interfaces using GetAdaptersAddresses
214 // (iphlpapi.dll), and assume we are "online" if there is at least one interface
215 // that is connected, and that interface is not a loopback or tunnel.
216 //
217 // Safari on Windows has a fairly simple implementation that does this:
218 // http://trac.webkit.org/browser/trunk/WebCore/platform/network/win/NetworkStateNotifierWin.cpp.
219 //
220 // Mozilla similarly uses this approach:
221 // http://mxr.mozilla.org/mozilla1.9.2/source/netwerk/system/win32/nsNotifyAddrListener.cpp
222 //
223 // The biggest drawback to this approach is it is quite complicated.
224 // WebKit's implementation for example doesn't seem to test for ICS gateways
225 // (internet connection sharing), whereas Mozilla's implementation has extra
226 // code to guess that.
227 //
228 // (3) The method used in this file comes from google talk, and is similar to
229 // method (2). The main difference is it enumerates the winsock namespace
230 // providers rather than the actual adapters.
231 //
232 // I ran some benchmarks comparing the performance of each on my Windows 7
233 // workstation. Here is what I found:
234 //   * Approach (1) was pretty much zero-cost after the initial call.
235 //   * Approach (2) took an average of 3.25 milliseconds to enumerate the
236 //     adapters.
237 //   * Approach (3) took an average of 0.8 ms to enumerate the providers.
238 //
239 // In terms of correctness, all three approaches were comparable for the simple
240 // experiments I ran... However none of them correctly returned "offline" when
241 // executing 'ipconfig /release'.
242 //
243 // static
244 NetworkChangeNotifier::ConnectionType
RecomputeCurrentConnectionType()245 NetworkChangeNotifierWin::RecomputeCurrentConnectionType() {
246   if (base::win::GetVersion() >= base::win::Version::WIN10_20H1 &&
247       base::FeatureList::IsEnabled(
248           features::kEnableGetNetworkConnectivityHintAPI)) {
249     return RecomputeCurrentConnectionTypeModern();
250   }
251 
252   EnsureWinsockInit();
253 
254   // The following code was adapted from:
255   // http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/net/notifier/base/win/async_network_alive_win32.cc?view=markup&pathrev=47343
256   // The main difference is we only call WSALookupServiceNext once, whereas
257   // the earlier code would traverse the entire list and pass LUP_FLUSHPREVIOUS
258   // to skip past the large results.
259 
260   HANDLE ws_handle;
261   WSAQUERYSET query_set = {0};
262   query_set.dwSize = sizeof(WSAQUERYSET);
263   query_set.dwNameSpace = NS_NLA;
264   // Initiate a client query to iterate through the
265   // currently connected networks.
266   if (0 != WSALookupServiceBegin(&query_set, LUP_RETURN_ALL, &ws_handle)) {
267     LOG(ERROR) << "WSALookupServiceBegin failed with: " << WSAGetLastError();
268     return NetworkChangeNotifier::CONNECTION_UNKNOWN;
269   }
270 
271   bool found_connection = false;
272 
273   // Retrieve the first available network. In this function, we only
274   // need to know whether or not there is network connection.
275   // Allocate 256 bytes for name, it should be enough for most cases.
276   // If the name is longer, it is OK as we will check the code returned and
277   // set correct network status.
278   char result_buffer[sizeof(WSAQUERYSET) + 256] = {0};
279   DWORD length = sizeof(result_buffer);
280   reinterpret_cast<WSAQUERYSET*>(&result_buffer[0])->dwSize =
281       sizeof(WSAQUERYSET);
282   int result =
283       WSALookupServiceNext(ws_handle, LUP_RETURN_NAME, &length,
284                            reinterpret_cast<WSAQUERYSET*>(&result_buffer[0]));
285 
286   if (result == 0) {
287     // Found a connection!
288     found_connection = true;
289   } else {
290     DCHECK_EQ(SOCKET_ERROR, result);
291     result = WSAGetLastError();
292 
293     // Error code WSAEFAULT means there is a network connection but the
294     // result_buffer size is too small to contain the results. The
295     // variable "length" returned from WSALookupServiceNext is the minimum
296     // number of bytes required. We do not need to retrieve detail info,
297     // it is enough knowing there was a connection.
298     if (result == WSAEFAULT) {
299       found_connection = true;
300     } else if (result == WSA_E_NO_MORE || result == WSAENOMORE) {
301       // There was nothing to iterate over!
302     } else {
303       LOG(WARNING) << "WSALookupServiceNext() failed with:" << result;
304     }
305   }
306 
307   result = WSALookupServiceEnd(ws_handle);
308   LOG_IF(ERROR, result != 0) << "WSALookupServiceEnd() failed with: " << result;
309 
310   // TODO(droger): Return something more detailed than CONNECTION_UNKNOWN.
311   return found_connection ? ConnectionTypeFromInterfaces()
312                           : NetworkChangeNotifier::CONNECTION_NONE;
313 }
314 
RecomputeCurrentConnectionTypeOnBlockingSequence(base::OnceCallback<void (ConnectionType)> reply_callback) const315 void NetworkChangeNotifierWin::RecomputeCurrentConnectionTypeOnBlockingSequence(
316     base::OnceCallback<void(ConnectionType)> reply_callback) const {
317   // Unretained is safe in this call because this object owns the thread and the
318   // thread is stopped in this object's destructor.
319   blocking_task_runner_->PostTaskAndReplyWithResult(
320       FROM_HERE,
321       base::BindOnce(&NetworkChangeNotifierWin::RecomputeCurrentConnectionType),
322       std::move(reply_callback));
323 }
324 
325 NetworkChangeNotifier::ConnectionCost
GetCurrentConnectionCost()326 NetworkChangeNotifierWin::GetCurrentConnectionCost() {
327   InitializeConnectionCost();
328 
329   // If we don't have the event sink we aren't registered for automatic updates.
330   // In that case, we need to update the value at the time it is requested.
331   if (!network_cost_manager_event_sink_)
332     UpdateConnectionCostFromCostManager();
333 
334   return last_computed_connection_cost_;
335 }
336 
InitializeConnectionCostOnce()337 bool NetworkChangeNotifierWin::InitializeConnectionCostOnce() {
338   HRESULT hr =
339       ::CoCreateInstance(CLSID_NetworkListManager, nullptr, CLSCTX_ALL,
340                          IID_INetworkCostManager, &network_cost_manager_);
341   if (FAILED(hr)) {
342     SetCurrentConnectionCost(CONNECTION_COST_UNKNOWN);
343     return true;
344   }
345 
346   UpdateConnectionCostFromCostManager();
347 
348   return true;
349 }
350 
InitializeConnectionCost()351 void NetworkChangeNotifierWin::InitializeConnectionCost() {
352   static bool g_connection_cost_initialized = InitializeConnectionCostOnce();
353   DCHECK(g_connection_cost_initialized);
354 }
355 
UpdateConnectionCostFromCostManager()356 HRESULT NetworkChangeNotifierWin::UpdateConnectionCostFromCostManager() {
357   if (!network_cost_manager_)
358     return E_ABORT;
359 
360   DWORD cost = NLM_CONNECTION_COST_UNKNOWN;
361   HRESULT hr = network_cost_manager_->GetCost(&cost, nullptr);
362   if (FAILED(hr)) {
363     SetCurrentConnectionCost(CONNECTION_COST_UNKNOWN);
364   } else {
365     SetCurrentConnectionCost(
366         ConnectionCostFromNlmCost((NLM_CONNECTION_COST)cost));
367   }
368   return hr;
369 }
370 
371 // static
372 NetworkChangeNotifier::ConnectionCost
ConnectionCostFromNlmCost(NLM_CONNECTION_COST cost)373 NetworkChangeNotifierWin::ConnectionCostFromNlmCost(NLM_CONNECTION_COST cost) {
374   if (cost == NLM_CONNECTION_COST_UNKNOWN)
375     return CONNECTION_COST_UNKNOWN;
376   else if ((cost & NLM_CONNECTION_COST_UNRESTRICTED) != 0)
377     return CONNECTION_COST_UNMETERED;
378   else
379     return CONNECTION_COST_METERED;
380 }
381 
SetCurrentConnectionCost(ConnectionCost connection_cost)382 void NetworkChangeNotifierWin::SetCurrentConnectionCost(
383     ConnectionCost connection_cost) {
384   last_computed_connection_cost_ = connection_cost;
385 }
386 
OnCostChanged()387 void NetworkChangeNotifierWin::OnCostChanged() {
388   ConnectionCost old_cost = last_computed_connection_cost_;
389   // It is possible to get multiple notifications in a short period of time.
390   // Rather than worrying about whether this notification represents the latest,
391   // just get the current value from the CostManager so we know that we're
392   // actually getting the correct value.
393   UpdateConnectionCostFromCostManager();
394   // Only notify if there's actually a change.
395   if (old_cost != GetCurrentConnectionCost())
396     NotifyObserversOfConnectionCostChange();
397 }
398 
ConnectionCostObserverAdded()399 void NetworkChangeNotifierWin::ConnectionCostObserverAdded() {
400   sequence_runner_for_registration_->PostTask(
401       FROM_HERE,
402       base::BindOnce(&NetworkChangeNotifierWin::OnConnectionCostObserverAdded,
403                      weak_factory_.GetWeakPtr()));
404 }
405 
OnConnectionCostObserverAdded()406 void NetworkChangeNotifierWin::OnConnectionCostObserverAdded() {
407   DCHECK(sequence_runner_for_registration_->RunsTasksInCurrentSequence());
408   InitializeConnectionCost();
409 
410   // No need to register if we don't have a cost manager or if we're already
411   // registered.
412   if (!network_cost_manager_ || network_cost_manager_event_sink_)
413     return;
414 
415   network_cost_manager_event_sink_ =
416       Microsoft::WRL::Make<net::NetworkCostManagerEventSink>(
417           network_cost_manager_.Get(),
418           base::BindRepeating(&NetworkChangeNotifierWin::OnCostChanged,
419                               weak_factory_.GetWeakPtr()));
420   HRESULT hr = network_cost_manager_event_sink_->RegisterForNotifications();
421   if (hr != S_OK) {
422     // If registration failed for any reason, just destroy the event sink. The
423     // observer will remain connected but will not receive any updates. If
424     // another observer gets added later, we can re-attempt registration.
425     network_cost_manager_event_sink_ = nullptr;
426   }
427 }
428 
429 NetworkChangeNotifier::ConnectionType
GetCurrentConnectionType() const430 NetworkChangeNotifierWin::GetCurrentConnectionType() const {
431   base::AutoLock auto_lock(last_computed_connection_type_lock_);
432   return last_computed_connection_type_;
433 }
434 
SetCurrentConnectionType(ConnectionType connection_type)435 void NetworkChangeNotifierWin::SetCurrentConnectionType(
436     ConnectionType connection_type) {
437   base::AutoLock auto_lock(last_computed_connection_type_lock_);
438   last_computed_connection_type_ = connection_type;
439 }
440 
OnObjectSignaled(HANDLE object)441 void NetworkChangeNotifierWin::OnObjectSignaled(HANDLE object) {
442   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
443   DCHECK(is_watching_);
444   is_watching_ = false;
445 
446   // Start watching for the next address change.
447   WatchForAddressChange();
448 
449   RecomputeCurrentConnectionTypeOnBlockingSequence(base::BindOnce(
450       &NetworkChangeNotifierWin::NotifyObservers, weak_factory_.GetWeakPtr()));
451 }
452 
NotifyObservers(ConnectionType connection_type)453 void NetworkChangeNotifierWin::NotifyObservers(ConnectionType connection_type) {
454   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
455   SetCurrentConnectionType(connection_type);
456   NotifyObserversOfIPAddressChange();
457 
458   // Calling GetConnectionType() at this very moment is likely to give
459   // the wrong result, so we delay that until a little bit later.
460   //
461   // The one second delay chosen here was determined experimentally
462   // by adamk on Windows 7.
463   // If after one second we determine we are still offline, we will
464   // delay again.
465   offline_polls_ = 0;
466   timer_.Start(FROM_HERE, base::Seconds(1), this,
467                &NetworkChangeNotifierWin::NotifyParentOfConnectionTypeChange);
468 }
469 
WatchForAddressChange()470 void NetworkChangeNotifierWin::WatchForAddressChange() {
471   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
472   DCHECK(!is_watching_);
473 
474   // NotifyAddrChange occasionally fails with ERROR_OPEN_FAILED for unknown
475   // reasons.  More rarely, it's also been observed failing with
476   // ERROR_NO_SYSTEM_RESOURCES.  When either of these happens, we retry later.
477   if (!WatchForAddressChangeInternal()) {
478     ++sequential_failures_;
479 
480     base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
481         FROM_HERE,
482         base::BindOnce(&NetworkChangeNotifierWin::WatchForAddressChange,
483                        weak_factory_.GetWeakPtr()),
484         base::Milliseconds(kWatchForAddressChangeRetryIntervalMs));
485     return;
486   }
487 
488   // Treat the transition from NotifyAddrChange failing to succeeding as a
489   // network change event, since network changes were not being observed in
490   // that interval.
491   if (sequential_failures_ > 0) {
492     RecomputeCurrentConnectionTypeOnBlockingSequence(
493         base::BindOnce(&NetworkChangeNotifierWin::NotifyObservers,
494                        weak_factory_.GetWeakPtr()));
495   }
496 
497   is_watching_ = true;
498   sequential_failures_ = 0;
499 }
500 
WatchForAddressChangeInternal()501 bool NetworkChangeNotifierWin::WatchForAddressChangeInternal() {
502   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
503 
504   ResetEventIfSignaled(addr_overlapped_.hEvent);
505   HANDLE handle = nullptr;
506   DWORD ret = NotifyAddrChange(&handle, &addr_overlapped_);
507   if (ret != ERROR_IO_PENDING)
508     return false;
509 
510   addr_watcher_.StartWatchingOnce(addr_overlapped_.hEvent, this);
511   return true;
512 }
513 
NotifyParentOfConnectionTypeChange()514 void NetworkChangeNotifierWin::NotifyParentOfConnectionTypeChange() {
515   RecomputeCurrentConnectionTypeOnBlockingSequence(base::BindOnce(
516       &NetworkChangeNotifierWin::NotifyParentOfConnectionTypeChangeImpl,
517       weak_factory_.GetWeakPtr()));
518 }
519 
NotifyParentOfConnectionTypeChangeImpl(ConnectionType connection_type)520 void NetworkChangeNotifierWin::NotifyParentOfConnectionTypeChangeImpl(
521     ConnectionType connection_type) {
522   SetCurrentConnectionType(connection_type);
523   bool current_offline = IsOffline();
524   offline_polls_++;
525   // If we continue to appear offline, delay sending out the notification in
526   // case we appear to go online within 20 seconds.  UMA histogram data shows
527   // we may not detect the transition to online state after 1 second but within
528   // 20 seconds we generally do.
529   if (last_announced_offline_ && current_offline && offline_polls_ <= 20) {
530     timer_.Start(FROM_HERE, base::Seconds(1), this,
531                  &NetworkChangeNotifierWin::NotifyParentOfConnectionTypeChange);
532     return;
533   }
534   if (last_announced_offline_)
535     UMA_HISTOGRAM_CUSTOM_COUNTS("NCN.OfflinePolls", offline_polls_, 1, 50, 50);
536   last_announced_offline_ = current_offline;
537 
538   NotifyObserversOfConnectionTypeChange();
539   double max_bandwidth_mbps = 0.0;
540   ConnectionType max_connection_type = CONNECTION_NONE;
541   GetCurrentMaxBandwidthAndConnectionType(&max_bandwidth_mbps,
542                                           &max_connection_type);
543   NotifyObserversOfMaxBandwidthChange(max_bandwidth_mbps, max_connection_type);
544 }
545 
546 }  // namespace net
547