$RodHat_
Syscall reference · I/O

SO_REUSEPORT

Let multiple sockets bind one address:port; the kernel spreads traffic across them.

Since
Linux 3.9check your kernel →
glibc
none (socket option set with setsockopt(2))
System
Linux
Man page
socket(7)

Synopsis

#include <sys/socket.h>

int setsockopt(int sockfd, int level, int optname,
               const void optval[optlen], socklen_t optlen);
/* level = SOL_SOCKET, optname = SO_REUSEPORT, set before bind(2) */

Rod's take

One listening socket shared by every worker means one accept queue, one lock, and the whole herd waking up for each connection. SO_REUSEPORT gives each worker its own socket on the same port, and the kernel hashes connections across the group. Set it on every socket before bind, same effective UID on all of them. Don't like the hash? Attach a BPF program and pick the socket yourself. What it won't save you from: close a socket and the connections already parked in its accept queue go down with it. Drain before you restart.

Flags and arguments that matter

NameWhat it does
SO_REUSEPORTMust be set on every socket, first included, before bind(2); all binders need the same effective UID.
SO_ATTACH_REUSEPORT_CBPFAttach a classic BPF program returning the index (0..N-1) of the socket that gets the packet.
SO_ATTACH_REUSEPORT_EBPFAttach an eBPF program to pick the socket. UDP support since Linux 4.5, TCP since Linux 4.6.
BPF_PROG_TYPE_SK_REUSEPORTLinux 4.19eBPF program type returning SK_PASS/SK_DROP; selects via bpf_sk_select_reuseport.

Errors you will actually hit

errnoMeaning
EADDRINUSEbind(2): address already in use, e.g. SO_REUSEPORT missing on one of the sockets.
ENOPROTOOPTsetsockopt(2): the option is unknown at the level indicated.
EINVALsetsockopt(2): optlen is invalid.
ENOTSOCKsetsockopt(2): sockfd does not refer to a socket.
EBADFsetsockopt(2): sockfd is not a valid file descriptor.

Example

int fd = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (fd < 0)
    err(1, "socket");

int one = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)) < 0)
    err(1, "SO_REUSEPORT");
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    err(1, "bind");
if (listen(fd, 1024) < 0)
    err(1, "listen");
/* run this in every worker; each accept()s on its own fd */

Trimmed from the full walkthrough in SO_REUSEPORT: each worker gets its own accept queue, and then the migration problem hits.

BSD equivalent

FreeBSD: SO_REUSEPORT_LB load-balances TCP and UDP across up to 256 sockets per group. OpenBSD: SO_REUSEPORT allows duplicate bindings, documented for multicast/broadcast reception rather than load balancing.

The long version, from the console

Sources

  1. socket(7), man7.org
  2. setsockopt(2), man7.org
  3. bind(2), man7.org
  4. getsockopt(2), FreeBSD
  5. getsockopt(2), OpenBSD

Version numbers are read off the cited man pages, not memory. Spot one that's wrong? Tell Rod.