diff options
author | eroman@chromium.org <eroman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-09 00:35:47 +0000 |
---|---|---|
committer | eroman@chromium.org <eroman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-09 00:35:47 +0000 |
commit | 2d3b7766506922d50aa2585c1a596350d57ad238 (patch) | |
tree | cb85915944eba0f47577c660682b4868780e816c /net/base | |
parent | ff9cebb3f64df1c0c779a8a60daee57da7f9b667 (diff) | |
download | chromium_src-2d3b7766506922d50aa2585c1a596350d57ad238.zip chromium_src-2d3b7766506922d50aa2585c1a596350d57ad238.tar.gz chromium_src-2d3b7766506922d50aa2585c1a596350d57ad238.tar.bz2 |
Return ERR_INTERNET_DISCONNECTED in place of ERR_NAME_NOT_RESOLVED and ERR_ADDRESS_UNREACHABLE, when the user is in offline mode.
This initial changelist includes the implementation just for Windows.
BUG=53473
TEST=unplug network cable, try to connect to www.google.com --> should get the error ERR_INTERNET_DISCONNECTED. Next, try connecting directly to an IP address --> should get the error ERR_INTERNET_DISCONNECTED.
Review URL: http://codereview.chromium.org/3634002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@62050 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/base')
-rw-r--r-- | net/base/host_resolver_impl.cc | 6 | ||||
-rw-r--r-- | net/base/network_change_notifier.cc | 16 | ||||
-rw-r--r-- | net/base/network_change_notifier.h | 20 | ||||
-rw-r--r-- | net/base/network_change_notifier_linux.cc | 5 | ||||
-rw-r--r-- | net/base/network_change_notifier_linux.h | 3 | ||||
-rw-r--r-- | net/base/network_change_notifier_mac.cc | 5 | ||||
-rw-r--r-- | net/base/network_change_notifier_mac.h | 3 | ||||
-rw-r--r-- | net/base/network_change_notifier_win.cc | 120 | ||||
-rw-r--r-- | net/base/network_change_notifier_win.h | 3 |
9 files changed, 176 insertions, 5 deletions
diff --git a/net/base/host_resolver_impl.cc b/net/base/host_resolver_impl.cc index 8854d07..6d1e2ba 100644 --- a/net/base/host_resolver_impl.cc +++ b/net/base/host_resolver_impl.cc @@ -470,6 +470,12 @@ class HostResolverImpl::Job //DCHECK_EQ(origin_loop_, MessageLoop::current()); DCHECK(error_ || results_.head()); + // Ideally the following code would be part of host_resolver_proc.cc, + // however it isn't safe to call NetworkChangeNotifier from worker + // threads. So we do it here on the IO thread instead. + if (error_ == ERR_NAME_NOT_RESOLVED && NetworkChangeNotifier::IsOffline()) + error_ = ERR_INTERNET_DISCONNECTED; + base::TimeDelta job_duration = base::TimeTicks::Now() - start_time_; if (had_non_speculative_request_) { diff --git a/net/base/network_change_notifier.cc b/net/base/network_change_notifier.cc index d069879..8c61ac6 100644 --- a/net/base/network_change_notifier.cc +++ b/net/base/network_change_notifier.cc @@ -22,6 +22,11 @@ namespace { // anyway.) NetworkChangeNotifier* g_network_change_notifier = NULL; +class MockNetworkChangeNotifier : public NetworkChangeNotifier { + public: + virtual bool IsCurrentlyOffline() const { return false; } +}; + } // namespace NetworkChangeNotifier::~NetworkChangeNotifier() { @@ -42,6 +47,17 @@ NetworkChangeNotifier* NetworkChangeNotifier::Create() { #endif } +// static +bool NetworkChangeNotifier::IsOffline() { + return g_network_change_notifier && + g_network_change_notifier->IsCurrentlyOffline(); +} + +// static +NetworkChangeNotifier* NetworkChangeNotifier::CreateMock() { + return new MockNetworkChangeNotifier(); +} + void NetworkChangeNotifier::AddObserver(Observer* observer) { if (g_network_change_notifier) g_network_change_notifier->observer_list_->AddObserver(observer); diff --git a/net/base/network_change_notifier.h b/net/base/network_change_notifier.h index 79909c2a..770b321 100644 --- a/net/base/network_change_notifier.h +++ b/net/base/network_change_notifier.h @@ -33,6 +33,11 @@ class NetworkChangeNotifier { virtual ~NetworkChangeNotifier(); + // See the description of NetworkChangeNotifier::IsOffline(). + // Implementations must be thread-safe. Implementations must also be + // cheap as this could be called (repeatedly) from the IO thread. + virtual bool IsCurrentlyOffline() const = 0; + // Creates the process-wide, platform-specific NetworkChangeNotifier. The // caller owns the returned pointer. You may call this on any thread. You // may also avoid creating this entirely (in which case nothing will be @@ -41,13 +46,18 @@ class NetworkChangeNotifier { // which might try to use it. static NetworkChangeNotifier* Create(); -#ifdef UNIT_TEST + // Returns true if there is currently no internet connection. + // + // A return value of |true| is a pretty strong indicator that the user + // won't be able to connect to remote sites. However, a return value of + // |false| is inconclusive; even if some link is up, it is uncertain + // whether a particular connection attempt to a particular remote site + // will be successfully. + static bool IsOffline(); + // Like Create(), but for use in tests. The mock object doesn't monitor any // events, it merely rebroadcasts notifications when requested. - static NetworkChangeNotifier* CreateMock() { - return new NetworkChangeNotifier(); - } -#endif + static NetworkChangeNotifier* CreateMock(); // Registers |observer| to receive notifications of network changes. The // thread on which this is called is the thread on which |observer| will be diff --git a/net/base/network_change_notifier_linux.cc b/net/base/network_change_notifier_linux.cc index 22be563..2601b20 100644 --- a/net/base/network_change_notifier_linux.cc +++ b/net/base/network_change_notifier_linux.cc @@ -43,6 +43,11 @@ NetworkChangeNotifierLinux::~NetworkChangeNotifierLinux() { DCHECK_EQ(kInvalidSocket, netlink_fd_); } +bool NetworkChangeNotifierLinux::IsCurrentlyOffline() const { + // TODO(eroman): http://crbug.com/53473 + return false; +} + void NetworkChangeNotifierLinux::WillDestroyCurrentMessageLoop() { DCHECK(notifier_thread_ != NULL); // We can't check the notifier_thread_'s message_loop(), as it's now 0. diff --git a/net/base/network_change_notifier_linux.h b/net/base/network_change_notifier_linux.h index d5a64d7..7bfff3e 100644 --- a/net/base/network_change_notifier_linux.h +++ b/net/base/network_change_notifier_linux.h @@ -26,6 +26,9 @@ class NetworkChangeNotifierLinux : public MessageLoop::DestructionObserver, private: virtual ~NetworkChangeNotifierLinux(); + // NetworkChangeNotifier: + virtual bool IsCurrentlyOffline() const; + // MessageLoop::DestructionObserver: virtual void WillDestroyCurrentMessageLoop(); diff --git a/net/base/network_change_notifier_mac.cc b/net/base/network_change_notifier_mac.cc index d07a15c..e5b3ce39 100644 --- a/net/base/network_change_notifier_mac.cc +++ b/net/base/network_change_notifier_mac.cc @@ -14,6 +14,11 @@ NetworkChangeNotifierMac::NetworkChangeNotifierMac() config_watcher_(&forwarder_) {} NetworkChangeNotifierMac::~NetworkChangeNotifierMac() {} +bool NetworkChangeNotifierMac::IsCurrentlyOffline() const { + // TODO(eroman): http://crbug.com/53473 + return false; +} + void NetworkChangeNotifierMac::SetDynamicStoreNotificationKeys( SCDynamicStoreRef store) { // Called on notifier thread. diff --git a/net/base/network_change_notifier_mac.h b/net/base/network_change_notifier_mac.h index 583fa64..f46a666 100644 --- a/net/base/network_change_notifier_mac.h +++ b/net/base/network_change_notifier_mac.h @@ -19,6 +19,9 @@ class NetworkChangeNotifierMac: public NetworkChangeNotifier { NetworkChangeNotifierMac(); virtual ~NetworkChangeNotifierMac(); + // NetworkChangeNotifier implementation: + virtual bool IsCurrentlyOffline() const; + private: // Forwarder just exists to keep the NetworkConfigWatcherMac API out of // NetworkChangeNotifierMac's public API. diff --git a/net/base/network_change_notifier_win.cc b/net/base/network_change_notifier_win.cc index 7bc5ddf..bc906cb 100644 --- a/net/base/network_change_notifier_win.cc +++ b/net/base/network_change_notifier_win.cc @@ -7,6 +7,9 @@ #include <iphlpapi.h> #include <winsock2.h> +#include "base/logging.h" +#include "net/base/winsock_init.h" + #pragma comment(lib, "iphlpapi.lib") namespace net { @@ -23,6 +26,123 @@ NetworkChangeNotifierWin::~NetworkChangeNotifierWin() { WSACloseEvent(addr_overlapped_.hEvent); } +// Conceptually we would like to tell whether the user is "online" verus +// "offline". This is challenging since the only thing we can test with +// certainty is whether a *particular* host is reachable. +// +// While we can't conclusively determine when a user is "online", we can at +// least reliably recognize some of the situtations when they are clearly +// "offline". For example, if the user's laptop is not plugged into an ethernet +// network and is not connected to any wireless networks, it must be offline. +// +// There are a number of different ways to implement this on Windows, each with +// their pros and cons. Here is a comparison of various techniques considered: +// +// (1) Use InternetGetConnectedState (wininet.dll). This function is really easy +// to use (literally a one-liner), and runs quickly. The drawback is it adds a +// dependency on the wininet DLL. +// +// (2) Enumerate all of the network interfaces using GetAdaptersAddresses +// (iphlpapi.dll), and assume we are "online" if there is at least one interface +// that is connected, and that interface is not a loopback or tunnel. +// +// Safari on Windows has a fairly simple implementation that does this: +// http://trac.webkit.org/browser/trunk/WebCore/platform/network/win/NetworkStateNotifierWin.cpp. +// +// Mozilla similarly uses this approach: +// http://mxr.mozilla.org/mozilla1.9.2/source/netwerk/system/win32/nsNotifyAddrListener.cpp +// +// The biggest drawback to this approach is it is quite complicated. +// WebKit's implementation for example doesn't seem to test for ICS gateways +// (internet connection sharing), whereas Mozilla's implementation has extra +// code to guess that. +// +// (3) The method used in this file comes from google talk, and is similar to +// method (2). The main difference is it enumerates the winsock namespace +// providers rather than the actual adapters. +// +// I ran some benchmarks comparing the performance of each on my Windows 7 +// workstation. Here is what I found: +// * Approach (1) was pretty much zero-cost after the initial call. +// * Approach (2) took an average of 3.25 milliseconds to enumerate the +// adapters. +// * Approach (3) took an average of 0.8 ms to enumerate the providers. +// +// In terms of correctness, all three approaches were comparable for the simple +// experiments I ran... However none of them correctly returned "offline" when +// executing 'ipconfig /release'. +// +bool NetworkChangeNotifierWin::IsCurrentlyOffline() const { + + // TODO(eroman): We could cache this value, and only re-calculate it on + // network changes. For now we recompute it each time asked, + // since it is relatively fast (sub 1ms) and not called often. + + EnsureWinsockInit(); + + // The following code was adapted from: + // http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/net/notifier/base/win/async_network_alive_win32.cc?view=markup&pathrev=47343 + // The main difference is we only call WSALookupServiceNext once, whereas + // the earlier code would traverse the entire list and pass LUP_FLUSHPREVIOUS + // to skip past the large results. + + HANDLE ws_handle; + WSAQUERYSET query_set = {0}; + query_set.dwSize = sizeof(WSAQUERYSET); + query_set.dwNameSpace = NS_NLA; + // Initiate a client query to iterate through the + // currently connected networks. + if (0 != WSALookupServiceBegin(&query_set, LUP_RETURN_ALL, + &ws_handle)) { + LOG(ERROR) << "WSALookupServiceBegin failed with: " << WSAGetLastError(); + return false; + } + + bool found_connection = false; + + // Retrieve the first available network. In this function, we only + // need to know whether or not there is network connection. + // Allocate 256 bytes for name, it should be enough for most cases. + // If the name is longer, it is OK as we will check the code returned and + // set correct network status. + char result_buffer[sizeof(WSAQUERYSET) + 256] = {0}; + DWORD length = sizeof(result_buffer); + reinterpret_cast<WSAQUERYSET*>(&result_buffer[0])->dwSize = + sizeof(WSAQUERYSET); + int result = WSALookupServiceNext( + ws_handle, + LUP_RETURN_NAME, + &length, + reinterpret_cast<WSAQUERYSET*>(&result_buffer[0])); + + if (result == 0) { + // Found a connection! + found_connection = true; + } else { + DCHECK_EQ(SOCKET_ERROR, result); + result = WSAGetLastError(); + + // Error code WSAEFAULT means there is a network connection but the + // result_buffer size is too small to contain the results. The + // variable "length" returned from WSALookupServiceNext is the minimum + // number of bytes required. We do not need to retrieve detail info, + // it is enough knowing there was a connection. + if (result == WSAEFAULT) { + found_connection = true; + } else if (result == WSA_E_NO_MORE || result == WSAENOMORE) { + // There was nothing to iterate over! + } else { + LOG(WARNING) << "WSALookupServiceNext() failed with:" << result; + } + } + + result = WSALookupServiceEnd(ws_handle); + LOG_IF(ERROR, result != 0) + << "WSALookupServiceEnd() failed with: " << result; + + return !found_connection; +} + void NetworkChangeNotifierWin::OnObjectSignaled(HANDLE object) { NotifyObserversOfIPAddressChange(); diff --git a/net/base/network_change_notifier_win.h b/net/base/network_change_notifier_win.h index 80f13f0..44782c7 100644 --- a/net/base/network_change_notifier_win.h +++ b/net/base/network_change_notifier_win.h @@ -22,6 +22,9 @@ class NetworkChangeNotifierWin : public NetworkChangeNotifier, private: virtual ~NetworkChangeNotifierWin(); + // NetworkChangeNotifier methods: + virtual bool IsCurrentlyOffline() const; + // ObjectWatcher::Delegate methods: virtual void OnObjectSignaled(HANDLE object); |