[Spread-users] SO_REUSEADDR Final Chapter?

Jonathan Stanton jonathan at cnds.jhu.edu
Fri Sep 21 10:24:02 EDT 2001


The REUSEADDR "discussion" from a month or so ago prompted me to
to get some hard facts to argue about instead of assertions. I wrote a
small test program that is attached to this email and ran it on all the
platforms I could get ahold of easily. 

The results show that, handling of SO_REUSEADDR is a bit mixed up.

Essentially, each operating system behaves
differently, and things have evolved since Steven's book. Here are the
results I have. I ask that anyone who is willing and has access to other
platforms to give the program a try and report what they find. I'll record
everything and post it to the website.

The tests consisted of running two copies of reuseaddr_test. One would
bind to one of { localhost, INADDR_ANY, local ethernet addr } and could
choose to just bind, or bind and listen. It also may use SO_REUSEADDR on
the socket or it might not. This first copy then sleeps for 30
seconds. During this period the other copy would be run with some other
set of parameters and would report whether it successfully bound the
socket or not. I usually ran netstat to verify the "bad" cases that the OS
actually did allow two binds to the same port when one was INADDR_ANY and
one was an ip address or localhost. Additionally, I reran the tests with
the programs being run by different users.

Linux (redhat 7 and 7.1) kernels 2.2.19 and 2.4.x

Do not allow any double binds even when same user and REUSEADDR.

Freebsd 4.0 Release

Allow double binds when programs run as same user and REUSEADDR set.
Do not allow double binds if programs run by different users.

BSDI 4.0.1
Allow double binds when programs run as either same or different users
when REUSEADDR is set. --This is the security risk case.

Solaris 5.7 x86
Allow double binds when programs run as either same or different users
when REUSEADDR is set. --This is the security risk case.


On all of them REUSEADDR did what we want it to do. When a program exited,
if REUSEADDR had been set, the program could rebind to the same ip/port.

So based on this it would seem using REUSEADDR is safe on linux and
freebsd 4, is not safe on bsdi 4.0.1 and solaris 5.7 and is unknown on
others. 

Any suggestions as to how to best handle this in Spread? 

I don't want to blindly enable it again, but it is not clear how to be
sure it works on any particular platform. It even looks like an autoconf
test is tricky as the test must be run as two different users to check for
cases like freebsd which is safe, but doesn't look like it is safe if you
run the test as the only one user.

Thanks,

Jonathan

-- 
-------------------------------------------------------
Jonathan R. Stanton         jonathan at cs.jhu.edu
Dept. of Computer Science   
Johns Hopkins University    
-------------------------------------------------------
-------------- next part --------------
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MY_PORT     8000

static  void    Print_help();
static	void	Usage(int argc, char *argv[]);

static  int     Use_Any;
static  int     Use_Loop;
static  int     Use_Listen;
static  int     Use_Reuse;
static  int     Use_Eth;
static	int	Eth_ip[4];

