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