Two relay counters are assigned the same index, so their values sum into one slot and one of them becomes unreadable.
The collision
relay/xdp/relay_constants.h:78,80 — and identically in relay/reference/reference_relay.cpp:106,108:
#define RELAY_COUNTER_RELAY_PING_PACKET_UNKNOWN_RELAY 15
#define RELAY_COUNTER_RELAY_PONG_PACKET_SENT 15
A scan of the whole block finds exactly one duplicate index:
grep "^#define RELAY_COUNTER_" relay/xdp/relay_constants.h | awk '{print $3}' | sort -n | uniq -d
15
Index 16 onward is occupied by the rest of the pong family, so this reads as a missing increment when the pong block was laid out rather than a deliberate alias.
Impact
Both are live in the XDP relay:
relay/xdp/relay_xdp.c:1246 increments RELAY_PING_PACKET_UNKNOWN_RELAY
relay/xdp/relay_xdp.c:1365 increments RELAY_PONG_PACKET_SENT
So slot 15 carries the sum. Because a healthy relay sends pongs continuously and receives pings from unknown relays rarely, the routine success drowns the security-relevant reject completely — the one signal you would want to notice is invisible inside a number that is always large and always climbing. That is the opposite of what the counter is for.
The reference relay increments only RELAY_PONG_PACKET_SENT (reference_relay.cpp:4664); RELAY_PING_PACKET_UNKNOWN_RELAY has no increment site there.
It propagates into the tooling
Both Go name tables assign index 15 twice, so the second silently clobbers the first:
cmd/func_test_relay/func_test_relay.go:6144-6145
cmd/relay_backend/relay_backend.go:797-798
Consequences:
- The relay backend's counter page can never display
RELAY_PING_PACKET_UNKNOWN_RELAY — the name is overwritten before render.
- In the functional tests
checkCounter("...RELAY_PING_PACKET_UNKNOWN_RELAY") and checkCounter("...RELAY_PONG_PACKET_SENT") resolve to the same index and are therefore the same assertion. func_test_relay.go:512 asserts on this slot in test_relay_pings.
Both tables were generated by the awk one-liner preserved in the comment at func_test_relay.go:6129, which faithfully reproduced the collision.
Suggested fix
- Give
RELAY_COUNTER_RELAY_PONG_PACKET_SENT a free index. The ping family block is 10-15 and the pong family runs 15-18 against a block that has room, so either moving pong up or taking a gap slot works; indices are a wire contract (modules/constants/constants.go:13, width checked at modules/packets/relay_packets.go:202-204), so only an unused index is safe.
- Make the Go name tables a generated build step rather than four hand-maintained copies, and assert no duplicate index in the generated output. A generator would not have prevented the original collision, but it gives the duplicate check somewhere to live.
- Add an increment for
RELAY_PING_PACKET_UNKNOWN_RELAY in the reference relay, or record deliberately that the reference does not implement that reject.
Found while studying the relay's counter discipline as a model for another UDP protocol's observability. The discipline itself is good — one counter per distinct early-return, reasons cross-producted with packet type, never reset — which is exactly why a masked reject counter is worth fixing.
Two relay counters are assigned the same index, so their values sum into one slot and one of them becomes unreadable.
The collision
relay/xdp/relay_constants.h:78,80— and identically inrelay/reference/reference_relay.cpp:106,108:A scan of the whole block finds exactly one duplicate index:
Index 16 onward is occupied by the rest of the pong family, so this reads as a missing increment when the pong block was laid out rather than a deliberate alias.
Impact
Both are live in the XDP relay:
relay/xdp/relay_xdp.c:1246incrementsRELAY_PING_PACKET_UNKNOWN_RELAYrelay/xdp/relay_xdp.c:1365incrementsRELAY_PONG_PACKET_SENTSo slot 15 carries the sum. Because a healthy relay sends pongs continuously and receives pings from unknown relays rarely, the routine success drowns the security-relevant reject completely — the one signal you would want to notice is invisible inside a number that is always large and always climbing. That is the opposite of what the counter is for.
The reference relay increments only
RELAY_PONG_PACKET_SENT(reference_relay.cpp:4664);RELAY_PING_PACKET_UNKNOWN_RELAYhas no increment site there.It propagates into the tooling
Both Go name tables assign index 15 twice, so the second silently clobbers the first:
cmd/func_test_relay/func_test_relay.go:6144-6145cmd/relay_backend/relay_backend.go:797-798Consequences:
RELAY_PING_PACKET_UNKNOWN_RELAY— the name is overwritten before render.checkCounter("...RELAY_PING_PACKET_UNKNOWN_RELAY")andcheckCounter("...RELAY_PONG_PACKET_SENT")resolve to the same index and are therefore the same assertion.func_test_relay.go:512asserts on this slot intest_relay_pings.Both tables were generated by the
awkone-liner preserved in the comment atfunc_test_relay.go:6129, which faithfully reproduced the collision.Suggested fix
RELAY_COUNTER_RELAY_PONG_PACKET_SENTa free index. The ping family block is 10-15 and the pong family runs 15-18 against a block that has room, so either moving pong up or taking a gap slot works; indices are a wire contract (modules/constants/constants.go:13, width checked atmodules/packets/relay_packets.go:202-204), so only an unused index is safe.RELAY_PING_PACKET_UNKNOWN_RELAYin the reference relay, or record deliberately that the reference does not implement that reject.Found while studying the relay's counter discipline as a model for another UDP protocol's observability. The discipline itself is good — one counter per distinct early-return, reasons cross-producted with packet type, never reset — which is exactly why a masked reject counter is worth fixing.