diff mbox series

[v4] socket: Add new test for shutdown

Message ID 20240625092123.177763-1-skolosov@redhat.com
State New
Headers show
Series [v4] socket: Add new test for shutdown | expand

Commit Message

Sergey Kolosov June 25, 2024, 9:19 a.m. UTC
This commit adds shutdown test with SHUT_RD, SHUT_WR, SHUT_RDWR for an
UNIX socket connection.
---
Change from v3:
1. Using /dev/null in do_test_enotsock() instead of temporary file.
2. Remove unnecessary headers.
---
 socket/Makefile       |   1 +
 socket/tst-shutdown.c | 257 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 258 insertions(+)
 create mode 100644 socket/tst-shutdown.c

Comments

Samuel Thibault July 7, 2024, 11:29 p.m. UTC | #1
Hello,

Sergey Kolosov, le mar. 25 juin 2024 11:19:03 +0200, a ecrit:
> +  /* Send some data to both sockets before shutdown.  */
> +  xwrite (client, str1, len);
> +  xwrite (server, str2, len);
> +
> +  /* Call shutdown with SHUT_RDWR on client socket.  */
> +  if (shutdown (client, SHUT_RDWR) != 0)
> +    {
> +      FAIL_EXIT1 ("shutdown with SHUT_RDWR on socket %d failed", client);
> +    }
> +
> +  /* Verify that socket is still connected.  */
> +  xgetsockname (client, &peer, &peer_len);
> +
> +  /* Read data written before shutdown.  */
> +  xread (client, s_buf, len);

This looks odd: we have shutdown the socket above, so we're not supposed
to be able to read any more?

It looks to me like it's succeeding on Linux just by luck. Open group
says:

“
The shutdown() function disables subsequent send and/or receive operations
on a socket, depending on the value of the how argument.
”

Samuel
Florian Weimer July 8, 2024, 8:15 a.m. UTC | #2
* Samuel Thibault:

> Hello,
>
> Sergey Kolosov, le mar. 25 juin 2024 11:19:03 +0200, a ecrit:
>> +  /* Send some data to both sockets before shutdown.  */
>> +  xwrite (client, str1, len);
>> +  xwrite (server, str2, len);
>> +
>> +  /* Call shutdown with SHUT_RDWR on client socket.  */
>> +  if (shutdown (client, SHUT_RDWR) != 0)
>> +    {
>> +      FAIL_EXIT1 ("shutdown with SHUT_RDWR on socket %d failed", client);
>> +    }
>> +
>> +  /* Verify that socket is still connected.  */
>> +  xgetsockname (client, &peer, &peer_len);
>> +
>> +  /* Read data written before shutdown.  */
>> +  xread (client, s_buf, len);
>
> This looks odd: we have shutdown the socket above, so we're not supposed
> to be able to read any more?
>
> It looks to me like it's succeeding on Linux just by luck. Open group
> says:
>
> “
> The shutdown() function disables subsequent send and/or receive operations
> on a socket, depending on the value of the how argument.
> ”

The documentation is definitely incorrect for TCP, where SHUT_RD is a
NOP.  It's probably a bit off for other sockets, too.

Thanks,
Floian
Samuel Thibault July 8, 2024, 10:52 p.m. UTC | #3
Hello,

Florian Weimer, le lun. 08 juil. 2024 10:15:38 +0200, a ecrit:
> > Sergey Kolosov, le mar. 25 juin 2024 11:19:03 +0200, a ecrit:
> >> +  /* Send some data to both sockets before shutdown.  */
> >> +  xwrite (client, str1, len);
> >> +  xwrite (server, str2, len);
> >> +
> >> +  /* Call shutdown with SHUT_RDWR on client socket.  */
> >> +  if (shutdown (client, SHUT_RDWR) != 0)
> >> +    {
> >> +      FAIL_EXIT1 ("shutdown with SHUT_RDWR on socket %d failed", client);
> >> +    }
> >> +
> >> +  /* Verify that socket is still connected.  */
> >> +  xgetsockname (client, &peer, &peer_len);
> >> +
> >> +  /* Read data written before shutdown.  */
> >> +  xread (client, s_buf, len);
> >
> > This looks odd: we have shutdown the socket above, so we're not supposed
> > to be able to read any more?
> >
> > It looks to me like it's succeeding on Linux just by luck. Open group
> > says:
> >
> > “
> > The shutdown() function disables subsequent send and/or receive operations
> > on a socket, depending on the value of the how argument.
> > ”
> 
> The documentation is definitely incorrect for TCP, where SHUT_RD is a
> NOP.

It's a nop on Linux, but e.g. freebsd apparently flushes the reception
buffers. GNU/Hurd does that too. Which does seem coherent with the Open
group definition above.

Samuel
Florian Weimer July 9, 2024, 8:21 a.m. UTC | #4
* Samuel Thibault:

> Hello,
>
> Florian Weimer, le lun. 08 juil. 2024 10:15:38 +0200, a ecrit:
>> > Sergey Kolosov, le mar. 25 juin 2024 11:19:03 +0200, a ecrit:
>> >> +  /* Send some data to both sockets before shutdown.  */
>> >> +  xwrite (client, str1, len);
>> >> +  xwrite (server, str2, len);
>> >> +
>> >> +  /* Call shutdown with SHUT_RDWR on client socket.  */
>> >> +  if (shutdown (client, SHUT_RDWR) != 0)
>> >> +    {
>> >> +      FAIL_EXIT1 ("shutdown with SHUT_RDWR on socket %d failed", client);
>> >> +    }
>> >> +
>> >> +  /* Verify that socket is still connected.  */
>> >> +  xgetsockname (client, &peer, &peer_len);
>> >> +
>> >> +  /* Read data written before shutdown.  */
>> >> +  xread (client, s_buf, len);
>> >
>> > This looks odd: we have shutdown the socket above, so we're not supposed
>> > to be able to read any more?
>> >
>> > It looks to me like it's succeeding on Linux just by luck. Open group
>> > says:
>> >
>> > “
>> > The shutdown() function disables subsequent send and/or receive operations
>> > on a socket, depending on the value of the how argument.
>> > ”
>> 
>> The documentation is definitely incorrect for TCP, where SHUT_RD is a
>> NOP.
>
> It's a nop on Linux, but e.g. freebsd apparently flushes the reception
> buffers. GNU/Hurd does that too. Which does seem coherent with the Open
> group definition above.

Do we need to make this test case specific to Linux?

Thanks,
Florian
Samuel Thibault July 9, 2024, 8:28 a.m. UTC | #5
Florian Weimer, le mar. 09 juil. 2024 10:21:04 +0200, a ecrit:
> > Florian Weimer, le lun. 08 juil. 2024 10:15:38 +0200, a ecrit:
> >> > Sergey Kolosov, le mar. 25 juin 2024 11:19:03 +0200, a ecrit:
> >> >> +  /* Send some data to both sockets before shutdown.  */
> >> >> +  xwrite (client, str1, len);
> >> >> +  xwrite (server, str2, len);
> >> >> +
> >> >> +  /* Call shutdown with SHUT_RDWR on client socket.  */
> >> >> +  if (shutdown (client, SHUT_RDWR) != 0)
> >> >> +    {
> >> >> +      FAIL_EXIT1 ("shutdown with SHUT_RDWR on socket %d failed", client);
> >> >> +    }
> >> >> +
> >> >> +  /* Verify that socket is still connected.  */
> >> >> +  xgetsockname (client, &peer, &peer_len);
> >> >> +
> >> >> +  /* Read data written before shutdown.  */
> >> >> +  xread (client, s_buf, len);
> >> >
> >> > This looks odd: we have shutdown the socket above, so we're not supposed
> >> > to be able to read any more?
> >> >
> >> > It looks to me like it's succeeding on Linux just by luck. Open group
> >> > says:
> >> >
> >> > “
> >> > The shutdown() function disables subsequent send and/or receive operations
> >> > on a socket, depending on the value of the how argument.
> >> > ”
> >> 
> >> The documentation is definitely incorrect for TCP, where SHUT_RD is a
> >> NOP.
> >
> > It's a nop on Linux, but e.g. freebsd apparently flushes the reception
> > buffers. GNU/Hurd does that too. Which does seem coherent with the Open
> > group definition above.
> 
> Do we need to make this test case specific to Linux?

