Home | History | Annotate | Download | only in base
      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 
     18 NetworkChangeNotifierWin::NetworkChangeNotifierWin() {
     19   memset(&addr_overlapped_, 0, sizeof addr_overlapped_);
     20   addr_overlapped_.hEvent = WSACreateEvent();
     21   WatchForAddressChange();
     22 }
     23 
     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 //
     76 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 
    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 
    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 
    170 void NetworkChangeNotifierWin::NotifyParentOfOnlineStateChange() {
    171   NotifyObserversOfOnlineStateChange();
    172 }
    173 
    174 }  // namespace net
    175