Subject: kern/29750: send/sendto/sendmsg don't block when no buffer space is available
To: None <kern-bug-people@netbsd.org, gnats-admin@netbsd.org,>
From: None <manu@netbsd.org>
List: netbsd-bugs
Date: 03/20/2005 22:23:00
>Number:         29750
>Category:       kern
>Synopsis:       send/sendto/sendmsg don't block when no buffer space is available
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sun Mar 20 22:23:00 +0000 2005
>Originator:     Emmanuel Dreyfus
>Release:        NetBSD-current (3.99.1)
>Organization:
The NetBSD Project
>Environment:
>Description:
NetBSD man page and Single Unix Specification say that if there is no kernel buffer space available, send/sendto/sendmsg should block if the socket was not set for non blocking I/O. 

Currently, we don't block, we return ENOBUFS, regardless if non blocking I/O was set. This breaks the standard.
>How-To-Repeat:
Use this test program with the numeric IP of a host near you as the first argument (NB: this will flood the network to get out of buffers. At mine it fails after sending 317 kB) 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include <time.h>

#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>

#include <arpa/inet.h>
#include <netinet/in.h>

int
main(ac, av) 
        int ac;
        char **av;
{
        int sd;
        int i;
        struct sockaddr_in src_addr;
        struct sockaddr_in send_addr;
        char *packet;
        size_t packet_size;

        if (ac != 2) {
                printf("Usage: test remote-host-ip\n");
                return 1;
        }

        if ((sd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
                printf("Cannot create UDP/IP socket: %s\n", strerror(errno));
                exit(-1);
        }

        (void)memset(&src_addr, 0, sizeof(src_addr));
        src_addr.sin_family = AF_INET;
        src_addr.sin_len = sizeof(src_addr);
        src_addr.sin_addr.s_addr = htonl(0x00000000);
        src_addr.sin_port = htons(5002);

        (void)memset(&send_addr, 0, sizeof(send_addr));
        send_addr.sin_family = AF_INET;
        send_addr.sin_len = sizeof(send_addr);
        if (inet_aton(av[1], &send_addr.sin_addr) != 1) {
                printf("inet_aton: %s\n", strerror(errno));
                exit(-1);
        }
        send_addr.sin_port = htons(5002);

        if ((bind(sd, (struct sockaddr *)&src_addr, sizeof(src_addr))) == -1) {
                printf("Cannot bind to UDP port 5002: %s\n", strerror(errno));
                exit(-1);
        }

        packet_size = 1024;
        if ((packet = malloc(packet_size)) == NULL) {
                perror("Cannot allocate buffer");
                exit(-1);
        }
        bzero(packet, sizeof(*packet));
        
        for (i = 0; i < (1024 * 1024); i++) {
                if (sendto(sd, packet, packet_size, 0, 
                    (struct sockaddr *)&send_addr, sizeof(send_addr)) == -1) {
                        if (errno == ENOBUFS) {
                                printf("TEST FAILED (%d packets)\n", i);
                                exit(1);
                        } else {
                                perror("Send failed");
                        }
                }
        }

        printf("TEST PASSED\n");
        return 0;
}
>Fix:
None yet. Maybe in src/sys/kern/uipc_syscalls.c:sendit() loop around so->so_send(), sleeping when we get ENOBUFS? Of course that require the code that frees the buffer to wake us up.