Allow plain and device-tagged listens to share a sockaddr (v0.7.1)
The previous wrapper skipped nginx's duplicate-listen check only
for listens that carried device=, so a `listen 80;` next to a
`listen 80 device=eth0 ...;` in the same server block was
rejected at config time. Under SO_BINDTODEVICE that restriction
tracked a real kernel constraint (device-tagged listens created
separate sockets, a bare listen alongside them was genuinely
ambiguous). Under the IP_PKTINFO model introduced in 450391a
the constraint no longer exists — all same-sockaddr listens
collapse to one wildcard kernel socket and attribution is a
per-connection cmsg readback — but the wrapper kept enforcing
the old rule by accident.
Extend the (cscf, sockaddr) dedup in the listen wrapper to
cover plain listens too: the first occurrence at a given
(server, sockaddr) pair calls nginx's handler and registers the
kernel socket, and every subsequent sibling — plain or
device-tagged — is accepted without tripping nginx's
duplicate-listen check. Device-tagged siblings additionally
push a binding into the attribution table as before; plain
siblings contribute only the seen-list entry. No code path
exercised by the existing 22 e2e tests changes behavior.
Update FR-1.5, the user-guide "shared port" section, the
module's top-of-function comments, and the test nginx.conf
comment to describe the relaxed rule. Bump VERSION and add a
debian/changelog entry for 0.7.1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -112,8 +112,9 @@ http {
|
||||
ipng_stats_flush_interval 1s;
|
||||
ipng_stats_default_source direct;
|
||||
|
||||
# Attributed vhost. Every listen on this port must be device-tagged —
|
||||
# see "All listens on a shared port must be device-tagged" below.
|
||||
# Attributed vhost. Wildcard listens below register one binding
|
||||
# per (device, family); all collapse to a single kernel socket
|
||||
# under the IP_PKTINFO attribution model.
|
||||
server {
|
||||
include /etc/nginx/ipng-stats/listens.conf;
|
||||
|
||||
@@ -176,22 +177,14 @@ You do not need to enumerate VIPs in `listen`. A wildcard `listen 80 device=gre-
|
||||
served through the `gre-mg1` interface, and nginx routes per-request to the right vhost by `server_name` / `Host:` header. Adding a new
|
||||
VIP is a `server_name` change; adding a new interface is an append to `listens.conf`.
|
||||
|
||||
### All listens on a shared port must be device-tagged
|
||||
|
||||
If you use multiple `listen` directives on the same port (e.g. port 80), **every one of them must carry `device=<ifname>`**. Mixing
|
||||
a device-tagged listen with a plain `listen 80;` or with an address-specific `listen 192.0.2.1:80;` on the same port is **not
|
||||
supported** — nginx's config-level dedup rejects same-sockaddr listens within a server block, and the module's wrapper only
|
||||
exempts directives that carry `device=`.
|
||||
|
||||
For "direct" traffic — clients hitting the host on a non-attributed interface — use a **separate port** on the direct interface
|
||||
(e.g. `listen 198.51.100.1:8081;`). That listen then has no `device=`, so it falls back to the tag set by
|
||||
`ipng_stats_default_source` (`direct` by default).
|
||||
|
||||
### Sharing a single port across address families and devices
|
||||
|
||||
Within the device-tagged set, you're free to share port numbers freely across devices and address families: as long as each listen
|
||||
has a distinct `device=`, the kernel keeps them apart, and within one device you can either reuse a single tag or split by family.
|
||||
For example:
|
||||
Under the `IP_PKTINFO` attribution model, all listens at a given sockaddr collapse to a single wildcard kernel socket at runtime —
|
||||
the kernel stamps every accepted connection with its ingress ifindex, and the module looks that up in the table of `device=`
|
||||
bindings registered by the listen wrapper. Multiple device-tagged wildcard listens on port 80 are therefore not "multiple
|
||||
sockets"; they're one wildcard socket plus N entries in the attribution table.
|
||||
|
||||
A device can reuse one tag across address families or split into per-family tags — whichever reads better in the scrape output:
|
||||
|
||||
```nginx
|
||||
listen 80 device=gre-mg1 ipng_source_tag=mg1;
|
||||
@@ -200,6 +193,12 @@ listen 80 device=gre-mg2 ipng_source_tag=mg2-v4;
|
||||
listen [::]:80 device=gre-mg2 ipng_source_tag=mg2-v6; # per-family tags
|
||||
```
|
||||
|
||||
A plain `listen 80;` can sit alongside device-tagged listens in the same server block; the wrapper treats the first occurrence
|
||||
at a given `(server, sockaddr)` pair as the one that registers the kernel socket and lets subsequent device-tagged siblings
|
||||
register bindings without tripping nginx's duplicate-listen check. Traffic arriving on an interface that has no binding falls
|
||||
back to `ipng_stats_default_source` (`direct` by default). Keeping "direct" traffic on its own port — e.g.
|
||||
`listen 198.51.100.1:8081;` — remains a fine pattern when you want a hard split, but it's no longer required.
|
||||
|
||||
## 4. Verify with curl
|
||||
|
||||
Generate some traffic (or wait for real traffic), then scrape the endpoint locally:
|
||||
|
||||
Reference in New Issue
Block a user