Either only xread data only Linux, or accept that we could be reading 0
bytes (but xread seems to insist on reading even when read returns 0 for
EOF ??).

Samuel
diff mbox series

Patch

diff --git a/socket/Makefile b/socket/Makefile
index fc1bd0a260..df732fa9b7 100644
--- a/socket/Makefile
+++ b/socket/Makefile
@@ -71,6 +71,7 @@  tests := \
   tst-cmsg_cloexec \
   tst-cmsghdr \
   tst-connect \
+  tst-shutdown \
   tst-sockopt \
   # tests
 
diff --git a/socket/tst-shutdown.c b/socket/tst-shutdown.c
new file mode 100644
index 0000000000..a305e5e494
--- /dev/null
+++ b/socket/tst-shutdown.c
@@ -0,0 +1,257 @@ 
+/* Test the shutdown function.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xsocket.h>
+#include <support/xunistd.h>
+#include <sys/socket.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+
+struct connection
+{
+  int sockets[2];
+};
+
+void
+establish_connection (struct connection *conn)
+{
+  if (socketpair (AF_UNIX, SOCK_STREAM, 0, conn->sockets) != 0)
+    {
+      FAIL_EXIT1 ("socketpair (AF_UNIX, SOCK_STREAM, 0): %m\n");
+    }
+}
+
+void
+close_connection (struct connection *conn)
+{
+  xclose (conn->sockets[0]);
+  xclose (conn->sockets[1]);
+}
+
+/* Open a file and check that shutdown fails with the ENOTSOCK error code.  */
+void
+do_test_enotsock (void)
+{
+  /* Open file and check that shutdown will fail with ENOTSOCK.  */
+  int fd = xopen ("/dev/null", O_RDWR, 0);
+
+  int result = shutdown (fd, SHUT_RD);
+  if (result == 0 || errno != ENOTSOCK)
+    {
+      FAIL_EXIT1 ("shutdown should fail with ENOTSOCK");
+    }
+  xclose (fd);
+}
+
+/* Test shutdown with SHUT_RD.  */
+void
+do_test_shut_rd (void)
+{
+  struct connection conn;
+  const char *str = "AAAAAAA";
+  int len = 8;
+  int ret;
+  void *s_buf = xmalloc (len);
+  bzero (s_buf, len);
+
+  establish_connection (&conn);
+  int server = conn.sockets[0];
+  int client = conn.sockets[1];
+
+  /* Call shutdown with SHUT_RD on server socket.  */
+  if (shutdown (server, SHUT_RD) != 0)
+    {
+      FAIL_EXIT1 ("shutdown with SHUT_RD on socket %d failed", server);
+    }
+
+  ret = send (server, str, len, 0);
+  if (ret <= 0)
+    {
+      FAIL_EXIT1 ("send (%d, data, %d): %m", server, len);
+    }
+
+  ret = recv (client, s_buf, len, 0);
+  if (ret <= 0)
+    {
+      FAIL_EXIT1 ("recv (%d, data, %d): %m", client, len);
+    }
+
+  TEST_COMPARE_BLOB (str, len, s_buf, len);
+
+  /* Send data should be disallowed on shutdown socket.  */
+  errno = 0;
+  ret = send (client, str, len, MSG_NOSIGNAL);
+  if (ret >= 0 || errno != EPIPE)
+    {
+      FAIL_EXIT1 ("Send on SHUT_RD socket should be disallowed: %m");
+    }
+
+  /* Recv should return zero and no error.  */
+  errno = 0;
+  ret = recv (server, s_buf, len, 0);
+  if (ret != 0 || errno != 0)
+    {
+      FAIL_EXIT1 ("recv should return 0 without error: %m");
+    }
+
+  close_connection (&conn);
+}
+
+/* Test shutdown with SHUT_WR.  */
+void
+do_test_shut_wr (void)
+{
+  struct connection conn;
+  const char *str1 = "CCCCCCC";
+  const char *str2 = "DDDDDDD";
+  const char *str3 = "EEEEEEE";
+  int len = 8;
+  int ret;
+  void *c_buf = xmalloc (len);
+  void *s_buf = xmalloc (len);
+
+  establish_connection (&conn);
+  int server = conn.sockets[0];
+  int client = conn.sockets[1];
+
+  xwrite (client, str1, len);
+
+  if (shutdown (client, SHUT_WR) != 0)
+    {
+      FAIL_EXIT1 ("shutdown with SHUT_WR on socket %d failed", client);
+    }
+
+  ret = send (client, str2, len, MSG_NOSIGNAL);
+  if (ret >= 0)
+    {
+      FAIL_EXIT1 ("send on SHUT_WR socket should fail");
+    }
+
+  /* Read data written before shutdown and check if it's correct.  */
+  xread (server, s_buf, len);
+  TEST_COMPARE_BLOB (str1, len, s_buf, len);
+
+  /* Second read should return zero without error.  */
+  errno = 0;
+  if (read (server, s_buf, len) != 0 || errno != 0)
+    {
+      FAIL_EXIT1 ("read after shutdown should return zero without error: %m");
+    }
+
+  /* Write some data to socket and check it still can be read on other side.  */
+  memcpy (s_buf, str3, len);
+  xwrite (server, s_buf, len);
+
+  xread (client, c_buf, len);
+  TEST_COMPARE_BLOB (s_buf, len, c_buf, len);
+
+  close_connection (&conn);
+}
+
+/* Test shutdown with SHUT_RDWR.  */
+void
+do_test_shut_rdwr (void)
+{
+  struct connection conn;
+  struct sockaddr peer;
+  socklen_t peer_len = sizeof (peer);
+
+  const char *str1 = "FFFFFFF";
+  const char *str2 = "GGGGGGG";
+  int len = 8;
+  int ret;
+  void *s_buf = xmalloc (len);
+  bzero (s_buf, len);
+
+  establish_connection (&conn);
+  int server = conn.sockets[0];
+  int client = conn.sockets[1];
+
+  /* Send some data to both sockets before shutdown.  */
+  xwrite (client, str1, len);
+  xwrite (server, str2, len);
+
+  /* Call shutdown with SHUT_RDWR on client socket.  */
+  if (shutdown (client, SHUT_RDWR) != 0)
+    {
+      FAIL_EXIT1 ("shutdown with SHUT_RDWR on socket %d failed", client);
+    }
+
+  /* Verify that socket is still connected.  */
+  xgetsockname (client, &peer, &peer_len);
+
+  /* Read data written before shutdown.  */
+  xread (client, s_buf, len);
+  TEST_COMPARE_BLOB (s_buf, len, str2, len);
+
+  /* Second read should return zero, but no error.  */
+  errno = 0;
+  if (read (client, s_buf, len) != 0 || errno != 0)
+    {
+      FAIL_EXIT1 ("read after shutdown should return zero without error: %m");
+    }
+
+  /* Send some data to shutdown socket and expect error.  */
+  errno = 0;
+  ret = send (server, str2, len, MSG_NOSIGNAL);
+  if (ret >= 0 || errno != EPIPE)
+    {
+      FAIL_EXIT1 ("send to RDWR shutdown socket should fail with EPIPE");
+    }
+
+  /* Read data written before shutdown.  */
+  xread (server, s_buf, len);
+  TEST_COMPARE_BLOB (s_buf, len, str1, len);
+
+  /* Second read should return zero, but no error.  */
+  errno = 0;
+  if (read (server, s_buf, len) != 0 || errno != 0)
+    {
+      FAIL_EXIT1 ("read after shutdown should return zero without error: %m");
+    }
+
+  /* Send some data to shutdown socket and expect error.  */
+  errno = 0;
+  ret = send (client, str1, len, MSG_NOSIGNAL);
+  if (ret >= 0 || errno != EPIPE)
+    {
+      FAIL_EXIT1 ("send to RDWR shutdown socket should fail with EPIPE");
+    }
+
+  close_connection (&conn);
+}
+
+static int
+do_test (void)
+{
+  do_test_enotsock ();
+  do_test_shut_rd ();
+  do_test_shut_wr ();
+  do_test_shut_rdwr ();
+
+  return 0;
+}
+
+#include <support/test-driver.c>