Home > database >  Correct way to use SO_REUSEADDR?
Correct way to use SO_REUSEADDR?

Time:01-19

Below is abstract code of something like a daemon. I need to check if my daemon is already here (if yes, I exit). Then I close the bound socket, do some forks (they will live after the daemon is restarted, so I don't want them having my bound socket).

int is_me_here()
{
  int sck = socket(AF_INET, SOCK_STREAM, 0), temp=1;
  struct sockaddr_in addr; 
  addr.sin_port = htons(1234);
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = INADDR_ANY;
  bzero(addr.sin_zero, 8); 

  //setsockopt(sck,SOL_SOCKET,SO_REUSEADDR,&temp,sizeof(int)); //       #1 HERE?  
  if ((bind(sck, (struct sockaddr *)(&addr), sizeof(struct sockaddr_in))) < 0){
    printf("cannot bind. My daemon is already here\n"); 
    return -1; 
  }
  close(sck); 
  return 0;
}

void foo(){
  int sck = socket(AF_INET, SOCK_STREAM, 0), temp=1, new_sockfd;
  struct sockaddr_in addr, cliaddr; 
  addr.sin_port = htons(1234);
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = INADDR_ANY;
  bzero(addr.sin_zero, 8); 

  //setsockopt(sck,SOL_SOCKET,SO_REUSEADDR,&temp,sizeof(int)); //       #2 HERE? 
  if ((bind(sck, (struct sockaddr *)(&addr), sizeof(struct sockaddr_in))) < 0){
    printf("WTF\n"); 
    return; 
  }
  listen(sck, 5);
  socklen_t lenaddr = sizeof(struct sockaddr_in); 
  while(1){
    if ((new_sockfd = accept(sck, (struct sockaddr *)(&cliaddr), &lenaddr)) == -1){
      sleep(1); 
      continue;
    }
    // ...
    close(new_sockfd); 
  }
}

int main()
{
  if (is_me_here() < 0)
    return 0; 
  //  some forks. Dont wanna them having the socket binded. 
  foo();  
  return 0;
}

As I know, the kernel keeps bound sockets in TIME_WAIT. So, I need to use SO_REUSEADDR to bind again after the forks. But, where is the right place to apply SO_REUSEADDR? Will the kernel keep my socket in TIME_WAIT after is_me_here()? I don't listen() or accept() something.

P.S. on my system, and some other systems, the code works fine wherever I set the SO_REUSEADDR. But I am afraid that some another system will give me an error in bind() in foo().

CodePudding user response:

In general, you always want to set SO_REUSEADDR on a TCP listening socket.

If you don't set it, if your program shuts down and is then restarted within 1 or 2 minutes, you can get an "address already in use" error if there are still sockets in TIME_WAIT.

You'll still get an "address already in use" error if you attempt to start the program and another program is already listening on that port.

This also means you don't need to open an extra socket first just to check. Just do it once, and exit if the socket is already in use.

CodePudding user response:

You have to set the flag to an int, pass the pointer and the size of the flag.

You can use SO_REUSEADDR on both UDP and TCP sockets, right after you create the socket.

    // reuse
    int reuse = 1;
    int result = setsockopt(sockid, SOL_SOCKET, SO_REUSEADDR, (void *)&reuse, sizeof(reuse));
    if ( result < 0 ) {
        perror("ERROR SO_REUSEADDR:");
    }

CodePudding user response:

Set it after successfully initialising the socket:

int sck = socket(AF_INET, SOCK_STREAM, 0)

These errors return -1 on failure, so you should for them.

errno = 0;
if ((sck = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
     perror("socket");
     /* We failed to open a socket, handle the error here */
}

Now set the option:

if (setsockopt(sck, SOL_SOCKET, SO_REUSEADDR, &temp, sizeof(int)) == -1) {
    perror("setsockopt");
    /* Handle error here */
}

Now bind().

See also: How do SO_REUSEADDR and SO_REUSEPORT differ?

  • Related