Skip to content

p2p/discover/topicindex: registration AddNodes record-update path bypasses the per-bucket IP-diversity limit #79

Description

@srene

Summary

In Registration.AddNodes (p2p/discover/topicindex/registration.go), the path that refreshes an already-known node to a newer ENR does not maintain the bucket's DistinctNetSet IP tracker. A node already in a bucket that presents a higher-Seq record with a different IP has its endpoint adopted without the 1-per-/24-per-bucket check ever running on the new IP, and without updating the tracker.

Location

attempt, ok := b.att[id]
if ok {
    // There is already an attempt scheduled with this node.
    // Update the record if newer.
    if attempt.Node.Seq() < n.Seq() {
        attempt.Node = n          // <-- adopts new IP; no ips.Remove(old)/ips.Add(new)
    }
    continue
}

The IP gate (b.ips.Add) runs only on the new-attempt path below it, so the update path skips it entirely.

Effect

The tracker drifts from reality, and the drift can be amplified to seat more than one node per /24 in a bucket:

  1. Node A is admitted normally via /24-X → tracker {/24-X: 1}.
  2. A presents a higher-Seq ENR with IP in /24-Y. attempt.Node is swapped; tracker stays {/24-X: 1} while A actually lives in /24-Y.
  3. A new node B in /24-Y is offered. ips.Add(/24-Y) sees count 0 and succeeds → the bucket now holds A and B both in /24-Y, defeating the "one /24 can't dominate a bucket" property.

Side effect: the old /24-X entry is never released (removeAttempt later removes the node's current IP, /24-Y), so /24-X stays counted forever and blocks honest nodes from that subnet.

Severity

Low / corner case. The node must remain reachable at the new IP to keep functioning as a registrar, so this is not pure spoofing — the attacker must actually hold IP resources in the target subnets, which is exactly the cost the /24 limit is meant to impose. The practical effect is a slow erosion of the IP-diversity guarantee plus a tracker leak, not an immediate eclipse. Worth fixing for correctness rather than as an urgent security hole.

Scope

Suggested fix

On the update path, when the IP changes, keep the tracker consistent — b.ips.Remove(oldIP) then b.ips.Add(newIP); if Add fails (the new /24 is full), reject the IP change or evict the attempt rather than silently adopting it. Add a regression test: admit a node via one /24, bump its Seq to a second /24, then verify a second node in that second /24 is still rejected and that the first /24 is released.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions