Skip to content

Counter index 15 is assigned to two counters, masking RELAY_PING_PACKET_UNKNOWN_RELAY #4223

Description

@rowan-claude

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

  1. 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.
  2. 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.
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions