tcpip/transport/tcp: read side only shutdown of an endpoint

Support shutdown on only the read side of an endpoint. Reads performed
after a call to Shutdown with only the ShutdownRead flag will return
ErrClosedForReceive without data.

Break out the shutdown(2) with SHUT_RD syscall test into to two tests.
The first tests that no packets are sent when shutting down the read
side of a socket. The second tests that, after shutting down the read
side of a socket, unread data can still be read, or an EOF if there is
no more data to read.

Change-Id: I9d7c0a06937909cbb466b7591544a4bcaebb11ce
PiperOrigin-RevId: 244459430
This commit is contained in:
Ben Burkert
2019-04-19 19:29:05 -07:00
committed by Shentubot
parent 358eb52a76
commit 56927e5317
3 changed files with 62 additions and 15 deletions
+16 -13
View File
@@ -1199,21 +1199,24 @@ func (e *endpoint) Shutdown(flags tcpip.ShutdownFlags) *tcpip.Error {
switch e.state {
case stateConnected:
// Close for read.
if (e.shutdownFlags & tcpip.ShutdownRead) != 0 {
// Mark read side as closed.
e.rcvListMu.Lock()
e.rcvClosed = true
rcvBufUsed := e.rcvBufUsed
e.rcvListMu.Unlock()
// If we're fully closed and we have unread data we need to abort
// the connection with a RST.
if (e.shutdownFlags&tcpip.ShutdownWrite) != 0 && rcvBufUsed > 0 {
e.notifyProtocolGoroutine(notifyReset)
return nil
}
}
// Close for write.
if (e.shutdownFlags & tcpip.ShutdownWrite) != 0 {
if (e.shutdownFlags & tcpip.ShutdownRead) != 0 {
// We're fully closed, if we have unread data we need to abort
// the connection with a RST.
e.rcvListMu.Lock()
rcvBufUsed := e.rcvBufUsed
e.rcvListMu.Unlock()
if rcvBufUsed > 0 {
e.notifyProtocolGoroutine(notifyReset)
return nil
}
}
e.sndBufMu.Lock()
if e.sndClosed {
+19
View File
@@ -687,6 +687,25 @@ func TestRstOnCloseWithUnreadDataFinConvertRst(t *testing.T) {
})
}
func TestShutdownRead(t *testing.T) {
c := context.New(t, defaultMTU)
defer c.Cleanup()
c.CreateConnected(789, 30000, nil)
if _, _, err := c.EP.Read(nil); err != tcpip.ErrWouldBlock {
t.Fatalf("got c.EP.Read(nil) = %v, want = %v", err, tcpip.ErrWouldBlock)
}
if err := c.EP.Shutdown(tcpip.ShutdownRead); err != nil {
t.Fatalf("Shutdown failed: %v", err)
}
if _, _, err := c.EP.Read(nil); err != tcpip.ErrClosedForReceive {
t.Fatalf("got c.EP.Read(nil) = %v, want = %v", err, tcpip.ErrClosedForReceive)
}
}
func TestFullWindowReceive(t *testing.T) {
c := context.New(t, defaultMTU)
defer c.Cleanup()
+27 -2
View File
@@ -190,8 +190,7 @@ TEST_P(TCPSocketPairTest, FINSentOnShutdownWrWithUnreadData) {
}
// This test will verify that when data is received by a socket, even if it's
// not read SHUT_RD will not cause any packets to be generated and data will
// remain in the buffer and can be read later.
// not read SHUT_RD will not cause any packets to be generated.
TEST_P(TCPSocketPairTest, ShutdownRdShouldCauseNoPacketsWithUnreadData) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
@@ -213,10 +212,36 @@ TEST_P(TCPSocketPairTest, ShutdownRdShouldCauseNoPacketsWithUnreadData) {
constexpr int kPollNoResponseTimeoutMs = 3000;
ASSERT_THAT(RetryEINTR(poll)(&poll_fd2, 1, kPollNoResponseTimeoutMs),
SyscallSucceedsWithValue(0)); // Timeout.
}
// This test will verify that a socket which has unread data will still allow
// the data to be read after shutting down the read side, and once there is no
// unread data left, then read will return an EOF.
TEST_P(TCPSocketPairTest, ShutdownRdAllowsReadOfReceivedDataBeforeEOF) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
char buf[10] = {};
ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)),
SyscallSucceedsWithValue(sizeof(buf)));
// Wait until t_ sees the data on its side but don't read it.
struct pollfd poll_fd = {sockets->second_fd(), POLLIN | POLLHUP, 0};
constexpr int kPollTimeoutMs = 20000; // Wait up to 20 seconds for the data.
ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs),
SyscallSucceedsWithValue(1));
// Now shutdown the read end.
ASSERT_THAT(shutdown(sockets->second_fd(), SHUT_RD), SyscallSucceeds());
// Even though we did a SHUT_RD on the read end we can still read the data.
ASSERT_THAT(RetryEINTR(read)(sockets->second_fd(), buf, sizeof(buf)),
SyscallSucceedsWithValue(sizeof(buf)));
// After reading all of the data, reading the closed read end returns EOF.
ASSERT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs),
SyscallSucceedsWithValue(1));
ASSERT_THAT(RetryEINTR(read)(sockets->second_fd(), buf, sizeof(buf)),
SyscallSucceedsWithValue(0));
}
TEST_P(TCPSocketPairTest, ClosedReadNonBlockingSocket) {