SO_REUSEADDR and accept() Inheritance

 

SO_REUSEADDR and accept() Inheritance
Chapter 61 — Part 1 | Advanced Socket Topics | EmbeddedPathashala

← Back to Chapter 61 Index

Overview

When you write a server program, two important things happen before it can accept connections: you set socket options (like SO_REUSEADDR), and then you call accept() which creates a new socket for each client. A natural question arises: does that new socket inherit all the settings you applied to the listening socket?

The answer is: it depends on what kind of setting it is. This tutorial explains the rules clearly.

Key Terms

SO_REUSEADDR setsockopt() accept() O_NONBLOCK O_ASYNC FD_CLOEXEC fcntl() F_SETFL F_SETOWN F_SETSIG

1. Why SO_REUSEADDR Is Important

When you stop and restart a server, the old TCP connection can stay in a TIME_WAIT state for up to 2 minutes. During this time, if you try to bind() to the same port again, you get an error: “Address already in use”.

The SO_REUSEADDR socket option tells the kernel: “Allow me to bind to this address/port even if it is still in TIME_WAIT.” This is why almost every TCP server sets this option before calling bind().

What Happens When You Restart a Server
Without SO_REUSEADDR
Server stops → port in TIME_WAIT → bind() fails → EADDRINUSE error
With SO_REUSEADDR
Server stops → port in TIME_WAIT → bind() succeeds → Server restarts cleanly

Code: Correct Server Socket Setup

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int sockfd, optval;
    struct sockaddr_in addr;

    /* Step 1: Create the socket */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    /* Step 2: Set SO_REUSEADDR BEFORE bind() */
    optval = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
                   &optval, sizeof(optval)) == -1) {
        perror("setsockopt SO_REUSEADDR");
        exit(EXIT_FAILURE);
    }

    /* Step 3: Bind to address */
    addr.sin_family      = AF_INET;
    addr.sin_port        = htons(8080);
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    /* Step 4: Start listening */
    if (listen(sockfd, 5) == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port 8080\n");
    /* ... accept loop ... */
    return 0;
}

Key rule: Always call setsockopt(SO_REUSEADDR) before bind(). Setting it after bind has no effect on the current bind.

2. Two Sockets: Listening vs. Connected

A TCP server actually uses two different sockets:

Listening Socket
Created by socket(), configured with setsockopt(), bound with bind(), set passive with listen().
This socket is reused for ALL clients. It never actually sends/receives application data.

accept()
Connected Socket
Returned by accept(). A brand-new socket representing ONE specific client connection.
This is the socket you use for read()/write(). A new one is created per client.

The critical question is: what does the connected socket inherit from the listening socket?

3. What Is NOT Inherited by accept()

On Linux, the following are not inherited by the new socket returned from accept(). This is actually a good thing — it prevents unwanted behavior from leaking into client connections.

What is NOT inherited How to set it Why it matters
O_NONBLOCK fcntl(fd, F_SETFL, O_NONBLOCK) Non-blocking I/O mode
O_ASYNC fcntl(fd, F_SETFL, O_ASYNC) Signal-driven I/O
FD_CLOEXEC fcntl(fd, F_SETFD, FD_CLOEXEC) Close on exec()
F_SETOWN (owner PID) fcntl(fd, F_SETOWN, pid) Target process for SIGIO
F_SETSIG (custom signal) fcntl(fd, F_SETSIG, signum) Signal for async I/O notification

In simple terms: open file status flags (set via fcntl F_SETFL) and file descriptor flags (set via fcntl F_SETFD) are NOT inherited.

4. What IS Inherited by accept()

The new connected socket does inherit a copy of most socket options set via setsockopt(). This includes important options like:

  • SO_KEEPALIVE — keep-alive probes
  • SO_LINGER — linger on close behavior
  • SO_RCVBUF / SO_SNDBUF — receive/send buffer sizes
  • TCP_NODELAY — disable Nagle algorithm
  • Most other setsockopt() options at SOL_SOCKET and IPPROTO_TCP level

So if you set TCP_NODELAY on the listening socket, every connected socket you get from accept() will also have TCP_NODELAY enabled. This saves you from calling setsockopt() again for each client.

5. Portability Issue and How to Handle It

The behavior described above is for Linux. On some other UNIX systems (like older BSD systems), O_NONBLOCK and O_ASYNC are inherited from the listening socket. This creates a portability trap.

⚠️ Portability Rule: If your code needs to run on multiple UNIX systems, always explicitly set (or reset) O_NONBLOCK and O_ASYNC on the socket returned by accept(). Do not rely on whether these flags are inherited or not.

Code: Making accept() Socket Non-Blocking (Portable Way)

#include <sys/socket.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

/* Helper: set a socket to non-blocking mode */
static void set_nonblocking(int fd)
{
    int flags;

    flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) {
        perror("fcntl F_GETFL");
        exit(EXIT_FAILURE);
    }

    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
        perror("fcntl F_SETFL O_NONBLOCK");
        exit(EXIT_FAILURE);
    }
}

int main(void)
{
    int listenfd, connfd;
    struct sockaddr_in client_addr;
    socklen_t addrlen = sizeof(client_addr);

    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    /* ... bind(), listen() ... */

    connfd = accept(listenfd, (struct sockaddr *)&client_addr, &addrlen);
    if (connfd == -1) {
        perror("accept");
        exit(EXIT_FAILURE);
    }

    /*
     * Portability: explicitly set O_NONBLOCK on the connected socket.
     * On Linux, O_NONBLOCK is NOT inherited from the listening socket.
     * On some BSD systems, it IS inherited.
     * Explicit setting handles both cases correctly.
     */
    set_nonblocking(connfd);

    printf("Client connected, connfd=%d (non-blocking)\n", connfd);
    /* ... read()/write() on connfd ... */
    return 0;
}

Code: Making accept() Socket Close-on-Exec (Portable Way)

#include <sys/socket.h>
#include <fcntl.h>

/*
 * FD_CLOEXEC is also NOT inherited by the accept() socket on Linux.
 * If your server forks and execs child processes, you want connected
 * sockets to be closed automatically in the child's exec().
 * Set it explicitly after accept().
 */
int accept_cloexec(int listenfd)
{
    int connfd;
    struct sockaddr_storage client_addr;
    socklen_t addrlen = sizeof(client_addr);
    int flags;

    connfd = accept(listenfd, (struct sockaddr *)&client_addr, &addrlen);
    if (connfd == -1)
        return -1;

    /* Set close-on-exec flag on the new socket */
    flags = fcntl(connfd, F_GETFD, 0);
    if (flags == -1 || fcntl(connfd, F_SETFD, flags | FD_CLOEXEC) == -1) {
        close(connfd);
        return -1;
    }

    return connfd;
}

/*
 * Linux 2.6.28+ alternative: use accept4() which can set flags atomically.
 * This avoids a race condition between accept() and fcntl().
 */
int accept_cloexec_atomic(int listenfd)
{
    struct sockaddr_storage client_addr;
    socklen_t addrlen = sizeof(client_addr);

    /* SOCK_CLOEXEC sets FD_CLOEXEC atomically */
    return accept4(listenfd, (struct sockaddr *)&client_addr,
                   &addrlen, SOCK_CLOEXEC | SOCK_NONBLOCK);
}

Tip: On Linux, prefer accept4() with SOCK_NONBLOCK and/or SOCK_CLOEXEC flags. This sets both atomically, which avoids the tiny race window between accept() and the subsequent fcntl() call.

6. Summary: Inheritance Rules at a Glance

What accept() socket inherits on Linux
❌ NOT Inherited
O_NONBLOCK (fcntl F_SETFL)
O_ASYNC (fcntl F_SETFL)
FD_CLOEXEC (fcntl F_SETFD)
F_SETOWN (owner PID)
F_SETSIG (signal number)
✅ IS Inherited
SO_KEEPALIVE
SO_LINGER
SO_RCVBUF / SO_SNDBUF
TCP_NODELAY
Most setsockopt() options

Interview Questions

Q1. What is the purpose of SO_REUSEADDR, and when should you set it?

It allows a server to bind to a port that is still in the TCP TIME_WAIT state from a previous connection. It must be set before calling bind(). Almost every TCP server should set this option so it can restart quickly without waiting 2 minutes for TIME_WAIT to expire.

Q2. Does the socket returned by accept() inherit O_NONBLOCK from the listening socket on Linux?

No. On Linux, O_NONBLOCK is an open file status flag set via fcntl F_SETFL and it is NOT inherited. The connected socket starts in blocking mode regardless of the listening socket’s flags. You must explicitly call fcntl(connfd, F_SETFL, O_NONBLOCK) or use accept4() with SOCK_NONBLOCK.

Q3. Which socket options ARE inherited by the socket returned from accept()?

Most options set via setsockopt() are inherited — for example, SO_KEEPALIVE, SO_RCVBUF, SO_SNDBUF, SO_LINGER, and TCP_NODELAY. This means you can configure these on the listening socket once and all accepted client sockets will automatically get the same settings.

Q4. What is accept4() and why is it preferred over accept() + fcntl()?

accept4() is a Linux-specific extension that accepts a flags argument. You can pass SOCK_NONBLOCK and/or SOCK_CLOEXEC to set these flags atomically on the returned socket descriptor. This avoids the race condition that exists between accept() and a subsequent fcntl() call in multi-threaded servers.

Q5. Why is FD_CLOEXEC useful on a connected socket in a forking server?

In a server that handles clients by forking a child process and then exec-ing a helper program, you do not want the connected client socket to remain open in the exec’d process. Setting FD_CLOEXEC on the connected socket ensures it is automatically closed when the child calls exec(), preventing resource leaks.

Q6. SUSv3 does not define accept() inheritance rules. What does this mean for portable code?

Since the POSIX standard (SUSv3) is silent on whether file status flags like O_NONBLOCK are inherited by the accept() socket, different UNIX implementations behave differently — some inherit them, some do not. For portable code, always explicitly set or clear these flags on the socket returned by accept() rather than relying on inheritance behavior.

Leave a Reply

Your email address will not be published. Required fields are marked *