Skip to content

Rate limiter: independent config defaults, ASN-sharing fix, bounded maps, contention cut - #65

Closed
Tumult1337 wants to merge 3 commits into
awlx:mainfrom
Tumult1337:pr/ratelimit
Closed

Rate limiter: independent config defaults, ASN-sharing fix, bounded maps, contention cut#65
Tumult1337 wants to merge 3 commits into
awlx:mainfrom
Tumult1337:pr/ratelimit

Conversation

@Tumult1337

Copy link
Copy Markdown
Contributor

Rate limiter correctness + hot-path perf.

Commits

  • Default each rate-limiter config field independently (a single zero field no longer wipes the others).
  • Fix ASN-limiter sharing across unrelated unresolved ASNs, bound the limiter maps, and stop inert rate updates.
  • Cut per-signal lock contention on the Allow fast path (perf).

Reviewer callout (low)

  • Batch eviction (EnforceMaxSizeBatch) runs under the exclusive limiter lock; under a distinct-source-IP flood at the map cap it takes each bucket lock + an O(n log n) sort per batch. Amortized/bounded, but it is on the attack path — worth a benchmark if fleet-wide Allow latency matters under load.

Validation: go build ./... clean; go test ./pkg/ratelimit passes.

NewLimiter only substituted defaults when IPRate was zero, so a partial
config (IPRate set, CleanupInterval unset) reached cleanupLoop with a
zero interval and panicked in time.NewTicker on a background goroutine,
taking down the process. Clamp every field to its default when unset or
non-positive.
- AllowASN no longer buckets on the "Unknown"/empty ASN sentinel that
  pkg/geoip returns for unresolved lookups. Previously every client
  with an unresolved ASN shared one TokenBucket, so an attacker could
  drain it and collaterally block unrelated victims (W5).
- ipLimiters/asnLimiters now enforce a hard entry cap (MaxIPEntries/
  MaxASNEntries, default 200000/50000), evicting the least-recently-
  used bucket on overflow instead of growing unbounded between the
  5-minute idle cleanup passes (W13).
- TokenBucket gained a setRate method, and SetIPRate/SetASNRate now
  apply the new rate/capacity to every already-tracked bucket, not
  just buckets created after the call (W27).
Two hot-path costs, one per signal from every collector stream:

- AllowIP/AllowASN took the exclusive limiter lock even when the token
  bucket already existed (the overwhelmingly common case), serializing
  all signal processing. Use an RLock fast path with double-checked
  insert under the write lock.
- checkRateLimit called GetStats (another lock round-trip) plus two
  Prometheus gauge writes per signal just to publish activity gauges.
  Publish them from a 10s ticker next to the tracking-map cleanup
  instead; enforcement behavior is unchanged.

benchstat (10 runs, i7-12700K), parallel Allow hit path:
  361.8ns -> 107.1ns (-70.4%)

Also adds the package's first unit tests (burst exhaustion deny paths,
nil-input allows, concurrent access under -race).
awlx added a commit that referenced this pull request Aug 8, 2026
@awlx

awlx commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Superseded by and merged through #77, which preserves this contribution and includes the follow-up fixes. Thanks for the contribution!

@awlx awlx closed this Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants