AI-generated analysisPublished automatically and not human-verified. Validated context appears in community notes below.
← Watch feed
Moderate 61 Bitcoin

onionmessage: add token-bucket rate limiter primitives

Public commit record

What the developer wrote

Authored by Olaoluwa Osuntokun

95/100 · Strong
onionmessage: add token-bucket rate limiter primitives

The existing per-peer actor mailbox (capacity 50, RED from depth 40) only
bounds in-flight queue depth. It does not cap throughput: a peer that
drains its actor quickly can saturate our Sphinx unwrap CPU, replay-DB
writes, and outbound forwarding bandwidth without ever tripping RED. At
spec-max onion message sizes (~32 KiB per sphinx packet) a single
well-behaved-draining peer is enough to push multiple Mbps of unpaid
forwarded traffic through us, and aggregate fan-in from many peers
multiplies that into tens of Mbps — an amount of bandwidth that is very
much out of proportion for a side channel on a payment routing node.

This commit adds the building blocks for two token-bucket limiters that
will be wired into the onion message ingress path in a follow-up commit:
a process-wide global limiter and a per-peer registry. Both drop (rather
than wait) on over-limit so that a hostile peer cannot grow our
goroutine or memory footprint simply by sustaining above-threshold
traffic. The per-peer registry keys buckets on the peer's compressed
pubkey, creates them lazily, and retains them for the lifetime of the
process so a peer cannot reset its burst by cycling the connection;
cardinality is bounded by the live channel-peer count (the ingress call
site gates on having a channel before allocating per-peer state), so no
time-based GC is needed.

A minimal RateLimiter interface is introduced so that callers and tests
can substitute noop or alternate implementations without reaching into
x/time/rate directly, and a small countingLimiter wrapper keeps an
atomic drop counter plus a one-shot first-drop flag for observability.
A rate of zero (or a non-positive burst) yields a noop limiter,
providing a clean "disabled" mode without branching at the call site.

On top of those, a single IngressLimiter interface composes the
per-peer and global buckets behind one surface so that callers —
notably the peer readHandler — only thread one object through Config
and call one method per incoming onion message. Drop reasons are
surfaced as sentinel errors (ErrPeerRateLimit, ErrGlobalRateLimit)
wrapped in fn.Result[fn.Unit] so callers match on them with errors.Is
rather than comparing free-form strings. The stock implementation
encodes the load-bearing ordering — per-peer first, then global —
inside AllowN so that a hostile peer whose own bucket is already empty
cannot burn global tokens on every rejected attempt and starve
legitimate peers.

Default constants targeting roughly ~5 Mbps worst-case ingress at
spec-max message sizes are added alongside the existing mailbox
defaults.
✓ Specific, descriptive subject✓ Names a concrete action or component✓ Provides detailed explanatory context✓ Explains rationale or failure mode✓ Mentions testing or verification
The short version

What changed, and why it matters

This commit adds new rate-limiting building blocks for LND's onion message handling. It does not yet wire them into live message processing, so by itself it cannot stop an attack. The code is clearly preparing to fix a denial-of-service risk: a single peer (or many peers together) could currently flood a routing node with large, unpaid onion messages and consume CPU, database writes, and outbound bandwidth. The new primitives cap per-peer and total incoming onion-message bytes using token buckets, and they drop excess traffic immediately rather than queuing it.

Recommended action

Treat this as a preparatory commit for a DoS hardening change. Review the follow-up commit that wires IngressLimiter into the peer readHandler and verify the limiter is invoked before expensive Sphinx unwrap/replay-DB work. Confirm default rates are configurable and that dropped messages are logged/metriced without leaking per-peer state to unauthenticated peers.

Security signals we found

01

Adds token-bucket rate limiters for onion message ingress

02

Per-peer and global limits with drop-on-over-limit semantics

03

Per-peer buckets retained across reconnections to prevent burst-reset abuse

04

Ordering of per-peer before global check prevents hostile peer from draining shared budget

05

Default constants sized to ~5 Mbps aggregate ingress at spec-max message sizes

06

Commit message describes DoS/amplification risk from unpaid onion message forwarding

07

No-op disabled mode when rate or burst is zero

08

Atomic drop counters and first-drop flags for observability

Risk score

Why this scored 61/100

Our methodology →
Potential impact 12/30
Exploitability 15/25
Stealth signal 10/15
Affected reach 12/15
Confidence 8/10
Evidence quality 4/5
Human-validated context

Community notes

Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.

No validated notes yet.

The AI analysis stands alone for now. Submit a note if you can add evidence or important context.