diff options
author | Nayana Bidari <nybidari@google.com> | 2020-09-28 16:37:44 -0700 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2020-09-28 16:39:12 -0700 |
commit | 237b761f9a61ad1a821320e68f5a71e7cda6b29e (patch) | |
tree | 45e97621c612c5ca7ec52ad52f216a523ecac9c7 | |
parent | a5acc0616c9552c7252e3f133f9ad4648422cc5f (diff) |
Fix lingering of TCP socket in the initial state.
When the socket is set with SO_LINGER and close()'d in the initial state, it
should not linger and return immediately.
PiperOrigin-RevId: 334263149
-rw-r--r-- | pkg/tcpip/transport/tcp/endpoint.go | 9 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_linger_test.go | 17 | ||||
-rw-r--r-- | test/syscalls/linux/tcp_socket.cc | 30 |
3 files changed, 55 insertions, 1 deletions
diff --git a/pkg/tcpip/transport/tcp/endpoint.go b/pkg/tcpip/transport/tcp/endpoint.go index 87db13720..7ad894840 100644 --- a/pkg/tcpip/transport/tcp/endpoint.go +++ b/pkg/tcpip/transport/tcp/endpoint.go @@ -927,7 +927,12 @@ func (e *endpoint) Readiness(mask waiter.EventMask) waiter.EventMask { result := waiter.EventMask(0) switch e.EndpointState() { - case StateInitial, StateBound, StateConnecting, StateSynSent, StateSynRecv: + case StateInitial, StateBound: + // This prevents blocking of new sockets which are not + // connected when SO_LINGER is set. + result |= waiter.EventHUp + + case StateConnecting, StateSynSent, StateSynRecv: // Ready for nothing. case StateClose, StateError, StateTimeWait: @@ -1098,6 +1103,8 @@ func (e *endpoint) closeNoShutdownLocked() { e.notifyProtocolGoroutine(notifyClose) } else { e.transitionToStateCloseLocked() + // Notify that the endpoint is closed. + e.waiterQueue.Notify(waiter.EventHUp) } } diff --git a/test/packetimpact/tests/tcp_linger_test.go b/test/packetimpact/tests/tcp_linger_test.go index 913e49e06..b9a0409aa 100644 --- a/test/packetimpact/tests/tcp_linger_test.go +++ b/test/packetimpact/tests/tcp_linger_test.go @@ -251,3 +251,20 @@ func TestTCPLingerShutdownSendNonZeroTimeout(t *testing.T) { }) } } + +func TestTCPLingerNonEstablished(t *testing.T) { + dut := testbench.NewDUT(t) + newFD := dut.Socket(t, unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_TCP) + dut.SetSockLingerOption(t, newFD, lingerDuration, true) + + // As the socket is in the initial state, Close() should not linger + // and return immediately. + start := time.Now() + dut.CloseWithErrno(context.Background(), t, newFD) + diff := time.Since(start) + + if diff > lingerDuration { + t.Errorf("expected close to return within %s, but returned after %s", lingerDuration, diff) + } + dut.TearDown() +} diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc index ab731db1d..e0981e28a 100644 --- a/test/syscalls/linux/tcp_socket.cc +++ b/test/syscalls/linux/tcp_socket.cc @@ -1643,6 +1643,36 @@ TEST_P(SimpleTcpSocketTest, GetSocketDetachFilter) { SyscallFailsWithErrno(ENOPROTOOPT)); } +TEST_P(SimpleTcpSocketTest, CloseNonConnectedLingerOption) { + FileDescriptor s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); + + constexpr int kLingerTimeout = 10; // Seconds. + + // Set the SO_LINGER option. + struct linger sl = { + .l_onoff = 1, + .l_linger = kLingerTimeout, + }; + ASSERT_THAT(setsockopt(s.get(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)), + SyscallSucceeds()); + + struct pollfd poll_fd = { + .fd = s.get(), + .events = POLLHUP, + }; + constexpr int kPollTimeoutMs = 0; + ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs), + SyscallSucceedsWithValue(1)); + + auto const start_time = absl::Now(); + EXPECT_THAT(close(s.release()), SyscallSucceeds()); + auto const end_time = absl::Now(); + + // Close() should not linger and return immediately. + ASSERT_LT((end_time - start_time), absl::Seconds(kLingerTimeout)); +} + INSTANTIATE_TEST_SUITE_P(AllInetTests, SimpleTcpSocketTest, ::testing::Values(AF_INET, AF_INET6)); |