Bug 5731 - Zero-length write() does not generate a datagram on connected socket
Summary: Zero-length write() does not generate a datagram on connected socket
Status: RESOLVED CODE_FIX
Alias: None
Product: Networking
Classification: Unclassified
Component: Other (show other bugs)
Hardware: i386 Linux
: P2 normal
Assignee: Stephen Hemminger
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2005-12-12 06:08 UTC by Michael Kerrisk
Modified: 2007-10-04 09:59 UTC (History)
0 users

See Also:
Kernel Version: 2.6.14
Subsystem:
Regression: ---
Bisected commit-id:


Attachments
Sender (673 bytes, text/x-csrc)
2007-09-14 06:12 UTC, Stephen Hemminger
Details
receiver (1.13 KB, text/x-csrc)
2007-09-14 06:13 UTC, Stephen Hemminger
Details

Description Michael Kerrisk 2005-12-12 06:08:42 UTC
Most recent kernel where this bug did not occur: 
this bug occurs in the current and all preceding kernels in at 
least 2.6.x and 2.4.x

Distribution: not applicable (I'm currently test 2.6.15-rc5)

Hardware Environment: x86

Software Environment:

Problem Description:
On most Unix implementations, doing a write() of the form:

write(fd, buf, 0);

on a connected datagram socket delivers a datagram of length zero to
the peer.  (This provide no data of course, but does provide 
metadata ("a datgram arrived").)

This works (see test program below) on Solaris 8, HP-UX 11, Tru64 5.1B,
and FreeBSD 5.5.  It does not work on Linux: it would be good
to make Linux compatible with other implementations in this respect.

The problem is the following lines (which are probably designed to 
catch other unrelated paths) in net/socket.c:sock_aio_read():

       if (size==0)            /* Match SYS5 behaviour */
                return 0;

Steps to reproduce:

If we run the program below on FreeBSD (fr example), we see 
the following:

$ ./a.out
FreeBSD td149.testdrive.hp.com 6.0-RELEASE FreeBSD 6.0-RELEASE #0: Thu Nov  3
01:10:43 UTC 2005     root@ds10.freebie.xs4all.nl:/usr/obj/usr/src/sys/GENERIC 
alpha
Mon Dec 12 14:10:18 EST 2005
Did a write of 0 bytes
Obtained datagram with length 0

But on Linux, we see the following:

$ ./a.out
Linux tekapo 2.6.15-rc5 #6 SMP PREEMPT Wed Dec 7 11:08:45 CET 2005 i686 i686
i386 GNU/Linux
Mon Dec 12 15:07:46 CET 2005
Did a write of 0 bytes
No datagram could be read

/* write_zero_len_dgram.c */
#include <netinet/in.h>
#include <sys/socket.h>
#include <signal.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#define errExit(msg)            do { perror(msg); exit(EXIT_FAILURE); \
                                } while (0)

#define usageErr(msg, progName) \
                                do { fprintf(stderr, "Usage: "); \
                                     fprintf(stderr, msg, progName); \
                                     exit(EXIT_FAILURE); } while (0)
static void
handler(int sig)
{
} /* handler */

