Shutdown(), TCP sockets, and select() discrepancy

Michael Kerrisk (m.kerrisk@gmx.net)
Tue, 17 Dec 2002 18:01:20 +0100 (MET)


After an offline discussion with Andi Kleen, I thought I'd bring this topic
here.

I have been experimenting with shutdown() and select() on TCP sockets and
noted a clear discrepancy from (seemingly all) other Unix flavours. On most
implementations, if we do a SHUT_WR or SHUT_RDWR, then the socket should test
as writable when we call select(), since a write() etc. will fail with a
SIGPIPE signal or an EPIPE error.

On Linux this doesn't happen - is there a reason why Linux differs in this
respect from other implementations? Given that FreeBSD, Solaris 8, HP/UX 11,
Irix 6.5, and OSF 5.1/a all do mark a socket as writable in this case, things
appear to be broken, de facto, on Linux.

At the foot of this mail is a test program I wrote which
demonstrates the disrepancy. When I run this on SuSE 8.0 (kernel 2.4.18)
and SuSE 7.2 (kernel 2.2.19) I see the following:

$ ./is_shutdown_select 1
Listening socket (fd=3) set up okay
Active socket (fd=4) set up okay
Connection established (fd=5)
shutdown(4, 1)
3:
4:
5: r w
$ ./is_shutdown_select 2
Listening socket (fd=3) set up okay
Active socket (fd=4) set up okay
Connection established (fd=5)
shutdown(4, 2)
3:
4: r
5: r w

But On FreeBSD 4.7 I see the following (which is what I expected to see on
Linux):

$ ./is_shutdown_select 1
Listening socket (fd=3) set up okay
Active socket (fd=4) set up okay
Connection established (fd=5)
shutdown(4, 1)
3:
4: w
5: r w
$ ./is_shutdown_select 2
Listening socket (fd=3) set up okay
Active socket (fd=4) set up okay
Connection established (fd=5)
shutdown(4, 2)
3:
4: r w
5: r w

Solaris 8, HP/UX 11, Irix 6.5, and OSF 5.1/a (I had to modify the header
files included slightly for the latter OSes) give the same results as FreeBSD
4.7.

Cheers

Michael

============================

/* is_shutdown_select.c

Experiment to determine behaviour of select() after a shutdown()
is performed on one or both ends of a TCP socket pair.
*/
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

#define errExit(msg) { perror(msg); exit(EXIT_FAILURE); }

#define PORT_NUM 50000 /* Port number for server */

int
main(int argc, char *argv[])
{
struct sockaddr_in svaddr;
int fd[3], optval, j;
fd_set rfds, wfds;
int nfds;
struct timeval tmo = { 0, 0 }; /* For polling select() */

if (argc > 1 && strcmp(argv[1], "--help") == 0) {
fprintf(stderr, "%s active-how accept-how\n", argv[0]);
exit(EXIT_FAILURE);
} /* if */

fd[0] = socket(AF_INET, SOCK_STREAM, 0); /* Listening socket */
if (fd[0] == -1) errExit("socket");

optval = 1;
if (setsockopt(fd[0], SOL_SOCKET, SO_REUSEADDR, &optval,
sizeof(optval)) == -1) errExit("setsockopt");

/* Bind to wildcard host address + well-known port, and mark as
listening socket */

memset(&svaddr, 0, sizeof(svaddr));
svaddr.sin_family = AF_INET;
svaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* Wildcard address */
svaddr.sin_port = htons(PORT_NUM); if (bind(fd[0], (struct sockaddr *)
&svaddr, sizeof(svaddr)) == -1)
errExit("bind");

if (listen(fd[0], 5) == -1) errExit("listen");

printf("Listening socket (fd=%d) set up okay\n", fd[0]);

/* Active sockets */

fd[1] = socket(AF_INET, SOCK_STREAM, 0);
if (fd[1] == -1) errExit("socket");
svaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (connect(fd[1], (struct sockaddr *) &svaddr, sizeof(svaddr)) == -1)
errExit("connect");

printf("Active socket (fd=%d) set up okay\n", fd[1]);

fd[2] = accept(fd[0], NULL, NULL);
if (fd[2] == -1) errExit("accept");

printf("Connection established (fd=%d)\n", fd[2]);

if (argc > 1) { /* Do a shutdown on active socket */
if (shutdown(fd[1], atoi(argv[1])) == -1)
errExit("shutdown (active socket)");
printf("shutdown(%d, %d)\n", fd[1], atoi(argv[1]));
} /* if */

if (argc > 2) { /* Do a shutdown on accepted socket */
if (shutdown(fd[2], atoi(argv[2])) == -1)
errExit("shutdown (accepted socket)");
printf("shutdown(%d, %d)\n", fd[2], atoi(argv[2]));
} /* if */

FD_ZERO(&rfds);
FD_ZERO(&wfds);
nfds = 0;

for (j = 0; j < 3; j++) {
FD_SET(fd[j], &rfds);
FD_SET(fd[j], &wfds);

if (fd[j] >= nfds)
nfds = fd[j] + 1;
} /* for */

if (select(nfds, &rfds, &wfds, NULL, &tmo) == -1) errExit("select");

for (j = 0; j < 3; j++) {
printf("%d: ", fd[j]);
if (FD_ISSET(fd[j], &rfds))
printf("r ");
if (FD_ISSET(fd[j], &wfds))
printf("w ");
printf("\n");
} /* for */

exit(EXIT_SUCCESS);
} /* main */

-- 
+++ GMX - Mail, Messaging & more  http://www.gmx.net +++
NEU: Mit GMX ins Internet. Rund um die Uhr für 1 ct/ Min. surfen!

- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/