diff options
author | mark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-11 23:53:14 +0000 |
---|---|---|
committer | mark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-11 23:53:14 +0000 |
commit | 0303c1151c1baad55157bde512fc622bc863cb1b (patch) | |
tree | b6947ebb352738c2674683c73009c9cb93bdb9ca /net/socket | |
parent | 099e3eb41df072bbc3dd5dd0b007595cb2a0c634 (diff) | |
download | chromium_src-0303c1151c1baad55157bde512fc622bc863cb1b.zip chromium_src-0303c1151c1baad55157bde512fc622bc863cb1b.tar.gz chromium_src-0303c1151c1baad55157bde512fc622bc863cb1b.tar.bz2 |
Non-blocking connect() attempts may fail synchronously in some cases. When
this occurs, connect() should be retried with another address if possible and
appropriate.
On Mac OS X 10.6 ("Snow Leopard"), getaddrinfo() returns IPv6 addresses even
when inappropriate due to the use of AI_ADDRCONFIG. connect() fails
immediately when trying to connect to an IPv6 address from a system that only
has IPv4 connectivity. The existing net::TCPClientSocketLibevent is not
prepared to deal with immediate connect() failures, so it fails without
trying additional addresses. Some sites, such as python.org, publish both
IPv4 and IPv6 addresses. On Snow Leopard, name resolution always returns
the IPv6 addresses first, rendering such sites impossible to connect to unless
reachable by IPv6.
This change restores the previous behavior of setting AI_ADDRCONFIG when
calling getaddrinfo() on Mac OS X. AI_ADDRCONFIG was removed in a previous
attempt to fix this bug. AI_ADDRCONFIG is now documented in Snow Leopard.
The associated comment, written for Mac OS X 10.5 ("Leopard"), is no longer
correct. In most cases, the presence or absence of this flag seems to have no
impact on the system resolver's behavior, but I believe that its presence is
correct per the documentation. A separate bug will be filed with Apple.
BUG=12711
TEST=http://python.org/ on Snow Leopard should load on a machine where only
IPv4 is available; it (and all other sites) should continue to function
properly on Leopard
Review URL: http://codereview.chromium.org/196094
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@26051 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/socket')
-rw-r--r-- | net/socket/tcp_client_socket_libevent.cc | 72 |
1 files changed, 49 insertions, 23 deletions
diff --git a/net/socket/tcp_client_socket_libevent.cc b/net/socket/tcp_client_socket_libevent.cc index 8ad5ef3..165c4cd 100644 --- a/net/socket/tcp_client_socket_libevent.cc +++ b/net/socket/tcp_client_socket_libevent.cc @@ -78,6 +78,26 @@ int MapConnectError(int err) { } } +// Given err, an errno from a connect() attempt, returns true if connect() +// should be retried with another address. +bool ShouldTryNextAddress(int err) { + switch (err) { + case EADDRNOTAVAIL: + case EAFNOSUPPORT: + case ECONNREFUSED: + case ECONNRESET: + case EACCES: + case EPERM: + case ENETUNREACH: + case EHOSTUNREACH: + case ENETDOWN: + case ETIMEDOUT: + return true; + default: + return false; + } +} + } // namespace //----------------------------------------------------------------------------- @@ -105,30 +125,42 @@ int TCPClientSocketLibevent::Connect(CompletionCallback* callback) { DCHECK(!waiting_connect_); TRACE_EVENT_BEGIN("socket.connect", this, ""); - const addrinfo* ai = current_ai_; - DCHECK(ai); - int rv = CreateSocket(ai); - if (rv != OK) - return rv; + while (true) { + DCHECK(current_ai_); - if (!HANDLE_EINTR(connect(socket_, ai->ai_addr, - static_cast<int>(ai->ai_addrlen)))) { - TRACE_EVENT_END("socket.connect", this, ""); - // Connected without waiting! - return OK; - } + int rv = CreateSocket(current_ai_); + if (rv != OK) + return rv; - // Synchronous operation not supported - DCHECK(callback); + if (!HANDLE_EINTR(connect(socket_, current_ai_->ai_addr, + static_cast<int>(current_ai_->ai_addrlen)))) { + TRACE_EVENT_END("socket.connect", this, ""); + // Connected without waiting! + return OK; + } + + int error_code = errno; + if (error_code == EINPROGRESS) + break; - if (errno != EINPROGRESS) { - DLOG(INFO) << "connect failed: " << errno; close(socket_); socket_ = kInvalidSocket; - return MapConnectError(errno); + + if (current_ai_->ai_next && ShouldTryNextAddress(error_code)) { + // connect() can fail synchronously for an address even on a + // non-blocking socket. As an example, this can happen when there is + // no route to the host. Retry using the next address in the list. + current_ai_ = current_ai_->ai_next; + } else { + DLOG(INFO) << "connect failed: " << error_code; + return MapConnectError(error_code); + } } + // Synchronous operation not supported + DCHECK(callback); + // Initialize write_socket_watcher_ and link it to our MessagePump. // POLLOUT is set if the connection is established. // POLLIN is set if the connection fails. @@ -324,13 +356,7 @@ void TCPClientSocketLibevent::DidCompleteConnect() { if (error_code == EINPROGRESS || error_code == EALREADY) { NOTREACHED(); // This indicates a bug in libevent or our code. result = ERR_IO_PENDING; - } else if (current_ai_->ai_next && ( - error_code == EADDRNOTAVAIL || - error_code == EAFNOSUPPORT || - error_code == ECONNREFUSED || - error_code == ENETUNREACH || - error_code == EHOSTUNREACH || - error_code == ETIMEDOUT)) { + } else if (current_ai_->ai_next && ShouldTryNextAddress(error_code)) { // This address failed, try next one in list. const addrinfo* next = current_ai_->ai_next; Disconnect(); |