summaryrefslogtreecommitdiffstats
path: root/net/base
diff options
context:
space:
mode:
authoreroman@chromium.org <eroman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-10-09 00:35:47 +0000
committereroman@chromium.org <eroman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-10-09 00:35:47 +0000
commit2d3b7766506922d50aa2585c1a596350d57ad238 (patch)
treecb85915944eba0f47577c660682b4868780e816c /net/base
parentff9cebb3f64df1c0c779a8a60daee57da7f9b667 (diff)
downloadchromium_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.cc6
-rw-r--r--net/base/network_change_notifier.cc16
-rw-r--r--net/base/network_change_notifier.h20
-rw-r--r--net/base/network_change_notifier_linux.cc5
-rw-r--r--net/base/network_change_notifier_linux.h3
-rw-r--r--net/base/network_change_notifier_mac.cc5
-rw-r--r--net/base/network_change_notifier_mac.h3
-rw-r--r--net/base/network_change_notifier_win.cc120
-rw-r--r--net/base/network_change_notifier_win.h3
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);