int
main(int argc, char *argv[])
{
  int any_fd, this_fd, eth_fd;
  int val = 1;
  struct sockaddr_in addr;

  Usage(argc, argv);

  if ( Use_Any 
       && ( (any_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0
            || fcntl(any_fd, F_SETFL, O_NONBLOCK) < 0 ) ) {
      perror("setup(any)");
      return 1;
  }
  if ( Use_Any && Use_Reuse 
       && setsockopt(any_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof val) < 0) {
      perror("ruseaddr(any)");
      return 1;
  }
  if ( Use_Loop 
       && ( (this_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0
            || fcntl(this_fd, F_SETFL, O_NONBLOCK) < 0 ) ) {
      perror("setup(this)");
      return 1;
  }
  if ( Use_Loop && Use_Reuse 
       && setsockopt(this_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof val) < 0) {
      perror("ruseaddr(this)");
      return 1;
  }

  if ( Use_Eth 
       && ( (eth_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0
            || fcntl(eth_fd, F_SETFL, O_NONBLOCK) < 0 ) ) {
      perror("setup(eth)");
      return 1;
  }
  if ( Use_Eth && Use_Reuse 
       && setsockopt(eth_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof val) < 0) {
      perror("ruseaddr(eth)");
      return 1;
  }

  addr.sin_family = AF_INET;
  addr.sin_port = htons(MY_PORT);
  memset(addr.sin_zero, 0, sizeof (addr.sin_zero));

  addr.sin_addr.s_addr = htonl(INADDR_ANY);

  if ( Use_Any && (bind(any_fd, (struct sockaddr *)&addr, sizeof (addr)) < 0) ) {
      perror("bind(any)");
      return 1;
  }

  if ( Use_Any && Use_Listen && (listen(any_fd, 10) < 0) ) {
      perror("listen(any)");
      return 1;
  }
#ifdef __linux__
  addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
#else
  addr.sin_addr.s_addr = htonl(127 << 24 | 0 << 16 | 0 << 8 | 1);
#endif
  if ( Use_Loop && (bind(this_fd, (struct sockaddr *)&addr, sizeof (addr)) < 0) ) {
      perror("bind(this)");
      return 1;
  }
  if ( Use_Loop && Use_Listen && (listen(this_fd, 10) < 0) ) {
      perror("listen(this)");
      return 1;
  }
  addr.sin_addr.s_addr = htonl(Eth_ip[0] << 24 | Eth_ip[1] << 16 | Eth_ip[2] << 8 | Eth_ip[3]);

  if ( Use_Eth && (bind(eth_fd, (struct sockaddr *)&addr, sizeof (addr)) < 0) ) {
      perror("bind(eth)");
      return 1;
  }
  if ( Use_Eth && Use_Listen && (listen(eth_fd, 10) < 0) ) {
      perror("listen(eth)");
      return 1;
  }
  printf("Sleeping for 30 seconds...");
  fflush(NULL);
  sleep(30);
  printf("done!\n");
  return 0;
}

static	void	Usage(int argc, char *argv[])
{
    char ips[16];
    char *ret;
    int i, iret;

    Use_Any = 0;
    Use_Loop = 0;
    Use_Listen = 0;
    Use_Reuse = 0;
    Use_Eth = 0;
    Eth_ip[0] = 0;
    Eth_ip[1] = 0;
    Eth_ip[2] = 0;
    Eth_ip[3] = 0;
    while( --argc > 0 )
    {
        argv++;
        
        if( !strncmp( *argv, "-a", 2 ) )
        {
            Use_Any = 1;
        }else if( !strncmp( *argv, "-o", 2 ) ){
            Use_Loop = 1;
        }else if( !strncmp( *argv, "-l", 2 ) ){
            Use_Listen = 1;
        }else if( !strncmp( *argv, "-r", 2 ) ){
            Use_Reuse = 1;
        }else if( !strncmp( *argv, "-e", 2 ) ){
            Use_Eth = 1;
	    if (argc < 2) Print_help();
	    strncpy(ips, argv[1], 16);
	    for(i=0; i< 3; i++)
	    {
		ret = strchr(ips, '.' );
		if ( ret == NULL)
		{
		  printf("error in ip addr\n");
		  Print_help();
		}
		*ret = ' ';
	    }
	    iret = sscanf(ips,"%d%d%d%d",
			  &Eth_ip[0], &Eth_ip[1], &Eth_ip[2], &Eth_ip[3]);
	    if( iret < 4 ) 
	    { 
	      printf("not a valid ip address: %s\n",ips);
	      Print_help();
	    }
	    printf("selected ip address %d.%d.%d.%d with string %s\n", Eth_ip[0],Eth_ip[1], Eth_ip[2], Eth_ip[3], argv[1]);
	    argc--; argv++;
        }else{
                    Print_help();
        }
    }
}

static  void    Print_help()
{
    printf( "Usage: reuseaddr_test\n%s\n%s\n%s\n%s\n%s\n",
            "\t[-a ]  : bind to INADDR_ANY",
            "\t[-e <ip address> ]  : bind to specified local Ethernet interface",
            "\t[-o ]  : bind to INADDR_LOOPBACK",
            "\t[-l ]  : use listen",
            "\t[-r ]  : use SO_REUSEADDR");
    exit( 0 );
}


More information about the Spread-users mailing list