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