feat: per-connection serverbound packet rate limiter#691
Merged
Conversation
Gate's existing quotas only limit new connections and logins per IP; an already-connected client could flood packets unchecked. Add a per-connection serverbound packet/byte rate limiter, closing the connection when a limit is exceeded. - pkg/internal/packetlimiter: a sliding-window counter (ring buffer) and a Limiter with Account(bytes) bool. A nil/zero-limit Limiter is disabled. - config: new packetLimiter section (interval, packetsPerSecond, bytesPerSecond) with defaults of a 7s window and 500 packets/s (bytes disabled). A limit <= 0 disables that dimension; both <= 0 (or interval <= 0) disables the limiter. - netmc: client connections (serverbound reads) account each packet's bytes in the read loop and are closed when over the limit. Backend connections pass a nil limiter (trusted, no limit). This adopts the rate-limiting approach Velocity added for the same purpose, fit to Gate's Go read-loop rather than its Netty pipeline. The sliding-window counter, the limiter, and the read-loop close behaviour each have tests (including a negative control that an unlimited connection is not closed).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Gap
Gate's existing rate limiting (
config.quota) only applies at connect time and login time per IP. Once a client is connected, it can send packets unthrottled — there's no per-connection packet/bytes-per-second limit during play. This PR closes that gap.(The acute payload/decompression/pre-join-queue DoS vectors are addressed separately in #689; this adds the missing per-connection flood limit. There's even a long-standing TODO in
config.Quotaanticipating a bytes-per-sec limiter.)What it does
A per-connection limiter accounts every serverbound packet (client→proxy) and closes the connection when a configured rate is exceeded. Backend connections are trusted and pass no limiter.
pkg/internal/packetlimiter/counter.goLimiterwithAccount(bytes) bool(nil = disabled)pkg/internal/packetlimiter/limiter.gopacketLimitersection + defaults + validationpkg/edition/java/config/config.go,config.ymlpkg/edition/java/netmc/connection.gopkg/edition/java/proxy/{proxy,server}.goConfig (defaults)
A limit
<= 0disables that dimension; both<= 0(orinterval <= 0) disables the limiter entirely. Defaults match Velocity's vetted values (7s / 500 pps). 500 pps sustained is far above normal play (movement is ~20/s), so legitimate clients are unaffected; the value is tunable.Tests (TDD)
counter_test.go— window accumulation, expiry, per-second rate, ring-buffer resize.limiter_test.go— allows under limit, rejects over packet rate, rejects over byte rate, nil/disabled is a no-op (race-clean).connection_limiter_test.go— drives real handshake frames through anet.Piperead loop: a flood closes the connection, and a negative control confirms an unlimited connection is not closed (so the flood test can't pass for an unrelated reason).Design note
This adopts the rate-limiting approach Velocity uses, fit to Gate's Go read loop (accounting
packetCtx.BytesReadper packet) rather than a Netty pipeline handler. The sliding-window counter mirrors the Velocity/PaperIntervalledCounter.Verification
go build ./...— cleango vet(touched packages) — cleango test ./...— all packages pass (0 failures); limiter + netmc tests pass under-racegofmt— cleanIndependent of #689 and #690 (all branch from
master).