Strip socket options on cross-cscf repeat listens (v0.7.2)

Make the shared-listen-include pattern work with `reuseport` and the
other socket-level listen options. Nginx core enforces at-most-once
per sockaddr on options that set lsopt.set=1 (reuseport, bind,
backlog=, rcvbuf=, sndbuf=, setfib=, fastopen=, accept_filter=,
deferred, ipv6only=, so_keepalive=) and emits "duplicate listen
options for <addr>" otherwise. That rule collides with a single
listens.conf included from every vhost — each vhost's include
re-submits the same options.

The listen wrapper now detects the cross-cscf case, strips those
options from cf->args before delegating to the core handler, and
logs one notice per stripped listen. The first cscf owns the
options on the kernel socket; later cscfs merge cleanly via
ngx_http_add_server. Protocol-level flags (ssl, http2, quic,
proxy_protocol) pass through untouched since nginx OR-merges those
across cscfs.

This unblocks `reuseport` for deployments that want better
new-connection spread across workers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-19 16:23:58 +02:00
parent badb684431
commit 7ed77f5b22
8 changed files with 168 additions and 18 deletions

View File

@@ -5,8 +5,15 @@
# file from multiple server blocks to exercise the wrapper's dedup
# logic: a naive implementation would either error with "duplicate
# listen options" or create N * (devices × families) sockets.
#
# `reuseport` is present on every listen to exercise the wrapper's
# cross-cscf option-stripping path: nginx itself would reject the
# second server block's include with "duplicate listen options" if
# reuseport weren't stripped from the repeat calls. The first cscf's
# listen binds the reuseport-cloned kernel socket; subsequent ones
# merge cleanly into it.
listen 8080 device=eth1 ipng_source_tag=tag1;
listen [::]:8080 device=eth1 ipng_source_tag=tag1;
listen 8080 device=eth2 ipng_source_tag=tag2-v4;
listen [::]:8080 device=eth2 ipng_source_tag=tag2-v6;
listen 8080 reuseport device=eth1 ipng_source_tag=tag1;
listen [::]:8080 reuseport device=eth1 ipng_source_tag=tag1;
listen 8080 reuseport device=eth2 ipng_source_tag=tag2-v4;
listen [::]:8080 reuseport device=eth2 ipng_source_tag=tag2-v6;

View File

@@ -48,9 +48,14 @@ http {
# pair, so each server block gets its own cscf attached but no
# server block triggers nginx's "duplicate listen options"
# check;
# * strip socket-level options (reuseport, bind, backlog=, ...)
# from cross-cscf repeat sockaddrs — nginx enforces these
# at-most-once per sockaddr, and the first cscf already owns
# the single kernel socket that the remaining cscfs merge
# into;
# * dedup bindings globally on (sockaddr, device), so init_module
# creates exactly four sockets here (two families × two
# devices) rather than 3 × 4 = 12.
# creates exactly one binding per (device, family) rather than
# one per (server block × device × family).
# The default server owns the locations used by the traffic tests;
# the two extras exist only to exercise the shared-include pattern.
server {