int
main(int argc, char *argv[])
{
    int rfd, sfd;
    struct sockaddr_in svaddr;
    char *buf;
    int port;
    ssize_t nw, nr;
    size_t len;
    struct sigaction sa;

    if (argc > 1 && strcmp(argv[1], "--help") == 0)
        usageErr("%s [len [port]]\n", argv[0]);

    system("uname -a; date");

    rfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (rfd == -1) errExit("socket");

    sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sfd == -1) errExit("socket");

    len = (argc > 1) ? atoi(argv[1]) : 0;
    port = (argc > 2) ? atoi(argv[2]) : 55555;

    memset(&svaddr, 0, sizeof(struct sockaddr_in));
    svaddr.sin_family = AF_INET;
    svaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* Wildcard address */
    svaddr.sin_port = htons(port);

    if (bind(rfd, (struct sockaddr *) &svaddr,
                sizeof(struct sockaddr_in)) == -1) errExit("bind");

    if (connect(sfd, (struct sockaddr *) &svaddr,
                sizeof(struct sockaddr_in)) == -1) errExit("connect");
    buf = malloc((len > 0) ? len : 10);
    if (buf == NULL) errExit("malloc");

    nw = write(sfd, buf, len);
    if (nw != len) errExit("write");
    printf("Did a write of %ld bytes\n", (long) len);

    sa.sa_flags = 0;
    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGALRM, &sa, NULL) == -1) errExit("sigaction");

    alarm(2);

    nr = read(rfd, buf, (len > 0) ? len : 10);
    if (nr == -1) {
        if (errno != EINTR)
            errExit("read");
        else
            printf("No datagram could be read\n");
    } else
        printf("Obtained datagram with length %ld\n", (long) nr);

    exit(EXIT_SUCCESS);
} /* main */
Comment 1 Michael Kerrisk 2006-06-29 07:24:45 UTC
You marked this bug as RESOLVED, but I still see the same 
problem in kernel 2.6.17.  What was the fix, and what kernel 
version was it made in?
Comment 2 Michael Kerrisk 2006-11-23 10:34:10 UTC
This bug is still present in 2.6.19-rc5.
Comment 3 Stephen Hemminger 2007-09-06 08:31:07 UTC
My concern is that we would break applications that depend on existing behaviour.
Is there something in Single Unix Standard or Posix about this?
Comment 4 Stephen Hemminger 2007-09-14 06:12:25 UTC
Created attachment 12827 [details]
Sender
Comment 5 Stephen Hemminger 2007-09-14 06:13:07 UTC
Created attachment 12828 [details]
receiver
Comment 6 Stephen Hemminger 2007-09-14 06:14:42 UTC
This appears to work fine with 2.6.23-rc6

$ ./rcv0 0.0.0.0 &
[1] 20322
$ ./snd0 127.0.0.1 0
0 from 127.0.0.1,32816
Comment 7 Michael Kerrisk 2007-09-22 02:44:32 UTC
Stephen, your test programs do not test the case that I am reporting!  The problem occurs with write(2) -- your program uses send(2).  Please try the test program I supplied -- it demonstrates the problem, which still exists in 2.6.23-rc7.
Comment 8 Alan 2007-09-27 05:44:03 UTC
SuSv3 says

Before any action described below is taken, and if nbyte is zero and the file is a regular file, the write() function may detect and return errors as described below. In the absence of errors, or if error detection is not performed, the write() function shall return zero and have no other results. If nbyte is zero and the file is not a regular file, the results are unspecified.


Thus a standards compliant application should use send(), which doesn't make our behaviour neccessarily the right thing to do for non-regular files. OTOH changing it would expose a lot of device driver code to a new and previously "can't happen" case and that might have fallout.
Comment 9 Stephen Hemminger 2007-09-27 07:55:13 UTC
But in this case that comment doesn't apply since the file in question is a socket not a regular file. Changing it doesn't expose device drivers to changes only protocols. The protocols already get the same thing when send() is used, and the mapping from write() to sendmsg happens in socket.c before the protocols see it.

This is really a splitting hairs thing.
Comment 10 Michael Kerrisk 2007-09-27 09:35:22 UTC
Alan, I'm not sure these SUSv3 quote are really relevant.  One has always been able to use write on sockets, on all systems I've known of.  But Linux behavior differs from every other system I know of (as noted in the initial report).  The question is, do we want to make Linux the same for the sake of portability? To be weighed against, what are the risks of breaking existing userland apps on Linux?
Comment 11 Stephen Hemminger 2007-10-04 09:59:25 UTC
Davem fixed this for 2.6.23

Note You need to log in before you can comment on or make changes to this bug.