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
| Name | What it does |
|---|---|
| SO_REUSEPORT | Must be set on every socket, first included, before bind(2); all binders need the same effective UID. |
| SO_ATTACH_REUSEPORT_CBPF | Attach a classic BPF program returning the index (0..N-1) of the socket that gets the packet. |
| SO_ATTACH_REUSEPORT_EBPF | Attach an eBPF program to pick the socket. UDP support since Linux 4.5, TCP since Linux 4.6. |
| BPF_PROG_TYPE_SK_REUSEPORTLinux 4.19 | eBPF program type returning SK_PASS/SK_DROP; selects via bpf_sk_select_reuseport. |
Errors you will actually hit
| errno | Meaning |
|---|---|
| EADDRINUSE | bind(2): address already in use, e.g. SO_REUSEPORT missing on one of the sockets. |
| ENOPROTOOPT | setsockopt(2): the option is unknown at the level indicated. |
| EINVAL | setsockopt(2): optlen is invalid. |
| ENOTSOCK | setsockopt(2): sockfd does not refer to a socket. |
| EBADF | setsockopt(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
- socket(7), man7.org
- setsockopt(2), man7.org
- bind(2), man7.org
- getsockopt(2), FreeBSD
- getsockopt(2), OpenBSD
Version numbers are read off the cited man pages, not memory. Spot one that's wrong? Tell Rod.
