On 04/25/2014 11:02 PM, William Morriss wrote:
> Okay I was able to do it without fork. This one been tested on Arch
> Linux and Ubuntu. It's simpler I think and also won't give the bind
> error. It's below and attached.

William,

Thanks for that. I've got it now, I think. The point is I think 
that you are running out of ephemeral ports on the system. An 
ephemeral port is a random port number that an Internet domain 
socket is assigned during any of the following operations:

* bind(), specifying a port number of 0
* listen() on an unbound socket
* connect() on an unbound socket
* sendto() on an unbound socket

The point is that port numbers are 16 bits long, and the ephemeral port 
numbers that are used by unprivileged processes is restricted to the
range given in /proc/sys/net/ipv4/ip_local_port_range (see the ip(7) man
page).

I've appended an even simpler program below that I think produces 
the error for the same reason as your program. (You may need to 
run as root and up your open files limit ("ulimit -n") when 
testing this.) You might want to confirm that it's doing something
equivalent your program, in terms of triggering the error.

Funnily enough, someone asked me the other day what happens if you 
run out of ephemeral ports, and I replied that the relevant system 
call would get an error. The next question was: what error? I said 
I didn't know (was a way from a machine, so could not check the 
man page). And now we see that the error is not in a man page.

However, things are sadly a little worse than I expected. The various 
cases above give different errors if the ephemeral port range is 
exhausted:

* listen() and bind() give EADDRINUSE
* connect() gives EADDRNOTAVAIL
* sendto() gives EAGAIN

I will add this text to connect()

   EADDRNOTAVAIL
          (Internet domain sockets)  The  socket  referred  to  by
          sockfd  had not previously been bound to an address and,
          upon attempting to bind it to an ephemeral port, it  was
          determined  that  all port numbers in the ephemeral port
          range are currently  in  use.   See  the  discussion  of
          /proc/sys/net/ipv4/ip_local_port_range in ip(7).

and similar text to the other pages. And I've updated the text in
ip(7) to say

   ip_local_port_range (since Linux 2.2)
          This file contains two integers that define the  default
          local  port  range  allocated  to  sockets  that are not
          explicitly bound to a port  number—that  is,  the  range
          used  for  ephemeral  ports.  An ephemeral port is allo‐
          cated to a socket in the following circumstances:

          *  the port number in a socket address is specified as 0
             when calling bind(2);

          *  listen(2)  is  called on a stream socket that was not
             previously bound;

          *  connect(2) was called on a socket that  was  not  not
             previously bound;

          *  sendto(2) is called on a datagram socket that was not
             not previously bound.

          Allocation of ephemeral ports starts with the first num‐
          ber in ip_local_port_range and ends with the second num‐
          ber.  If the range of ephemeral ports is exhausted, then
          the relevant system call returns an error (but see BUGS)

          Note  that  the port range in ip_local_port_range should
          not  conflict  with  the  ports  used  by   masquerading
          (although the case is handled).  Also, arbitrary choices
          may cause problems with  some  firewall  packet  filters
          that make assumptions about the local ports in use.  The
          first number should be at least greater  than  1024,  or
          better,  greater  than  4096, to avoid clashes with well
          known ports and to minimize firewall problems.

Thanks for the report.

Cheers,

Michael

/*#* consume_ephemeral_ports_connect.c

   Determine what error is generated when connect() is
   unable to obtain an ephemeral port.

   Usage: ./consume_ephemeral_ports_connect [num-loops [sleep-secs]]
*/
/*#**
   Change history

      Apr 14    Initial creation
*/
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>

#define errMsg(msg)     do { perror(msg); } while (0)

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

int
main(int argc, char *argv[])
{
    struct sockaddr_in svaddr, claddr;
    int sfd, cfd, afd, limit, j;
    socklen_t len;

    limit = (argc > 1) ? atoi(argv[1]) : INT_MAX;

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

    memset(&svaddr, 0, sizeof(struct sockaddr_in));
    svaddr.sin_family = AF_INET;
    svaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    svaddr.sin_port = 0;

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

    len = sizeof(struct sockaddr_in);
    if (getsockname(sfd, (struct sockaddr *) &svaddr, &len) == -1)
        errExit("getsockname");

    printf("Server port: %d\n", svaddr.sin_port);

    if (listen(sfd, 50) == -1)
        errExit("listen");

    for (j = 0; j < limit; j++) {
        cfd = socket(AF_INET, SOCK_STREAM, 0);
        if (cfd == -1)
            errExit("socket");
        if (connect(cfd, (struct sockaddr *) &svaddr, len) == -1) {
            errMsg("connect");
            if (argc > 2)
                sleep(atoi(argv[1]));
            exit(EXIT_FAILURE);
        }
        afd = accept(sfd, NULL, 0);
        if (afd ==-1)
            errExit("accept");

        len = sizeof(struct sockaddr_in);
        if (getsockname(cfd,  (struct sockaddr *) &claddr, &len) == -1)
            errExit("getsockname");

        printf("Client port [%d]: %d\n", j, ntohs(claddr.sin_port));
    }
    
    if (argc > 2)
        sleep(atoi(argv[2]));

    exit(EXIT_SUCCESS);
}

-- 
Michael Kerrisk
Linux man-pages maintainer; http://www.kernel.org/doc/man-pages/
Linux/UNIX System Programming Training: http://man7.org/training/


-- 
To UNSUBSCRIBE, email to debian-bugs-dist-requ...@lists.debian.org
with a subject of "unsubscribe". Trouble? Contact listmas...@lists.debian.org

Reply via email to