Index: net/socket/tcp_socket_libevent.cc |
diff --git a/net/socket/tcp_socket_libevent.cc b/net/socket/tcp_socket_libevent.cc |
index ae59cd9c82405595bd83cb57555443815e7e95ab..cc2376590f57de1904e8d2edef9e765ce740dc71 100644 |
--- a/net/socket/tcp_socket_libevent.cc |
+++ b/net/socket/tcp_socket_libevent.cc |
@@ -39,6 +39,8 @@ bool g_tcp_fastopen_supported = false; |
// True if TCP FastOpen is user-enabled for all connections. |
// TODO(jri): Change global variable to param in HttpNetworkSession::Params. |
bool g_tcp_fastopen_user_enabled = false; |
+// True if TCP FastOpen connect-with-write has failed at least once. |
+bool g_tcp_fastopen_has_failed = false; |
// 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. |
@@ -129,8 +131,9 @@ void CheckSupportAndMaybeEnableTCPFastOpen(bool user_enabled) { |
TCPSocketLibevent::TCPSocketLibevent(NetLog* net_log, |
const NetLog::Source& source) |
: use_tcp_fastopen_(false), |
+ tcp_fastopen_write_attempted_(false), |
tcp_fastopen_connected_(false), |
- fast_open_status_(FAST_OPEN_STATUS_UNKNOWN), |
+ tcp_fastopen_status_(TCP_FASTOPEN_STATUS_UNKNOWN), |
logging_multiple_connect_attempts_(false), |
net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SOCKET)) { |
net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE, |
@@ -139,10 +142,7 @@ TCPSocketLibevent::TCPSocketLibevent(NetLog* net_log, |
TCPSocketLibevent::~TCPSocketLibevent() { |
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) { |
@@ -222,7 +222,7 @@ int TCPSocketLibevent::Connect(const IPEndPoint& address, |
if (use_tcp_fastopen_) { |
// With TCP FastOpen, we pretend that the socket is connected. |
- DCHECK(!tcp_fastopen_connected_); |
+ DCHECK(!tcp_fastopen_write_attempted_); |
socket_->SetPeerAddress(storage); |
return OK; |
} |
@@ -239,7 +239,7 @@ bool TCPSocketLibevent::IsConnected() const { |
if (!socket_) |
return false; |
- if (use_tcp_fastopen_ && !tcp_fastopen_connected_ && |
+ if (use_tcp_fastopen_ && !tcp_fastopen_write_attempted_ && |
socket_->HasPeerAddress()) { |
// With TCP FastOpen, we pretend that the socket is connected. |
// This allows GetPeerAddress() to return peer_address_. |
@@ -268,8 +268,6 @@ int TCPSocketLibevent::Read(IOBuffer* buf, |
// use it when Read() completes, as otherwise, this transfers |
// ownership of buf to socket. |
base::Unretained(this), make_scoped_refptr(buf), callback)); |
- if (rv >= 0) |
- RecordFastOpenStatus(); |
if (rv != ERR_IO_PENDING) |
rv = HandleReadCompleted(buf, rv); |
return rv; |
@@ -288,7 +286,8 @@ int TCPSocketLibevent::Write(IOBuffer* buf, |
// ownership of buf to socket. |
base::Unretained(this), make_scoped_refptr(buf), callback); |
int rv; |
- if (use_tcp_fastopen_ && !tcp_fastopen_connected_) { |
+ |
+ if (use_tcp_fastopen_ && !tcp_fastopen_write_attempted_) { |
rv = TcpFastOpenWrite(buf, buf_len, write_callback); |
} else { |
rv = socket_->Write(buf, buf_len, write_callback); |
@@ -414,8 +413,17 @@ bool TCPSocketLibevent::SetNoDelay(bool no_delay) { |
void TCPSocketLibevent::Close() { |
socket_.reset(); |
+ |
+ // Record and reset TCP FastOpen state. |
+ if (tcp_fastopen_write_attempted_ || |
+ tcp_fastopen_status_ == TCP_FASTOPEN_PREVIOUSLY_FAILED) { |
+ UMA_HISTOGRAM_ENUMERATION("Net.TcpFastOpenSocketConnection", |
+ tcp_fastopen_status_, TCP_FASTOPEN_MAX_VALUE); |
+ } |
+ use_tcp_fastopen_ = false; |
tcp_fastopen_connected_ = false; |
- fast_open_status_ = FAST_OPEN_STATUS_UNKNOWN; |
+ tcp_fastopen_write_attempted_ = false; |
+ tcp_fastopen_status_ = TCP_FASTOPEN_STATUS_UNKNOWN; |
} |
bool TCPSocketLibevent::UsingTCPFastOpen() const { |
@@ -423,8 +431,17 @@ bool TCPSocketLibevent::UsingTCPFastOpen() const { |
} |
void TCPSocketLibevent::EnableTCPFastOpenIfSupported() { |
- if (IsTCPFastOpenSupported()) |
+ if (!IsTCPFastOpenSupported()) |
+ return; |
+ |
+ // Do not enable TCP FastOpen if it had previously failed. |
+ // This check conservatively avoids middleboxes that may blackhole |
+ // TCP FastOpen SYN+Data packets; on such a failure, subsequent sockets |
+ // should not use TCP FastOpen. |
+ if(!g_tcp_fastopen_has_failed) |
use_tcp_fastopen_ = true; |
+ else |
+ tcp_fastopen_status_ = TCP_FASTOPEN_PREVIOUSLY_FAILED; |
} |
bool TCPSocketLibevent::IsValid() const { |
@@ -553,14 +570,27 @@ void TCPSocketLibevent::ReadCompleted(const scoped_refptr<IOBuffer>& buf, |
const CompletionCallback& callback, |
int rv) { |
DCHECK_NE(ERR_IO_PENDING, rv); |
- // Records TCP FastOpen status regardless of error in asynchronous case. |
- // TODO(rdsmith,jri): Change histogram name to indicate it could be called on |
- // error. |
- RecordFastOpenStatus(); |
callback.Run(HandleReadCompleted(buf.get(), rv)); |
} |
int TCPSocketLibevent::HandleReadCompleted(IOBuffer* buf, int rv) { |
+ if (tcp_fastopen_write_attempted_ && !tcp_fastopen_connected_) { |
+ // A TCP FastOpen connect-with-write was attempted. This read was a |
+ // subsequent read, which either succeeded or failed. If the read |
+ // succeeded, the socket is considered connected via TCP FastOpen. |
+ // If the read failed, TCP FastOpen is (conservatively) turned off for all |
+ // subsequent connections. TCP FastOpen status is recorded in both cases. |
+ // TODO (jri): This currently results in conservative behavior, where TCP |
+ // FastOpen is turned off on _any_ error. Implement optimizations, |
+ // such as turning off TCP FastOpen on more specific errors, and |
+ // re-attempting TCP FastOpen after a certain amount of time has passed. |
+ if (rv >= 0) |
+ tcp_fastopen_connected_ = true; |
+ else |
+ g_tcp_fastopen_has_failed = true; |
+ UpdateTCPFastOpenStatusAfterRead(); |
+ } |
+ |
if (rv < 0) { |
net_log_.AddEvent(NetLog::TYPE_SOCKET_READ_ERROR, |
CreateNetLogSocketErrorCallback(rv, errno)); |
@@ -576,13 +606,24 @@ int TCPSocketLibevent::HandleReadCompleted(IOBuffer* buf, int rv) { |
void TCPSocketLibevent::WriteCompleted(const scoped_refptr<IOBuffer>& buf, |
const CompletionCallback& callback, |
- int rv) const { |
+ int rv) { |
DCHECK_NE(ERR_IO_PENDING, rv); |
callback.Run(HandleWriteCompleted(buf.get(), rv)); |
} |
-int TCPSocketLibevent::HandleWriteCompleted(IOBuffer* buf, int rv) const { |
+int TCPSocketLibevent::HandleWriteCompleted(IOBuffer* buf, int rv) { |
if (rv < 0) { |
+ if (tcp_fastopen_write_attempted_ && !tcp_fastopen_connected_) { |
+ // TCP FastOpen connect-with-write was attempted, and the write failed |
+ // for unknown reasons. Record status and (conservatively) turn off |
+ // TCP FastOpen for all subsequent connections. |
+ // TODO (jri): This currently results in conservative behavior, where TCP |
+ // FastOpen is turned off on _any_ error. Implement optimizations, |
+ // such as turning off TCP FastOpen on more specific errors, and |
+ // re-attempting TCP FastOpen after a certain amount of time has passed. |
+ tcp_fastopen_status_ = TCP_FASTOPEN_ERROR; |
+ g_tcp_fastopen_has_failed = true; |
+ } |
net_log_.AddEvent(NetLog::TYPE_SOCKET_WRITE_ERROR, |
CreateNetLogSocketErrorCallback(rv, errno)); |
return rv; |
@@ -606,10 +647,11 @@ int TCPSocketLibevent::TcpFastOpenWrite( |
int flags = 0x20000000; // Magic flag to enable TCP_FASTOPEN. |
#if defined(OS_LINUX) || defined(OS_ANDROID) |
- // 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. |
+ // sendto() will fail with EPIPE when the system doesn't implement TCP |
+ // FastOpen, and with EOPNOTSUPP when the system implements TCP FastOpen |
+ // but it is disabled. Theoretically these shouldn't happen |
+ // since the caller should check for system support on startup, but |
+ // users may dynamically disable TCP FastOpen via sysctl. |
flags |= MSG_NOSIGNAL; |
#endif // defined(OS_LINUX) || defined(OS_ANDROID) |
rv = HANDLE_EINTR(sendto(socket_->socket_fd(), |
@@ -618,10 +660,10 @@ int TCPSocketLibevent::TcpFastOpenWrite( |
flags, |
storage.addr, |
storage.addr_len)); |
- tcp_fastopen_connected_ = true; |
+ tcp_fastopen_write_attempted_ = true; |
if (rv >= 0) { |
- fast_open_status_ = FAST_OPEN_FAST_CONNECT_RETURN; |
+ tcp_fastopen_status_ = TCP_FASTOPEN_FAST_CONNECT_RETURN; |
return rv; |
} |
@@ -639,45 +681,64 @@ int TCPSocketLibevent::TcpFastOpenWrite( |
} |
if (rv != ERR_IO_PENDING) { |
- fast_open_status_ = FAST_OPEN_ERROR; |
+ // TCP FastOpen connect-with-write was attempted, and the write failed |
+ // since TCP FastOpen was not implemented or disabled in the OS. |
+ // Record status and turn off TCP FastOpen for all subsequent connections. |
+ // TODO (jri): This is almost certainly too conservative, since it blanket |
+ // turns off TCP FastOpen on any write error. Two things need to be done |
+ // here: (i) record a histogram of write errors; in particular, record |
+ // occurrences of EOPNOTSUPP and EPIPE, and (ii) afterwards, consider |
+ // turning off TCP FastOpen on more specific errors. |
+ tcp_fastopen_status_ = TCP_FASTOPEN_ERROR; |
+ g_tcp_fastopen_has_failed = true; |
return rv; |
} |
- fast_open_status_ = FAST_OPEN_SLOW_CONNECT_RETURN; |
+ tcp_fastopen_status_ = TCP_FASTOPEN_SLOW_CONNECT_RETURN; |
return socket_->WaitForWrite(buf, buf_len, callback); |
} |
-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); |
+void TCPSocketLibevent::UpdateTCPFastOpenStatusAfterRead() { |
+ DCHECK(tcp_fastopen_status_ == TCP_FASTOPEN_FAST_CONNECT_RETURN || |
+ tcp_fastopen_status_ == TCP_FASTOPEN_SLOW_CONNECT_RETURN); |
+ |
+ if (tcp_fastopen_write_attempted_ && !tcp_fastopen_connected_) { |
+ // TCP FastOpen connect-with-write was attempted, and failed. |
+ tcp_fastopen_status_ = |
+ (tcp_fastopen_status_ == TCP_FASTOPEN_FAST_CONNECT_RETURN ? |
+ TCP_FASTOPEN_FAST_CONNECT_READ_FAILED : |
+ TCP_FASTOPEN_SLOW_CONNECT_READ_FAILED); |
+ return; |
+ } |
+ |
+ bool getsockopt_success = false; |
+ bool server_acked_data = false; |
#if defined(TCP_INFO) |
- // Probe to see the if the socket used TCP FastOpen. |
- tcp_info info; |
- socklen_t info_len = sizeof(tcp_info); |
- getsockopt_success = |
- getsockopt(socket_->socket_fd(), 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); |
+ // Probe to see the if the socket used TCP FastOpen. |
+ tcp_info info; |
+ socklen_t info_len = sizeof(tcp_info); |
+ getsockopt_success = getsockopt(socket_->socket_fd(), 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); |
- } |
+ |
+ if (getsockopt_success) { |
+ if (tcp_fastopen_status_ == TCP_FASTOPEN_FAST_CONNECT_RETURN) { |
+ tcp_fastopen_status_ = (server_acked_data ? |
+ TCP_FASTOPEN_SYN_DATA_ACK : |
+ TCP_FASTOPEN_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); |
+ tcp_fastopen_status_ = (server_acked_data ? |
+ TCP_FASTOPEN_NO_SYN_DATA_ACK : |
+ TCP_FASTOPEN_NO_SYN_DATA_NACK); |
} |
+ } else { |
+ tcp_fastopen_status_ = |
+ (tcp_fastopen_status_ == TCP_FASTOPEN_FAST_CONNECT_RETURN ? |
+ TCP_FASTOPEN_SYN_DATA_GETSOCKOPT_FAILED : |
+ TCP_FASTOPEN_NO_SYN_DATA_GETSOCKOPT_FAILED); |
} |
} |