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:
@@ -31,14 +31,16 @@ Module loads
|
||||
|
||||
Shared-listen-include across multiple server blocks
|
||||
[Documentation] Three server blocks all pull in the same
|
||||
... ipng-listens.inc (see docs/user-guide.md). nginx
|
||||
... must start without "conflicting server name" or
|
||||
... "duplicate listen options" warnings, and the
|
||||
... module must end up with exactly one listening
|
||||
... socket per address family on port 8080 (one for
|
||||
... v4 wildcard, one for v6) — not one per (server
|
||||
... block × device × family), which would exhaust
|
||||
... the fd table on a real host.
|
||||
... ipng-listens.inc (see docs/user-guide.md). The
|
||||
... include also carries `reuseport` on every listen
|
||||
... — nginx core would normally reject the second
|
||||
... server block with "duplicate listen options", but
|
||||
... the wrapper strips socket-level options on a
|
||||
... repeat (cross-cscf) sockaddr so the first cscf
|
||||
... owns the reuseport-cloned socket and the rest
|
||||
... merge cleanly. With worker_processes unset
|
||||
... (default 1), reuseport produces one socket per
|
||||
... (worker × family), i.e. 2 on :8080 here.
|
||||
${output} = Docker Exec ${SERVER} nginx -t 2>&1
|
||||
Should Not Contain ${output} conflicting server name
|
||||
Should Not Contain ${output} duplicate listen
|
||||
@@ -46,6 +48,10 @@ Shared-listen-include across multiple server blocks
|
||||
${count} = Get Regexp Matches ${listens} :8080\\s
|
||||
Length Should Be ${count} 2
|
||||
... Expected 2 listening sockets on port 8080 (v4+v6 wildcards); got ${count}
|
||||
# Proves the cross-cscf option-stripping path actually fired for
|
||||
# the 2nd and 3rd server blocks. `nginx -t` replays the whole
|
||||
# config and emits the wrapper's NOTICE each time it strips.
|
||||
Should Contain ${output} stripped socket options from duplicate listen
|
||||
|
||||
Prometheus scrape
|
||||
[Documentation] Scrape returns HELP/TYPE preamble.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user