diff options
author | yzshen@chromium.org <yzshen@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-18 21:50:26 +0000 |
---|---|---|
committer | yzshen@chromium.org <yzshen@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-18 21:50:26 +0000 |
commit | 659fd67cf063e5a5b980fff02b1749ebaffdc664 (patch) | |
tree | 68b76ab69b4873b969ab3cd3a51229a41d23d317 | |
parent | 54dd6e94cfb2fdb125406e657f3bb2078d84af68 (diff) | |
download | chromium_src-659fd67cf063e5a5b980fff02b1749ebaffdc664.zip chromium_src-659fd67cf063e5a5b980fff02b1749ebaffdc664.tar.gz chromium_src-659fd67cf063e5a5b980fff02b1749ebaffdc664.tar.bz2 |
POSIX only: Move client socket functionality from TCPClientSocket into TCPSocket.
TCPClientSocket becomes a wrapper around TCPSocket to expose a client-only interface.
BUG=262601
TEST=tcp_socket_unittest.cc
Review URL: https://chromiumcodereview.appspot.com/23454010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@223945 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | net/net.gyp | 5 | ||||
-rw-r--r-- | net/socket/tcp_client_socket.cc | 53 | ||||
-rw-r--r-- | net/socket/tcp_client_socket.h | 27 | ||||
-rw-r--r-- | net/socket/tcp_client_socket_libevent.cc | 830 | ||||
-rw-r--r-- | net/socket/tcp_client_socket_libevent.h | 256 | ||||
-rw-r--r-- | net/socket/tcp_server_socket.cc | 21 | ||||
-rw-r--r-- | net/socket/tcp_socket.cc | 59 | ||||
-rw-r--r-- | net/socket/tcp_socket.h | 8 | ||||
-rw-r--r-- | net/socket/tcp_socket_libevent.cc | 704 | ||||
-rw-r--r-- | net/socket/tcp_socket_libevent.h | 191 | ||||
-rw-r--r-- | net/socket/tcp_socket_unittest.cc | 7 |
11 files changed, 909 insertions, 1252 deletions
diff --git a/net/net.gyp b/net/net.gyp index e396e47..3bea4ca 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -909,12 +909,11 @@ 'socket/stream_socket.h', 'socket/tcp_client_socket.cc', 'socket/tcp_client_socket.h', - 'socket/tcp_client_socket_libevent.cc', - 'socket/tcp_client_socket_libevent.h', 'socket/tcp_listen_socket.cc', 'socket/tcp_listen_socket.h', 'socket/tcp_server_socket.cc', 'socket/tcp_server_socket.h', + 'socket/tcp_socket.cc', 'socket/tcp_socket.h', 'socket/tcp_socket_libevent.cc', 'socket/tcp_socket_libevent.h', @@ -1376,8 +1375,6 @@ [ 'OS == "win"', { 'sources!': [ 'http/http_auth_handler_ntlm_portable.cc', - 'socket/tcp_client_socket_libevent.cc', - 'socket/tcp_client_socket_libevent.h', 'socket/tcp_socket_libevent.cc', 'socket/tcp_socket_libevent.h', 'ssl/client_cert_store_impl_nss.cc', diff --git a/net/socket/tcp_client_socket.cc b/net/socket/tcp_client_socket.cc index af1c555..22aea47 100644 --- a/net/socket/tcp_client_socket.cc +++ b/net/socket/tcp_client_socket.cc @@ -5,8 +5,6 @@ #include "net/socket/tcp_client_socket.h" #include "base/callback_helpers.h" -#include "base/file_util.h" -#include "base/files/file_path.h" #include "base/logging.h" #include "net/base/io_buffer.h" #include "net/base/ip_endpoint.h" @@ -15,55 +13,6 @@ namespace net { -namespace { - -#if defined(OS_LINUX) - -// Checks to see if the system supports TCP FastOpen. Notably, it requires -// kernel support. Additionally, this checks system configuration to ensure that -// it's enabled. -bool SystemSupportsTCPFastOpen() { - static const base::FilePath::CharType kTCPFastOpenProcFilePath[] = - "/proc/sys/net/ipv4/tcp_fastopen"; - std::string system_enabled_tcp_fastopen; - if (!base::ReadFileToString( - base::FilePath(kTCPFastOpenProcFilePath), - &system_enabled_tcp_fastopen)) { - return false; - } - - // As per http://lxr.linux.no/linux+v3.7.7/include/net/tcp.h#L225 - // TFO_CLIENT_ENABLE is the LSB - if (system_enabled_tcp_fastopen.empty() || - (system_enabled_tcp_fastopen[0] & 0x1) == 0) { - return false; - } - - return true; -} - -#else - -bool SystemSupportsTCPFastOpen() { - return false; -} - -#endif - -} - -static bool g_tcp_fastopen_enabled = false; - -void SetTCPFastOpenEnabled(bool value) { - g_tcp_fastopen_enabled = value && SystemSupportsTCPFastOpen(); -} - -bool IsTCPFastOpenEnabled() { - return g_tcp_fastopen_enabled; -} - -#if defined(OS_WIN) - TCPClientSocket::TCPClientSocket(const AddressList& addresses, net::NetLog* net_log, const net::NetLog::Source& source) @@ -368,6 +317,4 @@ int TCPClientSocket::OpenSocket(AddressFamily family) { return OK; } -#endif - } // namespace net diff --git a/net/socket/tcp_client_socket.h b/net/socket/tcp_client_socket.h index 841bc81..fabcbc1 100644 --- a/net/socket/tcp_client_socket.h +++ b/net/socket/tcp_client_socket.h @@ -5,42 +5,19 @@ #ifndef NET_SOCKET_TCP_CLIENT_SOCKET_H_ #define NET_SOCKET_TCP_CLIENT_SOCKET_H_ -#include "build/build_config.h" -#include "net/base/net_export.h" - -// TODO(yzshen): Switch OS_POSIX to use the same platform-independent -// TCPClientSocket. -#if defined(OS_POSIX) - -#include "net/socket/tcp_client_socket_libevent.h" - -#elif defined(OS_WIN) - #include "base/basictypes.h" #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" #include "net/base/address_list.h" #include "net/base/completion_callback.h" +#include "net/base/net_export.h" #include "net/base/net_log.h" #include "net/socket/stream_socket.h" #include "net/socket/tcp_socket.h" -#endif - namespace net { -// Enable/disable experimental TCP FastOpen option. -// Not thread safe. Must be called during initialization/startup only. -NET_EXPORT void SetTCPFastOpenEnabled(bool value); - -// Check if the TCP FastOpen option is enabled. -bool IsTCPFastOpenEnabled(); - // A client socket that uses TCP as the transport layer. -#if defined(OS_POSIX) -typedef TCPClientSocketLibevent TCPClientSocket; -#elif defined(OS_WIN) - class NET_EXPORT TCPClientSocket : public StreamSocket { public: // The IP address(es) and port number to connect to. The TCP socket will try @@ -139,8 +116,6 @@ class NET_EXPORT TCPClientSocket : public StreamSocket { DISALLOW_COPY_AND_ASSIGN(TCPClientSocket); }; -#endif - } // namespace net #endif // NET_SOCKET_TCP_CLIENT_SOCKET_H_ diff --git a/net/socket/tcp_client_socket_libevent.cc b/net/socket/tcp_client_socket_libevent.cc deleted file mode 100644 index cbcd25f..0000000 --- a/net/socket/tcp_client_socket_libevent.cc +++ /dev/null @@ -1,830 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "net/socket/tcp_client_socket.h" - -#include <errno.h> -#include <fcntl.h> -#include <netdb.h> -#include <sys/socket.h> -#include <netinet/tcp.h> -#if defined(OS_POSIX) -#include <netinet/in.h> -#endif - -#include "base/logging.h" -#include "base/message_loop/message_loop.h" -#include "base/metrics/histogram.h" -#include "base/metrics/stats_counters.h" -#include "base/posix/eintr_wrapper.h" -#include "base/strings/string_util.h" -#include "net/base/connection_type_histograms.h" -#include "net/base/io_buffer.h" -#include "net/base/ip_endpoint.h" -#include "net/base/net_errors.h" -#include "net/base/net_log.h" -#include "net/base/net_util.h" -#include "net/base/network_change_notifier.h" -#include "net/socket/socket_descriptor.h" -#include "net/socket/socket_net_log_params.h" - -// If we don't have a definition for TCPI_OPT_SYN_DATA, create one. -#ifndef TCPI_OPT_SYN_DATA -#define TCPI_OPT_SYN_DATA 32 -#endif - -namespace net { - -namespace { - -const int kTCPKeepAliveSeconds = 45; - -// SetTCPNoDelay turns on/off buffering in the kernel. By default, TCP sockets -// will wait up to 200ms for more data to complete a packet before transmitting. -// After calling this function, the kernel will not wait. See TCP_NODELAY in -// `man 7 tcp`. -bool SetTCPNoDelay(int fd, bool no_delay) { - int on = no_delay ? 1 : 0; - int error = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, - sizeof(on)); - return error == 0; -} - -// SetTCPKeepAlive sets SO_KEEPALIVE. -bool SetTCPKeepAlive(int fd, bool enable, int delay) { - int on = enable ? 1 : 0; - if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on))) { - PLOG(ERROR) << "Failed to set SO_KEEPALIVE on fd: " << fd; - return false; - } -#if defined(OS_LINUX) || defined(OS_ANDROID) - // Set seconds until first TCP keep alive. - if (setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &delay, sizeof(delay))) { - PLOG(ERROR) << "Failed to set TCP_KEEPIDLE on fd: " << fd; - return false; - } - // Set seconds between TCP keep alives. - if (setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &delay, sizeof(delay))) { - PLOG(ERROR) << "Failed to set TCP_KEEPINTVL on fd: " << fd; - return false; - } -#endif - return true; -} - -// Sets socket parameters. Returns the OS error code (or 0 on -// success). -int SetupSocket(int socket) { - if (SetNonBlocking(socket)) - return errno; - - // This mirrors the behaviour on Windows. See the comment in - // tcp_client_socket_win.cc after searching for "NODELAY". - SetTCPNoDelay(socket, true); // If SetTCPNoDelay fails, we don't care. - SetTCPKeepAlive(socket, true, kTCPKeepAliveSeconds); - - return 0; -} - -// Creates a new socket and sets default parameters for it. Returns -// the OS error code (or 0 on success). -int CreateSocket(int family, int* socket) { - *socket = CreatePlatformSocket(family, SOCK_STREAM, IPPROTO_TCP); - if (*socket == kInvalidSocket) - return errno; - int error = SetupSocket(*socket); - if (error) { - if (HANDLE_EINTR(close(*socket)) < 0) - PLOG(ERROR) << "close"; - *socket = kInvalidSocket; - return error; - } - return 0; -} - -int MapConnectError(int os_error) { - switch (os_error) { - case EACCES: - return ERR_NETWORK_ACCESS_DENIED; - case ETIMEDOUT: - return ERR_CONNECTION_TIMED_OUT; - default: { - int net_error = MapSystemError(os_error); - if (net_error == ERR_FAILED) - return ERR_CONNECTION_FAILED; // More specific than ERR_FAILED. - - // Give a more specific error when the user is offline. - if (net_error == ERR_ADDRESS_UNREACHABLE && - NetworkChangeNotifier::IsOffline()) { - return ERR_INTERNET_DISCONNECTED; - } - return net_error; - } - } -} - -} // namespace - -//----------------------------------------------------------------------------- - -TCPClientSocketLibevent::TCPClientSocketLibevent( - const AddressList& addresses, - net::NetLog* net_log, - const net::NetLog::Source& source) - : socket_(kInvalidSocket), - bound_socket_(kInvalidSocket), - addresses_(addresses), - current_address_index_(-1), - read_watcher_(this), - write_watcher_(this), - next_connect_state_(CONNECT_STATE_NONE), - connect_os_error_(0), - net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SOCKET)), - previously_disconnected_(false), - use_tcp_fastopen_(IsTCPFastOpenEnabled()), - tcp_fastopen_connected_(false), - fast_open_status_(FAST_OPEN_STATUS_UNKNOWN) { - net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE, - source.ToEventParametersCallback()); -} - -TCPClientSocketLibevent::~TCPClientSocketLibevent() { - Disconnect(); - net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE); - if (tcp_fastopen_connected_) { - UMA_HISTOGRAM_ENUMERATION("Net.TcpFastOpenSocketConnection", - fast_open_status_, FAST_OPEN_MAX_VALUE); - } -} - -int TCPClientSocketLibevent::AdoptSocket(int socket) { - DCHECK_EQ(socket_, kInvalidSocket); - - int error = SetupSocket(socket); - if (error) - return MapSystemError(error); - - socket_ = socket; - - // This is to make GetPeerAddress() work. It's up to the caller ensure - // that |address_| contains a reasonable address for this - // socket. (i.e. at least match IPv4 vs IPv6!). - current_address_index_ = 0; - use_history_.set_was_ever_connected(); - - return OK; -} - -int TCPClientSocketLibevent::Bind(const IPEndPoint& address) { - if (current_address_index_ >= 0 || bind_address_.get()) { - // Cannot bind the socket if we are already bound connected or - // connecting. - return ERR_UNEXPECTED; - } - - SockaddrStorage storage; - if (!address.ToSockAddr(storage.addr, &storage.addr_len)) - return ERR_INVALID_ARGUMENT; - - // Create |bound_socket_| and try to bind it to |address|. - int error = CreateSocket(address.GetSockAddrFamily(), &bound_socket_); - if (error) - return MapSystemError(error); - - if (HANDLE_EINTR(bind(bound_socket_, storage.addr, storage.addr_len))) { - error = errno; - if (HANDLE_EINTR(close(bound_socket_)) < 0) - PLOG(ERROR) << "close"; - bound_socket_ = kInvalidSocket; - return MapSystemError(error); - } - - bind_address_.reset(new IPEndPoint(address)); - - return 0; -} - -int TCPClientSocketLibevent::Connect(const CompletionCallback& callback) { - DCHECK(CalledOnValidThread()); - - // If already connected, then just return OK. - if (socket_ != kInvalidSocket) - return OK; - - base::StatsCounter connects("tcp.connect"); - connects.Increment(); - - DCHECK(!waiting_connect()); - - net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT, - addresses_.CreateNetLogCallback()); - - // We will try to connect to each address in addresses_. Start with the - // first one in the list. - next_connect_state_ = CONNECT_STATE_CONNECT; - current_address_index_ = 0; - - int rv = DoConnectLoop(OK); - if (rv == ERR_IO_PENDING) { - // Synchronous operation not supported. - DCHECK(!callback.is_null()); - write_callback_ = callback; - } else { - LogConnectCompletion(rv); - } - - return rv; -} - -int TCPClientSocketLibevent::DoConnectLoop(int result) { - DCHECK_NE(next_connect_state_, CONNECT_STATE_NONE); - - int rv = result; - do { - ConnectState state = next_connect_state_; - next_connect_state_ = CONNECT_STATE_NONE; - switch (state) { - case CONNECT_STATE_CONNECT: - DCHECK_EQ(OK, rv); - rv = DoConnect(); - break; - case CONNECT_STATE_CONNECT_COMPLETE: - rv = DoConnectComplete(rv); - break; - default: - LOG(DFATAL) << "bad state"; - rv = ERR_UNEXPECTED; - break; - } - } while (rv != ERR_IO_PENDING && next_connect_state_ != CONNECT_STATE_NONE); - - return rv; -} - -int TCPClientSocketLibevent::DoConnect() { - DCHECK_GE(current_address_index_, 0); - DCHECK_LT(current_address_index_, static_cast<int>(addresses_.size())); - DCHECK_EQ(0, connect_os_error_); - - const IPEndPoint& endpoint = addresses_[current_address_index_]; - - if (previously_disconnected_) { - use_history_.Reset(); - previously_disconnected_ = false; - } - - net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT, - CreateNetLogIPEndPointCallback(&endpoint)); - - next_connect_state_ = CONNECT_STATE_CONNECT_COMPLETE; - - if (bound_socket_ != kInvalidSocket) { - DCHECK(bind_address_.get()); - socket_ = bound_socket_; - bound_socket_ = kInvalidSocket; - } else { - // Create a non-blocking socket. - connect_os_error_ = CreateSocket(endpoint.GetSockAddrFamily(), &socket_); - if (connect_os_error_) - return MapSystemError(connect_os_error_); - - if (bind_address_.get()) { - SockaddrStorage storage; - if (!bind_address_->ToSockAddr(storage.addr, &storage.addr_len)) - return ERR_INVALID_ARGUMENT; - if (HANDLE_EINTR(bind(socket_, storage.addr, storage.addr_len))) - return MapSystemError(errno); - } - } - - // Connect the socket. - if (!use_tcp_fastopen_) { - SockaddrStorage storage; - if (!endpoint.ToSockAddr(storage.addr, &storage.addr_len)) - return ERR_INVALID_ARGUMENT; - - if (!HANDLE_EINTR(connect(socket_, storage.addr, storage.addr_len))) { - // Connected without waiting! - return OK; - } - } else { - // With TCP FastOpen, we pretend that the socket is connected. - DCHECK(!tcp_fastopen_connected_); - return OK; - } - - // Check if the connect() failed synchronously. - connect_os_error_ = errno; - if (connect_os_error_ != EINPROGRESS) - return MapConnectError(connect_os_error_); - - // Otherwise the connect() is going to complete asynchronously, so watch - // for its completion. - if (!base::MessageLoopForIO::current()->WatchFileDescriptor( - socket_, true, base::MessageLoopForIO::WATCH_WRITE, - &write_socket_watcher_, &write_watcher_)) { - connect_os_error_ = errno; - DVLOG(1) << "WatchFileDescriptor failed: " << connect_os_error_; - return MapSystemError(connect_os_error_); - } - - return ERR_IO_PENDING; -} - -int TCPClientSocketLibevent::DoConnectComplete(int result) { - // Log the end of this attempt (and any OS error it threw). - int os_error = connect_os_error_; - connect_os_error_ = 0; - if (result != OK) { - net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT, - NetLog::IntegerCallback("os_error", os_error)); - } else { - net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT); - } - - if (result == OK) { - write_socket_watcher_.StopWatchingFileDescriptor(); - use_history_.set_was_ever_connected(); - return OK; // Done! - } - - // Close whatever partially connected socket we currently have. - DoDisconnect(); - - // Try to fall back to the next address in the list. - if (current_address_index_ + 1 < static_cast<int>(addresses_.size())) { - next_connect_state_ = CONNECT_STATE_CONNECT; - ++current_address_index_; - return OK; - } - - // Otherwise there is nothing to fall back to, so give up. - return result; -} - -void TCPClientSocketLibevent::Disconnect() { - DCHECK(CalledOnValidThread()); - - DoDisconnect(); - current_address_index_ = -1; - bind_address_.reset(); -} - -void TCPClientSocketLibevent::DoDisconnect() { - if (socket_ == kInvalidSocket) - return; - - bool ok = read_socket_watcher_.StopWatchingFileDescriptor(); - DCHECK(ok); - ok = write_socket_watcher_.StopWatchingFileDescriptor(); - DCHECK(ok); - if (HANDLE_EINTR(close(socket_)) < 0) - PLOG(ERROR) << "close"; - socket_ = kInvalidSocket; - previously_disconnected_ = true; -} - -bool TCPClientSocketLibevent::IsConnected() const { - DCHECK(CalledOnValidThread()); - - if (socket_ == kInvalidSocket || waiting_connect()) - return false; - - if (use_tcp_fastopen_ && !tcp_fastopen_connected_) { - // With TCP FastOpen, we pretend that the socket is connected. - // This allows GetPeerAddress() to return current_ai_ as the peer - // address. Since we don't fail over to the next address if - // sendto() fails, current_ai_ is the only possible peer address. - CHECK_LT(current_address_index_, static_cast<int>(addresses_.size())); - return true; - } - - // Check if connection is alive. - char c; - int rv = HANDLE_EINTR(recv(socket_, &c, 1, MSG_PEEK)); - if (rv == 0) - return false; - if (rv == -1 && errno != EAGAIN && errno != EWOULDBLOCK) - return false; - - return true; -} - -bool TCPClientSocketLibevent::IsConnectedAndIdle() const { - DCHECK(CalledOnValidThread()); - - if (socket_ == kInvalidSocket || waiting_connect()) - return false; - - // TODO(wtc): should we also handle the TCP FastOpen case here, - // as we do in IsConnected()? - - // Check if connection is alive and we haven't received any data - // unexpectedly. - char c; - int rv = HANDLE_EINTR(recv(socket_, &c, 1, MSG_PEEK)); - if (rv >= 0) - return false; - if (errno != EAGAIN && errno != EWOULDBLOCK) - return false; - - return true; -} - -int TCPClientSocketLibevent::Read(IOBuffer* buf, - int buf_len, - const CompletionCallback& callback) { - DCHECK(CalledOnValidThread()); - DCHECK_NE(kInvalidSocket, socket_); - DCHECK(!waiting_connect()); - DCHECK(read_callback_.is_null()); - // Synchronous operation not supported - DCHECK(!callback.is_null()); - DCHECK_GT(buf_len, 0); - - int nread = HANDLE_EINTR(read(socket_, buf->data(), buf_len)); - if (nread >= 0) { - base::StatsCounter read_bytes("tcp.read_bytes"); - read_bytes.Add(nread); - if (nread > 0) - use_history_.set_was_used_to_convey_data(); - net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, nread, - buf->data()); - RecordFastOpenStatus(); - return nread; - } - if (errno != EAGAIN && errno != EWOULDBLOCK) { - int net_error = MapSystemError(errno); - net_log_.AddEvent(NetLog::TYPE_SOCKET_READ_ERROR, - CreateNetLogSocketErrorCallback(net_error, errno)); - return net_error; - } - - if (!base::MessageLoopForIO::current()->WatchFileDescriptor( - socket_, true, base::MessageLoopForIO::WATCH_READ, - &read_socket_watcher_, &read_watcher_)) { - DVLOG(1) << "WatchFileDescriptor failed on read, errno " << errno; - return MapSystemError(errno); - } - - read_buf_ = buf; - read_buf_len_ = buf_len; - read_callback_ = callback; - return ERR_IO_PENDING; -} - -int TCPClientSocketLibevent::Write(IOBuffer* buf, - int buf_len, - const CompletionCallback& callback) { - DCHECK(CalledOnValidThread()); - DCHECK_NE(kInvalidSocket, socket_); - DCHECK(!waiting_connect()); - DCHECK(write_callback_.is_null()); - // Synchronous operation not supported - DCHECK(!callback.is_null()); - DCHECK_GT(buf_len, 0); - - int nwrite = InternalWrite(buf, buf_len); - if (nwrite >= 0) { - base::StatsCounter write_bytes("tcp.write_bytes"); - write_bytes.Add(nwrite); - if (nwrite > 0) - use_history_.set_was_used_to_convey_data(); - net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, nwrite, - buf->data()); - return nwrite; - } - if (errno != EAGAIN && errno != EWOULDBLOCK) { - int net_error = MapSystemError(errno); - net_log_.AddEvent(NetLog::TYPE_SOCKET_WRITE_ERROR, - CreateNetLogSocketErrorCallback(net_error, errno)); - return net_error; - } - - if (!base::MessageLoopForIO::current()->WatchFileDescriptor( - socket_, true, base::MessageLoopForIO::WATCH_WRITE, - &write_socket_watcher_, &write_watcher_)) { - DVLOG(1) << "WatchFileDescriptor failed on write, errno " << errno; - return MapSystemError(errno); - } - - write_buf_ = buf; - write_buf_len_ = buf_len; - write_callback_ = callback; - return ERR_IO_PENDING; -} - -int TCPClientSocketLibevent::InternalWrite(IOBuffer* buf, int buf_len) { - int nwrite; - if (use_tcp_fastopen_ && !tcp_fastopen_connected_) { - SockaddrStorage storage; - if (!addresses_[current_address_index_].ToSockAddr(storage.addr, - &storage.addr_len)) { - errno = EINVAL; - return -1; - } - - int flags = 0x20000000; // Magic flag to enable TCP_FASTOPEN. -#if defined(OS_LINUX) - // sendto() will fail with EPIPE when the system doesn't support TCP Fast - // Open. Theoretically that shouldn't happen since the caller should check - // for system support on startup, but users may dynamically disable TCP Fast - // Open via sysctl. - flags |= MSG_NOSIGNAL; -#endif // defined(OS_LINUX) - nwrite = HANDLE_EINTR(sendto(socket_, - buf->data(), - buf_len, - flags, - storage.addr, - storage.addr_len)); - tcp_fastopen_connected_ = true; - - if (nwrite < 0) { - DCHECK_NE(EPIPE, errno); - - // If errno == EINPROGRESS, that means the kernel didn't have a cookie - // and would block. The kernel is internally doing a connect() though. - // Remap EINPROGRESS to EAGAIN so we treat this the same as our other - // asynchronous cases. Note that the user buffer has not been copied to - // kernel space. - if (errno == EINPROGRESS) { - errno = EAGAIN; - fast_open_status_ = FAST_OPEN_SLOW_CONNECT_RETURN; - } else { - fast_open_status_ = FAST_OPEN_ERROR; - } - } else { - fast_open_status_ = FAST_OPEN_FAST_CONNECT_RETURN; - } - } else { - nwrite = HANDLE_EINTR(write(socket_, buf->data(), buf_len)); - } - return nwrite; -} - -bool TCPClientSocketLibevent::SetReceiveBufferSize(int32 size) { - DCHECK(CalledOnValidThread()); - int rv = setsockopt(socket_, SOL_SOCKET, SO_RCVBUF, - reinterpret_cast<const char*>(&size), - sizeof(size)); - DCHECK(!rv) << "Could not set socket receive buffer size: " << errno; - return rv == 0; -} - -bool TCPClientSocketLibevent::SetSendBufferSize(int32 size) { - DCHECK(CalledOnValidThread()); - int rv = setsockopt(socket_, SOL_SOCKET, SO_SNDBUF, - reinterpret_cast<const char*>(&size), - sizeof(size)); - DCHECK(!rv) << "Could not set socket send buffer size: " << errno; - return rv == 0; -} - -bool TCPClientSocketLibevent::SetKeepAlive(bool enable, int delay) { - int socket = socket_ != kInvalidSocket ? socket_ : bound_socket_; - return SetTCPKeepAlive(socket, enable, delay); -} - -bool TCPClientSocketLibevent::SetNoDelay(bool no_delay) { - int socket = socket_ != kInvalidSocket ? socket_ : bound_socket_; - return SetTCPNoDelay(socket, no_delay); -} - -void TCPClientSocketLibevent::ReadWatcher::OnFileCanReadWithoutBlocking(int) { - socket_->RecordFastOpenStatus(); - if (!socket_->read_callback_.is_null()) - socket_->DidCompleteRead(); -} - -void TCPClientSocketLibevent::WriteWatcher::OnFileCanWriteWithoutBlocking(int) { - if (socket_->waiting_connect()) { - socket_->DidCompleteConnect(); - } else if (!socket_->write_callback_.is_null()) { - socket_->DidCompleteWrite(); - } -} - -void TCPClientSocketLibevent::LogConnectCompletion(int net_error) { - if (net_error == OK) - UpdateConnectionTypeHistograms(CONNECTION_ANY); - - if (net_error != OK) { - net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_CONNECT, net_error); - return; - } - - SockaddrStorage storage; - int rv = getsockname(socket_, storage.addr, &storage.addr_len); - if (rv != 0) { - PLOG(ERROR) << "getsockname() [rv: " << rv << "] error: "; - NOTREACHED(); - net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_CONNECT, rv); - return; - } - - net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT, - CreateNetLogSourceAddressCallback(storage.addr, - storage.addr_len)); -} - -void TCPClientSocketLibevent::DoReadCallback(int rv) { - DCHECK_NE(rv, ERR_IO_PENDING); - DCHECK(!read_callback_.is_null()); - - // since Run may result in Read being called, clear read_callback_ up front. - CompletionCallback c = read_callback_; - read_callback_.Reset(); - c.Run(rv); -} - -void TCPClientSocketLibevent::DoWriteCallback(int rv) { - DCHECK_NE(rv, ERR_IO_PENDING); - DCHECK(!write_callback_.is_null()); - - // since Run may result in Write being called, clear write_callback_ up front. - CompletionCallback c = write_callback_; - write_callback_.Reset(); - c.Run(rv); -} - -void TCPClientSocketLibevent::DidCompleteConnect() { - DCHECK_EQ(next_connect_state_, CONNECT_STATE_CONNECT_COMPLETE); - - // Get the error that connect() completed with. - int os_error = 0; - socklen_t len = sizeof(os_error); - if (getsockopt(socket_, SOL_SOCKET, SO_ERROR, &os_error, &len) < 0) - os_error = errno; - - // TODO(eroman): Is this check really necessary? - if (os_error == EINPROGRESS || os_error == EALREADY) { - NOTREACHED(); // This indicates a bug in libevent or our code. - return; - } - - connect_os_error_ = os_error; - int rv = DoConnectLoop(MapConnectError(os_error)); - if (rv != ERR_IO_PENDING) { - LogConnectCompletion(rv); - DoWriteCallback(rv); - } -} - -void TCPClientSocketLibevent::DidCompleteRead() { - int bytes_transferred; - bytes_transferred = HANDLE_EINTR(read(socket_, read_buf_->data(), - read_buf_len_)); - - int result; - if (bytes_transferred >= 0) { - result = bytes_transferred; - base::StatsCounter read_bytes("tcp.read_bytes"); - read_bytes.Add(bytes_transferred); - if (bytes_transferred > 0) - use_history_.set_was_used_to_convey_data(); - net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, result, - read_buf_->data()); - } else { - result = MapSystemError(errno); - if (result != ERR_IO_PENDING) { - net_log_.AddEvent(NetLog::TYPE_SOCKET_READ_ERROR, - CreateNetLogSocketErrorCallback(result, errno)); - } - } - - if (result != ERR_IO_PENDING) { - read_buf_ = NULL; - read_buf_len_ = 0; - bool ok = read_socket_watcher_.StopWatchingFileDescriptor(); - DCHECK(ok); - DoReadCallback(result); - } -} - -void TCPClientSocketLibevent::DidCompleteWrite() { - int bytes_transferred; - bytes_transferred = HANDLE_EINTR(write(socket_, write_buf_->data(), - write_buf_len_)); - - int result; - if (bytes_transferred >= 0) { - result = bytes_transferred; - base::StatsCounter write_bytes("tcp.write_bytes"); - write_bytes.Add(bytes_transferred); - if (bytes_transferred > 0) - use_history_.set_was_used_to_convey_data(); - net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, result, - write_buf_->data()); - } else { - result = MapSystemError(errno); - if (result != ERR_IO_PENDING) { - net_log_.AddEvent(NetLog::TYPE_SOCKET_WRITE_ERROR, - CreateNetLogSocketErrorCallback(result, errno)); - } - } - - if (result != ERR_IO_PENDING) { - write_buf_ = NULL; - write_buf_len_ = 0; - write_socket_watcher_.StopWatchingFileDescriptor(); - DoWriteCallback(result); - } -} - -int TCPClientSocketLibevent::GetPeerAddress(IPEndPoint* address) const { - DCHECK(CalledOnValidThread()); - DCHECK(address); - if (!IsConnected()) - return ERR_SOCKET_NOT_CONNECTED; - *address = addresses_[current_address_index_]; - return OK; -} - -int TCPClientSocketLibevent::GetLocalAddress(IPEndPoint* address) const { - DCHECK(CalledOnValidThread()); - DCHECK(address); - if (socket_ == kInvalidSocket) { - if (bind_address_.get()) { - *address = *bind_address_; - return OK; - } - return ERR_SOCKET_NOT_CONNECTED; - } - - SockaddrStorage storage; - if (getsockname(socket_, storage.addr, &storage.addr_len)) - return MapSystemError(errno); - if (!address->FromSockAddr(storage.addr, storage.addr_len)) - return ERR_FAILED; - - return OK; -} - -void TCPClientSocketLibevent::RecordFastOpenStatus() { - if (use_tcp_fastopen_ && - (fast_open_status_ == FAST_OPEN_FAST_CONNECT_RETURN || - fast_open_status_ == FAST_OPEN_SLOW_CONNECT_RETURN)) { - DCHECK_NE(FAST_OPEN_STATUS_UNKNOWN, fast_open_status_); - bool getsockopt_success(false); - bool server_acked_data(false); -#if defined(TCP_INFO) - // Probe to see the if the socket used TCP Fast Open. - tcp_info info; - socklen_t info_len = sizeof(tcp_info); - getsockopt_success = - getsockopt(socket_, IPPROTO_TCP, TCP_INFO, &info, &info_len) == 0 && - info_len == sizeof(tcp_info); - server_acked_data = getsockopt_success && - (info.tcpi_options & TCPI_OPT_SYN_DATA); -#endif - if (getsockopt_success) { - if (fast_open_status_ == FAST_OPEN_FAST_CONNECT_RETURN) { - fast_open_status_ = (server_acked_data ? FAST_OPEN_SYN_DATA_ACK : - FAST_OPEN_SYN_DATA_NACK); - } else { - fast_open_status_ = (server_acked_data ? FAST_OPEN_NO_SYN_DATA_ACK : - FAST_OPEN_NO_SYN_DATA_NACK); - } - } else { - fast_open_status_ = (fast_open_status_ == FAST_OPEN_FAST_CONNECT_RETURN ? - FAST_OPEN_SYN_DATA_FAILED : - FAST_OPEN_NO_SYN_DATA_FAILED); - } - } -} - -const BoundNetLog& TCPClientSocketLibevent::NetLog() const { - return net_log_; -} - -void TCPClientSocketLibevent::SetSubresourceSpeculation() { - use_history_.set_subresource_speculation(); -} - -void TCPClientSocketLibevent::SetOmniboxSpeculation() { - use_history_.set_omnibox_speculation(); -} - -bool TCPClientSocketLibevent::WasEverUsed() const { - return use_history_.was_used_to_convey_data(); -} - -bool TCPClientSocketLibevent::UsingTCPFastOpen() const { - return use_tcp_fastopen_; -} - -bool TCPClientSocketLibevent::WasNpnNegotiated() const { - return false; -} - -NextProto TCPClientSocketLibevent::GetNegotiatedProtocol() const { - return kProtoUnknown; -} - -bool TCPClientSocketLibevent::GetSSLInfo(SSLInfo* ssl_info) { - return false; -} - -} // namespace net diff --git a/net/socket/tcp_client_socket_libevent.h b/net/socket/tcp_client_socket_libevent.h deleted file mode 100644 index e5a0d8d..0000000 --- a/net/socket/tcp_client_socket_libevent.h +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef NET_SOCKET_TCP_CLIENT_SOCKET_LIBEVENT_H_ -#define NET_SOCKET_TCP_CLIENT_SOCKET_LIBEVENT_H_ - -#include "base/memory/ref_counted.h" -#include "base/memory/scoped_ptr.h" -#include "base/message_loop/message_loop.h" -#include "base/threading/non_thread_safe.h" -#include "net/base/address_list.h" -#include "net/base/completion_callback.h" -#include "net/base/net_log.h" -#include "net/socket/stream_socket.h" - -namespace net { - -class BoundNetLog; - -// A client socket that uses TCP as the transport layer. -class NET_EXPORT_PRIVATE TCPClientSocketLibevent : public StreamSocket, - public base::NonThreadSafe { - public: - // The IP address(es) and port number to connect to. The TCP socket will try - // each IP address in the list until it succeeds in establishing a - // connection. - TCPClientSocketLibevent(const AddressList& addresses, - net::NetLog* net_log, - const net::NetLog::Source& source); - - virtual ~TCPClientSocketLibevent(); - - // AdoptSocket causes the given, connected socket to be adopted as a TCP - // socket. This object must not be connected. This object takes ownership of - // the given socket and then acts as if Connect() had been called. This - // function is used by TCPServerSocket() to adopt accepted connections - // and for testing. - int AdoptSocket(int socket); - - // Binds the socket to a local IP address and port. - int Bind(const IPEndPoint& address); - - // StreamSocket implementation. - virtual int Connect(const CompletionCallback& callback) OVERRIDE; - virtual void Disconnect() OVERRIDE; - virtual bool IsConnected() const OVERRIDE; - virtual bool IsConnectedAndIdle() const OVERRIDE; - virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE; - virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE; - virtual const BoundNetLog& NetLog() const OVERRIDE; - virtual void SetSubresourceSpeculation() OVERRIDE; - virtual void SetOmniboxSpeculation() OVERRIDE; - virtual bool WasEverUsed() const OVERRIDE; - virtual bool UsingTCPFastOpen() const OVERRIDE; - virtual bool WasNpnNegotiated() const OVERRIDE; - virtual NextProto GetNegotiatedProtocol() const OVERRIDE; - virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE; - - // Socket implementation. - // Multiple outstanding requests are not supported. - // Full duplex mode (reading and writing at the same time) is supported - virtual int Read(IOBuffer* buf, - int buf_len, - const CompletionCallback& callback) OVERRIDE; - virtual int Write(IOBuffer* buf, - int buf_len, - const CompletionCallback& callback) OVERRIDE; - virtual bool SetReceiveBufferSize(int32 size) OVERRIDE; - virtual bool SetSendBufferSize(int32 size) OVERRIDE; - - virtual bool SetKeepAlive(bool enable, int delay); - virtual bool SetNoDelay(bool no_delay); - - private: - // State machine for connecting the socket. - enum ConnectState { - CONNECT_STATE_CONNECT, - CONNECT_STATE_CONNECT_COMPLETE, - CONNECT_STATE_NONE, - }; - - // States that a fast open socket attempt can result in. - enum FastOpenStatus { - FAST_OPEN_STATUS_UNKNOWN, - - // The initial fast open connect attempted returned synchronously, - // indicating that we had and sent a cookie along with the initial data. - FAST_OPEN_FAST_CONNECT_RETURN, - - // The initial fast open connect attempted returned asynchronously, - // indicating that we did not have a cookie for the server. - FAST_OPEN_SLOW_CONNECT_RETURN, - - // Some other error occurred on connection, so we couldn't tell if - // fast open would have worked. - FAST_OPEN_ERROR, - - // An attempt to do a fast open succeeded immediately - // (FAST_OPEN_FAST_CONNECT_RETURN) and we later confirmed that the server - // had acked the data we sent. - FAST_OPEN_SYN_DATA_ACK, - - // An attempt to do a fast open succeeded immediately - // (FAST_OPEN_FAST_CONNECT_RETURN) and we later confirmed that the server - // had nacked the data we sent. - FAST_OPEN_SYN_DATA_NACK, - - // An attempt to do a fast open succeeded immediately - // (FAST_OPEN_FAST_CONNECT_RETURN) and our probe to determine if the - // socket was using fast open failed. - FAST_OPEN_SYN_DATA_FAILED, - - // An attempt to do a fast open failed (FAST_OPEN_SLOW_CONNECT_RETURN) - // and we later confirmed that the server had acked initial data. This - // should never happen (we didn't send data, so it shouldn't have - // been acked). - FAST_OPEN_NO_SYN_DATA_ACK, - - // An attempt to do a fast open failed (FAST_OPEN_SLOW_CONNECT_RETURN) - // and we later discovered that the server had nacked initial data. This - // is the expected case results for FAST_OPEN_SLOW_CONNECT_RETURN. - FAST_OPEN_NO_SYN_DATA_NACK, - - // An attempt to do a fast open failed (FAST_OPEN_SLOW_CONNECT_RETURN) - // and our later probe for ack/nack state failed. - FAST_OPEN_NO_SYN_DATA_FAILED, - - FAST_OPEN_MAX_VALUE - }; - - class ReadWatcher : public base::MessageLoopForIO::Watcher { - public: - explicit ReadWatcher(TCPClientSocketLibevent* socket) : socket_(socket) {} - - // MessageLoopForIO::Watcher methods - - virtual void OnFileCanReadWithoutBlocking(int /* fd */) OVERRIDE; - - virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE {} - - private: - TCPClientSocketLibevent* const socket_; - - DISALLOW_COPY_AND_ASSIGN(ReadWatcher); - }; - - class WriteWatcher : public base::MessageLoopForIO::Watcher { - public: - explicit WriteWatcher(TCPClientSocketLibevent* socket) : socket_(socket) {} - - // MessageLoopForIO::Watcher implementation. - virtual void OnFileCanReadWithoutBlocking(int /* fd */) OVERRIDE {} - virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE; - - private: - TCPClientSocketLibevent* const socket_; - - DISALLOW_COPY_AND_ASSIGN(WriteWatcher); - }; - - // State machine used by Connect(). - int DoConnectLoop(int result); - int DoConnect(); - int DoConnectComplete(int result); - - // Helper used by Disconnect(), which disconnects minus the logging and - // resetting of current_address_index_. - void DoDisconnect(); - - void DoReadCallback(int rv); - void DoWriteCallback(int rv); - void DidCompleteRead(); - void DidCompleteWrite(); - void DidCompleteConnect(); - - // Returns true if a Connect() is in progress. - bool waiting_connect() const { - return next_connect_state_ != CONNECT_STATE_NONE; - } - - // Helper to add a TCP_CONNECT (end) event to the NetLog. - void LogConnectCompletion(int net_error); - - // Internal function to write to a socket. - int InternalWrite(IOBuffer* buf, int buf_len); - - // Called when the socket is known to be in a connected state. - void RecordFastOpenStatus(); - - int socket_; - - // Local IP address and port we are bound to. Set to NULL if Bind() - // was't called (in that cases OS chooses address/port). - scoped_ptr<IPEndPoint> bind_address_; - - // Stores bound socket between Bind() and Connect() calls. - int bound_socket_; - - // The list of addresses we should try in order to establish a connection. - AddressList addresses_; - - // Where we are in above list. Set to -1 if uninitialized. - int current_address_index_; - - // The socket's libevent wrappers - base::MessageLoopForIO::FileDescriptorWatcher read_socket_watcher_; - base::MessageLoopForIO::FileDescriptorWatcher write_socket_watcher_; - - // The corresponding watchers for reads and writes. - ReadWatcher read_watcher_; - WriteWatcher write_watcher_; - - // The buffer used by OnSocketReady to retry Read requests - scoped_refptr<IOBuffer> read_buf_; - int read_buf_len_; - - // The buffer used by OnSocketReady to retry Write requests - scoped_refptr<IOBuffer> write_buf_; - int write_buf_len_; - - // External callback; called when read is complete. - CompletionCallback read_callback_; - - // External callback; called when write is complete. - CompletionCallback write_callback_; - - // The next state for the Connect() state machine. - ConnectState next_connect_state_; - - // The OS error that CONNECT_STATE_CONNECT last completed with. - int connect_os_error_; - - BoundNetLog net_log_; - - // This socket was previously disconnected and has not been re-connected. - bool previously_disconnected_; - - // Record of connectivity and transmissions, for use in speculative connection - // histograms. - UseHistory use_history_; - - // Enables experimental TCP FastOpen option. - const bool use_tcp_fastopen_; - - // True when TCP FastOpen is in use and we have done the connect. - bool tcp_fastopen_connected_; - - enum FastOpenStatus fast_open_status_; - - DISALLOW_COPY_AND_ASSIGN(TCPClientSocketLibevent); -}; - -} // namespace net - -#endif // NET_SOCKET_TCP_CLIENT_SOCKET_LIBEVENT_H_ diff --git a/net/socket/tcp_server_socket.cc b/net/socket/tcp_server_socket.cc index 63cb2cf..a25f73f 100644 --- a/net/socket/tcp_server_socket.cc +++ b/net/socket/tcp_server_socket.cc @@ -7,7 +7,6 @@ #include "base/bind.h" #include "base/bind_helpers.h" #include "base/logging.h" -#include "build/build_config.h" #include "net/base/net_errors.h" #include "net/socket/tcp_client_socket.h" @@ -88,27 +87,9 @@ int TCPServerSocket::ConvertAcceptedSocket( if (result != OK) return result; - // TODO(yzshen): Once we switch TCPClientSocketLibevent to take a connected - // TCPSocket object, we don't need to do platform-specific handling. -#if defined(OS_WIN) - scoped_ptr<TCPClientSocket> client_socket(new TCPClientSocket( + output_accepted_socket->reset(new TCPClientSocket( temp_accepted_socket.Pass(), accepted_address_)); -#elif defined(OS_POSIX) - scoped_ptr<TCPClientSocket> client_socket(new TCPClientSocket( - AddressList(accepted_address_), - temp_accepted_socket->net_log().net_log(), - temp_accepted_socket->net_log().source())); - int raw_socket = temp_accepted_socket->Release(); - result = client_socket->AdoptSocket(raw_socket); - if (result != OK) { - // |client_socket| won't take ownership of |raw_socket| on failure. - // Therefore, we put it back into |temp_accepted_socket| to close it. - temp_accepted_socket->Adopt(raw_socket); - return result; - } -#endif - *output_accepted_socket = client_socket.Pass(); return OK; } diff --git a/net/socket/tcp_socket.cc b/net/socket/tcp_socket.cc new file mode 100644 index 0000000..fd72f6b --- /dev/null +++ b/net/socket/tcp_socket.cc @@ -0,0 +1,59 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/socket/tcp_socket.h" + +#include "base/file_util.h" +#include "base/files/file_path.h" + +namespace net { + +namespace { + +#if defined(OS_LINUX) + +// Checks to see if the system supports TCP FastOpen. Notably, it requires +// kernel support. Additionally, this checks system configuration to ensure that +// it's enabled. +bool SystemSupportsTCPFastOpen() { + static const base::FilePath::CharType kTCPFastOpenProcFilePath[] = + "/proc/sys/net/ipv4/tcp_fastopen"; + std::string system_enabled_tcp_fastopen; + if (!base::ReadFileToString( + base::FilePath(kTCPFastOpenProcFilePath), + &system_enabled_tcp_fastopen)) { + return false; + } + + // As per http://lxr.linux.no/linux+v3.7.7/include/net/tcp.h#L225 + // TFO_CLIENT_ENABLE is the LSB + if (system_enabled_tcp_fastopen.empty() || + (system_enabled_tcp_fastopen[0] & 0x1) == 0) { + return false; + } + + return true; +} + +#else + +bool SystemSupportsTCPFastOpen() { + return false; +} + +#endif + +bool g_tcp_fastopen_enabled = false; + +} // namespace + +void SetTCPFastOpenEnabled(bool value) { + g_tcp_fastopen_enabled = value && SystemSupportsTCPFastOpen(); +} + +bool IsTCPFastOpenEnabled() { + return g_tcp_fastopen_enabled; +} + +} // namespace net diff --git a/net/socket/tcp_socket.h b/net/socket/tcp_socket.h index aea8f12..8b36fad 100644 --- a/net/socket/tcp_socket.h +++ b/net/socket/tcp_socket.h @@ -6,6 +6,7 @@ #define NET_SOCKET_TCP_SOCKET_H_ #include "build/build_config.h" +#include "net/base/net_export.h" #if defined(OS_WIN) #include "net/socket/tcp_socket_win.h" @@ -15,6 +16,13 @@ namespace net { +// Enable/disable experimental TCP FastOpen option. +// Not thread safe. Must be called during initialization/startup only. +NET_EXPORT void SetTCPFastOpenEnabled(bool value); + +// Check if the TCP FastOpen option is enabled. +bool IsTCPFastOpenEnabled(); + // TCPSocket provides a platform-independent interface for TCP sockets. // // It is recommended to use TCPClientSocket/TCPServerSocket instead of this diff --git a/net/socket/tcp_socket_libevent.cc b/net/socket/tcp_socket_libevent.cc index 4861133..66416f7 100644 --- a/net/socket/tcp_socket_libevent.cc +++ b/net/socket/tcp_socket_libevent.cc @@ -2,43 +2,156 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "net/socket/tcp_socket_libevent.h" +#include "net/socket/tcp_socket.h" #include <errno.h> #include <fcntl.h> #include <netdb.h> -#include <sys/socket.h> - -#include "build/build_config.h" - -#if defined(OS_POSIX) #include <netinet/in.h> -#endif +#include <netinet/tcp.h> +#include <sys/socket.h> +#include "base/callback_helpers.h" #include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/metrics/stats_counters.h" #include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" +#include "net/base/address_list.h" +#include "net/base/connection_type_histograms.h" +#include "net/base/io_buffer.h" #include "net/base/ip_endpoint.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" -#include "net/socket/socket_descriptor.h" +#include "net/base/network_change_notifier.h" #include "net/socket/socket_net_log_params.h" +// If we don't have a definition for TCPI_OPT_SYN_DATA, create one. +#ifndef TCPI_OPT_SYN_DATA +#define TCPI_OPT_SYN_DATA 32 +#endif + namespace net { +namespace { + +const int kTCPKeepAliveSeconds = 45; + +// SetTCPNoDelay turns on/off buffering in the kernel. By default, TCP sockets +// will wait up to 200ms for more data to complete a packet before transmitting. +// After calling this function, the kernel will not wait. See TCP_NODELAY in +// `man 7 tcp`. +bool SetTCPNoDelay(int fd, bool no_delay) { + int on = no_delay ? 1 : 0; + int error = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); + return error == 0; +} + +// SetTCPKeepAlive sets SO_KEEPALIVE. +bool SetTCPKeepAlive(int fd, bool enable, int delay) { + int on = enable ? 1 : 0; + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on))) { + PLOG(ERROR) << "Failed to set SO_KEEPALIVE on fd: " << fd; + return false; + } +#if defined(OS_LINUX) || defined(OS_ANDROID) + // Set seconds until first TCP keep alive. + if (setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &delay, sizeof(delay))) { + PLOG(ERROR) << "Failed to set TCP_KEEPIDLE on fd: " << fd; + return false; + } + // Set seconds between TCP keep alives. + if (setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &delay, sizeof(delay))) { + PLOG(ERROR) << "Failed to set TCP_KEEPINTVL on fd: " << fd; + return false; + } +#endif + return true; +} + +int MapConnectError(int os_error) { + switch (os_error) { + case EACCES: + return ERR_NETWORK_ACCESS_DENIED; + case ETIMEDOUT: + return ERR_CONNECTION_TIMED_OUT; + default: { + int net_error = MapSystemError(os_error); + if (net_error == ERR_FAILED) + return ERR_CONNECTION_FAILED; // More specific than ERR_FAILED. + + // Give a more specific error when the user is offline. + if (net_error == ERR_ADDRESS_UNREACHABLE && + NetworkChangeNotifier::IsOffline()) { + return ERR_INTERNET_DISCONNECTED; + } + return net_error; + } + } +} + +} // namespace + +//----------------------------------------------------------------------------- + +TCPSocketLibevent::Watcher::Watcher( + const base::Closure& read_ready_callback, + const base::Closure& write_ready_callback) + : read_ready_callback_(read_ready_callback), + write_ready_callback_(write_ready_callback) { +} + +TCPSocketLibevent::Watcher::~Watcher() { +} + +void TCPSocketLibevent::Watcher::OnFileCanReadWithoutBlocking(int /* fd */) { + if (!read_ready_callback_.is_null()) + read_ready_callback_.Run(); + else + NOTREACHED(); +} + +void TCPSocketLibevent::Watcher::OnFileCanWriteWithoutBlocking(int /* fd */) { + if (!write_ready_callback_.is_null()) + write_ready_callback_.Run(); + else + NOTREACHED(); +} + TCPSocketLibevent::TCPSocketLibevent(NetLog* net_log, const NetLog::Source& source) : socket_(kInvalidSocket), + accept_watcher_(base::Bind(&TCPSocketLibevent::DidCompleteAccept, + base::Unretained(this)), + base::Closure()), accept_socket_(NULL), accept_address_(NULL), + read_watcher_(base::Bind(&TCPSocketLibevent::DidCompleteRead, + base::Unretained(this)), + base::Closure()), + write_watcher_(base::Closure(), + base::Bind(&TCPSocketLibevent::DidCompleteConnectOrWrite, + base::Unretained(this))), + read_buf_len_(0), + write_buf_len_(0), + use_tcp_fastopen_(IsTCPFastOpenEnabled()), + tcp_fastopen_connected_(false), + fast_open_status_(FAST_OPEN_STATUS_UNKNOWN), + waiting_connect_(false), + connect_os_error_(0), + logging_multiple_connect_attempts_(false), net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SOCKET)) { net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE, source.ToEventParametersCallback()); } TCPSocketLibevent::~TCPSocketLibevent() { - if (socket_ != kInvalidSocket) - Close(); net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE); + if (tcp_fastopen_connected_) { + UMA_HISTOGRAM_ENUMERATION("Net.TcpFastOpenSocketConnection", + fast_open_status_, FAST_OPEN_MAX_VALUE); + } + Close(); } int TCPSocketLibevent::Open(AddressFamily family) { @@ -61,7 +174,8 @@ int TCPSocketLibevent::Open(AddressFamily family) { return OK; } -int TCPSocketLibevent::Adopt(int socket) { +int TCPSocketLibevent::AdoptConnectedSocket(int socket, + const IPEndPoint& peer_address) { DCHECK(CalledOnValidThread()); DCHECK_EQ(socket_, kInvalidSocket); @@ -73,16 +187,9 @@ int TCPSocketLibevent::Adopt(int socket) { return result; } - return OK; -} - -int TCPSocketLibevent::Release() { - DCHECK(CalledOnValidThread()); - DCHECK(accept_callback_.is_null()); + peer_address_.reset(new IPEndPoint(peer_address)); - int result = socket_; - socket_ = kInvalidSocket; - return result; + return OK; } int TCPSocketLibevent::Bind(const IPEndPoint& address) { @@ -102,19 +209,6 @@ int TCPSocketLibevent::Bind(const IPEndPoint& address) { return OK; } -int TCPSocketLibevent::GetLocalAddress(IPEndPoint* address) const { - DCHECK(CalledOnValidThread()); - DCHECK(address); - - SockaddrStorage storage; - if (getsockname(socket_, storage.addr, &storage.addr_len) < 0) - return MapSystemError(errno); - if (!address->FromSockAddr(storage.addr, storage.addr_len)) - return ERR_FAILED; - - return OK; -} - int TCPSocketLibevent::Listen(int backlog) { DCHECK(CalledOnValidThread()); DCHECK_GT(backlog, 0); @@ -145,7 +239,7 @@ int TCPSocketLibevent::Accept(scoped_ptr<TCPSocketLibevent>* socket, if (result == ERR_IO_PENDING) { if (!base::MessageLoopForIO::current()->WatchFileDescriptor( socket_, true, base::MessageLoopForIO::WATCH_READ, - &accept_socket_watcher_, this)) { + &accept_socket_watcher_, &accept_watcher_)) { PLOG(ERROR) << "WatchFileDescriptor failed on read"; return MapSystemError(errno); } @@ -158,11 +252,201 @@ int TCPSocketLibevent::Accept(scoped_ptr<TCPSocketLibevent>* socket, return result; } +int TCPSocketLibevent::Connect(const IPEndPoint& address, + const CompletionCallback& callback) { + DCHECK(CalledOnValidThread()); + DCHECK_NE(socket_, kInvalidSocket); + DCHECK(!waiting_connect_); + + // |peer_address_| will be non-NULL if Connect() has been called. Unless + // Close() is called to reset the internal state, a second call to Connect() + // is not allowed. + // Please note that we don't allow a second Connect() even if the previous + // Connect() has failed. Connecting the same |socket_| again after a + // connection attempt failed results in unspecified behavior according to + // POSIX. + DCHECK(!peer_address_); + + if (!logging_multiple_connect_attempts_) + LogConnectBegin(AddressList(address)); + + peer_address_.reset(new IPEndPoint(address)); + + int rv = DoConnect(); + if (rv == ERR_IO_PENDING) { + // Synchronous operation not supported. + DCHECK(!callback.is_null()); + write_callback_ = callback; + waiting_connect_ = true; + } else { + DoConnectComplete(rv); + } + + return rv; +} + +bool TCPSocketLibevent::IsConnected() const { + DCHECK(CalledOnValidThread()); + + if (socket_ == kInvalidSocket || waiting_connect_) + return false; + + if (use_tcp_fastopen_ && !tcp_fastopen_connected_ && peer_address_) { + // With TCP FastOpen, we pretend that the socket is connected. + // This allows GetPeerAddress() to return peer_address_. + return true; + } + + // Check if connection is alive. + char c; + int rv = HANDLE_EINTR(recv(socket_, &c, 1, MSG_PEEK)); + if (rv == 0) + return false; + if (rv == -1 && errno != EAGAIN && errno != EWOULDBLOCK) + return false; + + return true; +} + +bool TCPSocketLibevent::IsConnectedAndIdle() const { + DCHECK(CalledOnValidThread()); + + if (socket_ == kInvalidSocket || waiting_connect_) + return false; + + // TODO(wtc): should we also handle the TCP FastOpen case here, + // as we do in IsConnected()? + + // Check if connection is alive and we haven't received any data + // unexpectedly. + char c; + int rv = HANDLE_EINTR(recv(socket_, &c, 1, MSG_PEEK)); + if (rv >= 0) + return false; + if (errno != EAGAIN && errno != EWOULDBLOCK) + return false; + + return true; +} + +int TCPSocketLibevent::Read(IOBuffer* buf, + int buf_len, + const CompletionCallback& callback) { + DCHECK(CalledOnValidThread()); + DCHECK_NE(kInvalidSocket, socket_); + DCHECK(!waiting_connect_); + DCHECK(read_callback_.is_null()); + // Synchronous operation not supported + DCHECK(!callback.is_null()); + DCHECK_GT(buf_len, 0); + + int nread = HANDLE_EINTR(read(socket_, buf->data(), buf_len)); + if (nread >= 0) { + base::StatsCounter read_bytes("tcp.read_bytes"); + read_bytes.Add(nread); + net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, nread, + buf->data()); + RecordFastOpenStatus(); + return nread; + } + if (errno != EAGAIN && errno != EWOULDBLOCK) { + int net_error = MapSystemError(errno); + net_log_.AddEvent(NetLog::TYPE_SOCKET_READ_ERROR, + CreateNetLogSocketErrorCallback(net_error, errno)); + return net_error; + } + + if (!base::MessageLoopForIO::current()->WatchFileDescriptor( + socket_, true, base::MessageLoopForIO::WATCH_READ, + &read_socket_watcher_, &read_watcher_)) { + DVLOG(1) << "WatchFileDescriptor failed on read, errno " << errno; + return MapSystemError(errno); + } + + read_buf_ = buf; + read_buf_len_ = buf_len; + read_callback_ = callback; + return ERR_IO_PENDING; +} + +int TCPSocketLibevent::Write(IOBuffer* buf, + int buf_len, + const CompletionCallback& callback) { + DCHECK(CalledOnValidThread()); + DCHECK_NE(kInvalidSocket, socket_); + DCHECK(!waiting_connect_); + DCHECK(write_callback_.is_null()); + // Synchronous operation not supported + DCHECK(!callback.is_null()); + DCHECK_GT(buf_len, 0); + + int nwrite = InternalWrite(buf, buf_len); + if (nwrite >= 0) { + base::StatsCounter write_bytes("tcp.write_bytes"); + write_bytes.Add(nwrite); + net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, nwrite, + buf->data()); + return nwrite; + } + if (errno != EAGAIN && errno != EWOULDBLOCK) { + int net_error = MapSystemError(errno); + net_log_.AddEvent(NetLog::TYPE_SOCKET_WRITE_ERROR, + CreateNetLogSocketErrorCallback(net_error, errno)); + return net_error; + } + + if (!base::MessageLoopForIO::current()->WatchFileDescriptor( + socket_, true, base::MessageLoopForIO::WATCH_WRITE, + &write_socket_watcher_, &write_watcher_)) { + DVLOG(1) << "WatchFileDescriptor failed on write, errno " << errno; + return MapSystemError(errno); + } + + write_buf_ = buf; + write_buf_len_ = buf_len; + write_callback_ = callback; + return ERR_IO_PENDING; +} + +int TCPSocketLibevent::GetLocalAddress(IPEndPoint* address) const { + DCHECK(CalledOnValidThread()); + DCHECK(address); + + SockaddrStorage storage; + if (getsockname(socket_, storage.addr, &storage.addr_len) < 0) + return MapSystemError(errno); + if (!address->FromSockAddr(storage.addr, storage.addr_len)) + return ERR_ADDRESS_INVALID; + + return OK; +} + +int TCPSocketLibevent::GetPeerAddress(IPEndPoint* address) const { + DCHECK(CalledOnValidThread()); + DCHECK(address); + if (!IsConnected()) + return ERR_SOCKET_NOT_CONNECTED; + *address = *peer_address_; + return OK; +} + int TCPSocketLibevent::SetDefaultOptionsForServer() { + DCHECK(CalledOnValidThread()); return SetAddressReuse(true); } +void TCPSocketLibevent::SetDefaultOptionsForClient() { + DCHECK(CalledOnValidThread()); + + // This mirrors the behaviour on Windows. See the comment in + // tcp_socket_win.cc after searching for "NODELAY". + SetTCPNoDelay(socket_, true); // If SetTCPNoDelay fails, we don't care. + SetTCPKeepAlive(socket_, true, kTCPKeepAliveSeconds); +} + int TCPSocketLibevent::SetAddressReuse(bool allow) { + DCHECK(CalledOnValidThread()); + // SO_REUSEADDR is useful for server sockets to bind to a recently unbound // port. When a socket is closed, the end point changes its state to TIME_WAIT // and wait for 2 MSL (maximum segment lifetime) to ensure the remote peer @@ -184,14 +468,96 @@ int TCPSocketLibevent::SetAddressReuse(bool allow) { return OK; } +bool TCPSocketLibevent::SetReceiveBufferSize(int32 size) { + DCHECK(CalledOnValidThread()); + int rv = setsockopt(socket_, SOL_SOCKET, SO_RCVBUF, + reinterpret_cast<const char*>(&size), + sizeof(size)); + DCHECK(!rv) << "Could not set socket receive buffer size: " << errno; + return rv == 0; +} + +bool TCPSocketLibevent::SetSendBufferSize(int32 size) { + DCHECK(CalledOnValidThread()); + int rv = setsockopt(socket_, SOL_SOCKET, SO_SNDBUF, + reinterpret_cast<const char*>(&size), + sizeof(size)); + DCHECK(!rv) << "Could not set socket send buffer size: " << errno; + return rv == 0; +} + +bool TCPSocketLibevent::SetKeepAlive(bool enable, int delay) { + DCHECK(CalledOnValidThread()); + return SetTCPKeepAlive(socket_, enable, delay); +} + +bool TCPSocketLibevent::SetNoDelay(bool no_delay) { + DCHECK(CalledOnValidThread()); + return SetTCPNoDelay(socket_, no_delay); +} + void TCPSocketLibevent::Close() { + DCHECK(CalledOnValidThread()); + + bool ok = accept_socket_watcher_.StopWatchingFileDescriptor(); + DCHECK(ok); + ok = read_socket_watcher_.StopWatchingFileDescriptor(); + DCHECK(ok); + ok = write_socket_watcher_.StopWatchingFileDescriptor(); + DCHECK(ok); + if (socket_ != kInvalidSocket) { - bool ok = accept_socket_watcher_.StopWatchingFileDescriptor(); - DCHECK(ok); if (HANDLE_EINTR(close(socket_)) < 0) PLOG(ERROR) << "close"; socket_ = kInvalidSocket; } + + if (!accept_callback_.is_null()) { + accept_socket_ = NULL; + accept_address_ = NULL; + accept_callback_.Reset(); + } + + if (!read_callback_.is_null()) { + read_buf_ = NULL; + read_buf_len_ = 0; + read_callback_.Reset(); + } + + if (!write_callback_.is_null()) { + write_buf_ = NULL; + write_buf_len_ = 0; + write_callback_.Reset(); + } + + tcp_fastopen_connected_ = false; + fast_open_status_ = FAST_OPEN_STATUS_UNKNOWN; + waiting_connect_ = false; + peer_address_.reset(); + connect_os_error_ = 0; +} + +bool TCPSocketLibevent::UsingTCPFastOpen() const { + return use_tcp_fastopen_; +} + +void TCPSocketLibevent::StartLoggingMultipleConnectAttempts( + const AddressList& addresses) { + if (!logging_multiple_connect_attempts_) { + logging_multiple_connect_attempts_ = true; + LogConnectBegin(addresses); + } else { + NOTREACHED(); + } +} + +void TCPSocketLibevent::EndLoggingMultipleConnectAttempts(int net_error) { + if (logging_multiple_connect_attempts_) { + LogConnectEnd(net_error); + logging_multiple_connect_attempts_ = false; + } else { + NOTREACHED(); + } } int TCPSocketLibevent::AcceptInternal(scoped_ptr<TCPSocketLibevent>* socket, @@ -212,12 +578,13 @@ int TCPSocketLibevent::AcceptInternal(scoped_ptr<TCPSocketLibevent>* socket, NOTREACHED(); if (HANDLE_EINTR(close(new_socket)) < 0) PLOG(ERROR) << "close"; - net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_ACCEPT, ERR_FAILED); - return ERR_FAILED; + net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_ACCEPT, + ERR_ADDRESS_INVALID); + return ERR_ADDRESS_INVALID; } scoped_ptr<TCPSocketLibevent> tcp_socket(new TCPSocketLibevent( net_log_.net_log(), net_log_.source())); - int adopt_result = tcp_socket->Adopt(new_socket); + int adopt_result = tcp_socket->AdoptConnectedSocket(new_socket, ip_end_point); if (adopt_result != OK) { net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_ACCEPT, adopt_result); return adopt_result; @@ -229,7 +596,183 @@ int TCPSocketLibevent::AcceptInternal(scoped_ptr<TCPSocketLibevent>* socket, return OK; } -void TCPSocketLibevent::OnFileCanReadWithoutBlocking(int fd) { +int TCPSocketLibevent::DoConnect() { + DCHECK_EQ(0, connect_os_error_); + + net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT, + CreateNetLogIPEndPointCallback(peer_address_.get())); + + // Connect the socket. + if (!use_tcp_fastopen_) { + SockaddrStorage storage; + if (!peer_address_->ToSockAddr(storage.addr, &storage.addr_len)) + return ERR_INVALID_ARGUMENT; + + if (!HANDLE_EINTR(connect(socket_, storage.addr, storage.addr_len))) { + // Connected without waiting! + return OK; + } + } else { + // With TCP FastOpen, we pretend that the socket is connected. + DCHECK(!tcp_fastopen_connected_); + return OK; + } + + // Check if the connect() failed synchronously. + connect_os_error_ = errno; + if (connect_os_error_ != EINPROGRESS) + return MapConnectError(connect_os_error_); + + // Otherwise the connect() is going to complete asynchronously, so watch + // for its completion. + if (!base::MessageLoopForIO::current()->WatchFileDescriptor( + socket_, true, base::MessageLoopForIO::WATCH_WRITE, + &write_socket_watcher_, &write_watcher_)) { + connect_os_error_ = errno; + DVLOG(1) << "WatchFileDescriptor failed: " << connect_os_error_; + return MapSystemError(connect_os_error_); + } + + return ERR_IO_PENDING; +} + +void TCPSocketLibevent::DoConnectComplete(int result) { + // Log the end of this attempt (and any OS error it threw). + int os_error = connect_os_error_; + connect_os_error_ = 0; + if (result != OK) { + net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT, + NetLog::IntegerCallback("os_error", os_error)); + } else { + net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT); + } + + if (!logging_multiple_connect_attempts_) + LogConnectEnd(result); +} + +void TCPSocketLibevent::LogConnectBegin(const AddressList& addresses) { + base::StatsCounter connects("tcp.connect"); + connects.Increment(); + + net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT, + addresses.CreateNetLogCallback()); +} + +void TCPSocketLibevent::LogConnectEnd(int net_error) { + if (net_error == OK) + UpdateConnectionTypeHistograms(CONNECTION_ANY); + + if (net_error != OK) { + net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_CONNECT, net_error); + return; + } + + SockaddrStorage storage; + int rv = getsockname(socket_, storage.addr, &storage.addr_len); + if (rv != 0) { + PLOG(ERROR) << "getsockname() [rv: " << rv << "] error: "; + NOTREACHED(); + net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_CONNECT, rv); + return; + } + + net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT, + CreateNetLogSourceAddressCallback(storage.addr, + storage.addr_len)); +} + +void TCPSocketLibevent::DidCompleteRead() { + RecordFastOpenStatus(); + if (read_callback_.is_null()) + return; + + int bytes_transferred; + bytes_transferred = HANDLE_EINTR(read(socket_, read_buf_->data(), + read_buf_len_)); + + int result; + if (bytes_transferred >= 0) { + result = bytes_transferred; + base::StatsCounter read_bytes("tcp.read_bytes"); + read_bytes.Add(bytes_transferred); + net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, result, + read_buf_->data()); + } else { + result = MapSystemError(errno); + if (result != ERR_IO_PENDING) { + net_log_.AddEvent(NetLog::TYPE_SOCKET_READ_ERROR, + CreateNetLogSocketErrorCallback(result, errno)); + } + } + + if (result != ERR_IO_PENDING) { + read_buf_ = NULL; + read_buf_len_ = 0; + bool ok = read_socket_watcher_.StopWatchingFileDescriptor(); + DCHECK(ok); + base::ResetAndReturn(&read_callback_).Run(result); + } +} + +void TCPSocketLibevent::DidCompleteWrite() { + if (write_callback_.is_null()) + return; + + int bytes_transferred; + bytes_transferred = HANDLE_EINTR(write(socket_, write_buf_->data(), + write_buf_len_)); + + int result; + if (bytes_transferred >= 0) { + result = bytes_transferred; + base::StatsCounter write_bytes("tcp.write_bytes"); + write_bytes.Add(bytes_transferred); + net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, result, + write_buf_->data()); + } else { + result = MapSystemError(errno); + if (result != ERR_IO_PENDING) { + net_log_.AddEvent(NetLog::TYPE_SOCKET_WRITE_ERROR, + CreateNetLogSocketErrorCallback(result, errno)); + } + } + + if (result != ERR_IO_PENDING) { + write_buf_ = NULL; + write_buf_len_ = 0; + write_socket_watcher_.StopWatchingFileDescriptor(); + base::ResetAndReturn(&write_callback_).Run(result); + } +} + +void TCPSocketLibevent::DidCompleteConnect() { + DCHECK(waiting_connect_); + + // Get the error that connect() completed with. + int os_error = 0; + socklen_t len = sizeof(os_error); + if (getsockopt(socket_, SOL_SOCKET, SO_ERROR, &os_error, &len) < 0) + os_error = errno; + + int result = MapConnectError(os_error); + connect_os_error_ = os_error; + if (result != ERR_IO_PENDING) { + DoConnectComplete(result); + waiting_connect_ = false; + write_socket_watcher_.StopWatchingFileDescriptor(); + base::ResetAndReturn(&write_callback_).Run(result); + } +} + +void TCPSocketLibevent::DidCompleteConnectOrWrite() { + if (waiting_connect_) + DidCompleteConnect(); + else + DidCompleteWrite(); +} + +void TCPSocketLibevent::DidCompleteAccept() { DCHECK(CalledOnValidThread()); int result = AcceptInternal(accept_socket_, accept_address_); @@ -244,8 +787,85 @@ void TCPSocketLibevent::OnFileCanReadWithoutBlocking(int fd) { } } -void TCPSocketLibevent::OnFileCanWriteWithoutBlocking(int fd) { - NOTREACHED(); +int TCPSocketLibevent::InternalWrite(IOBuffer* buf, int buf_len) { + int nwrite; + if (use_tcp_fastopen_ && !tcp_fastopen_connected_) { + SockaddrStorage storage; + if (!peer_address_->ToSockAddr(storage.addr, &storage.addr_len)) { + errno = EINVAL; + return -1; + } + + int flags = 0x20000000; // Magic flag to enable TCP_FASTOPEN. +#if defined(OS_LINUX) + // sendto() will fail with EPIPE when the system doesn't support TCP Fast + // Open. Theoretically that shouldn't happen since the caller should check + // for system support on startup, but users may dynamically disable TCP Fast + // Open via sysctl. + flags |= MSG_NOSIGNAL; +#endif // defined(OS_LINUX) + nwrite = HANDLE_EINTR(sendto(socket_, + buf->data(), + buf_len, + flags, + storage.addr, + storage.addr_len)); + tcp_fastopen_connected_ = true; + + if (nwrite < 0) { + DCHECK_NE(EPIPE, errno); + + // If errno == EINPROGRESS, that means the kernel didn't have a cookie + // and would block. The kernel is internally doing a connect() though. + // Remap EINPROGRESS to EAGAIN so we treat this the same as our other + // asynchronous cases. Note that the user buffer has not been copied to + // kernel space. + if (errno == EINPROGRESS) { + errno = EAGAIN; + fast_open_status_ = FAST_OPEN_SLOW_CONNECT_RETURN; + } else { + fast_open_status_ = FAST_OPEN_ERROR; + } + } else { + fast_open_status_ = FAST_OPEN_FAST_CONNECT_RETURN; + } + } else { + nwrite = HANDLE_EINTR(write(socket_, buf->data(), buf_len)); + } + return nwrite; +} + +void TCPSocketLibevent::RecordFastOpenStatus() { + if (use_tcp_fastopen_ && + (fast_open_status_ == FAST_OPEN_FAST_CONNECT_RETURN || + fast_open_status_ == FAST_OPEN_SLOW_CONNECT_RETURN)) { + DCHECK_NE(FAST_OPEN_STATUS_UNKNOWN, fast_open_status_); + bool getsockopt_success(false); + bool server_acked_data(false); +#if defined(TCP_INFO) + // Probe to see the if the socket used TCP Fast Open. + tcp_info info; + socklen_t info_len = sizeof(tcp_info); + getsockopt_success = + getsockopt(socket_, IPPROTO_TCP, TCP_INFO, &info, &info_len) == 0 && + info_len == sizeof(tcp_info); + server_acked_data = getsockopt_success && + (info.tcpi_options & TCPI_OPT_SYN_DATA); +#endif + if (getsockopt_success) { + if (fast_open_status_ == FAST_OPEN_FAST_CONNECT_RETURN) { + fast_open_status_ = (server_acked_data ? FAST_OPEN_SYN_DATA_ACK : + FAST_OPEN_SYN_DATA_NACK); + } else { + fast_open_status_ = (server_acked_data ? FAST_OPEN_NO_SYN_DATA_ACK : + FAST_OPEN_NO_SYN_DATA_NACK); + } + } else { + fast_open_status_ = (fast_open_status_ == FAST_OPEN_FAST_CONNECT_RETURN ? + FAST_OPEN_SYN_DATA_FAILED : + FAST_OPEN_NO_SYN_DATA_FAILED); + } + } } } // namespace net diff --git a/net/socket/tcp_socket_libevent.h b/net/socket/tcp_socket_libevent.h index 610e489..a50caf0 100644 --- a/net/socket/tcp_socket_libevent.h +++ b/net/socket/tcp_socket_libevent.h @@ -6,7 +6,9 @@ #define NET_SOCKET_TCP_SOCKET_LIBEVENT_H_ #include "base/basictypes.h" +#include "base/callback.h" #include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/threading/non_thread_safe.h" @@ -14,54 +16,215 @@ #include "net/base/completion_callback.h" #include "net/base/net_export.h" #include "net/base/net_log.h" +#include "net/socket/socket_descriptor.h" namespace net { +class AddressList; +class IOBuffer; class IPEndPoint; -// TODO(yzshen): This class is incomplete. TCP client operations (Connect/Read/ -// Write/etc.) will be added. And TCPClientSocket will be changed to be a -// wrapper around TCPSocket. -class NET_EXPORT TCPSocketLibevent : public base::NonThreadSafe, - public base::MessageLoopForIO::Watcher { +class NET_EXPORT TCPSocketLibevent : public base::NonThreadSafe { public: TCPSocketLibevent(NetLog* net_log, const NetLog::Source& source); virtual ~TCPSocketLibevent(); int Open(AddressFamily family); // Takes ownership of |socket|. - int Adopt(int socket); - // Returns a socket file descriptor. The ownership is transferred to the - // caller. - int Release(); + int AdoptConnectedSocket(int socket, const IPEndPoint& peer_address); + int Bind(const IPEndPoint& address); - int GetLocalAddress(IPEndPoint* address) const; + int Listen(int backlog); int Accept(scoped_ptr<TCPSocketLibevent>* socket, IPEndPoint* address, const CompletionCallback& callback); + + int Connect(const IPEndPoint& address, const CompletionCallback& callback); + bool IsConnected() const; + bool IsConnectedAndIdle() const; + + // Multiple outstanding requests are not supported. + // Full duplex mode (reading and writing at the same time) is supported. + int Read(IOBuffer* buf, int buf_len, const CompletionCallback& callback); + int Write(IOBuffer* buf, int buf_len, const CompletionCallback& callback); + + int GetLocalAddress(IPEndPoint* address) const; + int GetPeerAddress(IPEndPoint* address) const; + + // Sets various socket options. + // The commonly used options for server listening sockets: + // - SetAddressReuse(true). int SetDefaultOptionsForServer(); + // The commonly used options for client sockets and accepted sockets: + // - SetNoDelay(true); + // - SetKeepAlive(true, 45). + void SetDefaultOptionsForClient(); int SetAddressReuse(bool allow); + bool SetReceiveBufferSize(int32 size); + bool SetSendBufferSize(int32 size); + bool SetKeepAlive(bool enable, int delay); + bool SetNoDelay(bool no_delay); + void Close(); - const BoundNetLog& net_log() const { return net_log_; } + bool UsingTCPFastOpen() const; + bool IsValid() const { return socket_ != kInvalidSocket; } + + // Marks the start/end of a series of connect attempts for logging purpose. + // + // TCPClientSocket may attempt to connect to multiple addresses until it + // succeeds in establishing a connection. The corresponding log will have + // multiple NetLog::TYPE_TCP_CONNECT_ATTEMPT entries nested within a + // NetLog::TYPE_TCP_CONNECT. These methods set the start/end of + // NetLog::TYPE_TCP_CONNECT. + // + // TODO(yzshen): Change logging format and let TCPClientSocket log the + // start/end of a series of connect attempts itself. + void StartLoggingMultipleConnectAttempts(const AddressList& addresses); + void EndLoggingMultipleConnectAttempts(int net_error); - // MessageLoopForIO::Watcher implementation. - virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; - virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + const BoundNetLog& net_log() const { return net_log_; } private: + // States that a fast open socket attempt can result in. + enum FastOpenStatus { + FAST_OPEN_STATUS_UNKNOWN, + + // The initial fast open connect attempted returned synchronously, + // indicating that we had and sent a cookie along with the initial data. + FAST_OPEN_FAST_CONNECT_RETURN, + + // The initial fast open connect attempted returned asynchronously, + // indicating that we did not have a cookie for the server. + FAST_OPEN_SLOW_CONNECT_RETURN, + + // Some other error occurred on connection, so we couldn't tell if + // fast open would have worked. + FAST_OPEN_ERROR, + + // An attempt to do a fast open succeeded immediately + // (FAST_OPEN_FAST_CONNECT_RETURN) and we later confirmed that the server + // had acked the data we sent. + FAST_OPEN_SYN_DATA_ACK, + + // An attempt to do a fast open succeeded immediately + // (FAST_OPEN_FAST_CONNECT_RETURN) and we later confirmed that the server + // had nacked the data we sent. + FAST_OPEN_SYN_DATA_NACK, + + // An attempt to do a fast open succeeded immediately + // (FAST_OPEN_FAST_CONNECT_RETURN) and our probe to determine if the + // socket was using fast open failed. + FAST_OPEN_SYN_DATA_FAILED, + + // An attempt to do a fast open failed (FAST_OPEN_SLOW_CONNECT_RETURN) + // and we later confirmed that the server had acked initial data. This + // should never happen (we didn't send data, so it shouldn't have + // been acked). + FAST_OPEN_NO_SYN_DATA_ACK, + + // An attempt to do a fast open failed (FAST_OPEN_SLOW_CONNECT_RETURN) + // and we later discovered that the server had nacked initial data. This + // is the expected case results for FAST_OPEN_SLOW_CONNECT_RETURN. + FAST_OPEN_NO_SYN_DATA_NACK, + + // An attempt to do a fast open failed (FAST_OPEN_SLOW_CONNECT_RETURN) + // and our later probe for ack/nack state failed. + FAST_OPEN_NO_SYN_DATA_FAILED, + + FAST_OPEN_MAX_VALUE + }; + + // Watcher simply forwards notifications to Closure objects set via the + // constructor. + class Watcher: public base::MessageLoopForIO::Watcher { + public: + Watcher(const base::Closure& read_ready_callback, + const base::Closure& write_ready_callback); + virtual ~Watcher(); + + // base::MessageLoopForIO::Watcher methods. + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + + private: + base::Closure read_ready_callback_; + base::Closure write_ready_callback_; + + DISALLOW_COPY_AND_ASSIGN(Watcher); + }; + int AcceptInternal(scoped_ptr<TCPSocketLibevent>* socket, IPEndPoint* address); + int DoConnect(); + void DoConnectComplete(int result); + + void LogConnectBegin(const AddressList& addresses); + void LogConnectEnd(int net_error); + + void DidCompleteRead(); + void DidCompleteWrite(); + void DidCompleteConnect(); + void DidCompleteConnectOrWrite(); + void DidCompleteAccept(); + + // Internal function to write to a socket. Returns an OS error. + int InternalWrite(IOBuffer* buf, int buf_len); + + // Called when the socket is known to be in a connected state. + void RecordFastOpenStatus(); + int socket_; base::MessageLoopForIO::FileDescriptorWatcher accept_socket_watcher_; + Watcher accept_watcher_; scoped_ptr<TCPSocketLibevent>* accept_socket_; IPEndPoint* accept_address_; CompletionCallback accept_callback_; + // The socket's libevent wrappers for reads and writes. + base::MessageLoopForIO::FileDescriptorWatcher read_socket_watcher_; + base::MessageLoopForIO::FileDescriptorWatcher write_socket_watcher_; + + // The corresponding watchers for reads and writes. + Watcher read_watcher_; + Watcher write_watcher_; + + // The buffer used for reads. + scoped_refptr<IOBuffer> read_buf_; + int read_buf_len_; + + // The buffer used for writes. + scoped_refptr<IOBuffer> write_buf_; + int write_buf_len_; + + // External callback; called when read is complete. + CompletionCallback read_callback_; + + // External callback; called when write or connect is complete. + CompletionCallback write_callback_; + + // Enables experimental TCP FastOpen option. + const bool use_tcp_fastopen_; + + // True when TCP FastOpen is in use and we have done the connect. + bool tcp_fastopen_connected_; + + FastOpenStatus fast_open_status_; + + // A connect operation is pending. In this case, |write_callback_| needs to be + // called when connect is complete. + bool waiting_connect_; + + scoped_ptr<IPEndPoint> peer_address_; + // The OS error that a connect attempt last completed with. + int connect_os_error_; + + bool logging_multiple_connect_attempts_; + BoundNetLog net_log_; DISALLOW_COPY_AND_ASSIGN(TCPSocketLibevent); diff --git a/net/socket/tcp_socket_unittest.cc b/net/socket/tcp_socket_unittest.cc index 0eb3083..a45fcba 100644 --- a/net/socket/tcp_socket_unittest.cc +++ b/net/socket/tcp_socket_unittest.cc @@ -11,7 +11,6 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" -#include "build/build_config.h" #include "net/base/address_list.h" #include "net/base/io_buffer.h" #include "net/base/ip_endpoint.h" @@ -200,10 +199,6 @@ TEST_F(TCPSocketTest, AcceptIPv6) { EXPECT_EQ(OK, connect_callback.WaitForResult()); } -// TODO(yzshen): Enable it for other platforms once TCPSocketLibevent supports -// client socket operations. -#if defined(OS_WIN) - TEST_F(TCPSocketTest, ReadWrite) { ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4()); @@ -264,7 +259,5 @@ TEST_F(TCPSocketTest, ReadWrite) { ASSERT_EQ(message, received_message); } -#endif - } // namespace } // namespace net |