ratelimit

package
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: AGPL-3.0 Imports: 11 Imported by: 0

Documentation

Overview

Package ratelimit is Ken's application-layer abuse defense: a per-IP token-bucket "first filter" (allowlist bypass, auto-block for repeat offenders, 429 + Retry-After otherwise) plus a reusable per-key bucket the MCP layer uses for per-token limits. This is app-layer abuse control — NOT volumetric L3/4 DDoS mitigation, which belongs upstream at the proxy / kernel / edge.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Bucket

type Bucket struct {
	// contains filtered or unexported fields
}

Bucket is a token-bucket rate limiter keyed by an arbitrary string (an IP key or a token id). It refills at rate tokens/sec up to burst. Safe for concurrent use; a nil *Bucket allows everything (so callers can hold an optional limiter).

func NewBucket

func NewBucket(perMinute, burst int) *Bucket

NewBucket builds a limiter allowing ~perMinute requests/minute with the given burst capacity. A non-positive perMinute or burst disables it (Allow always ok).

func (*Bucket) Allow

func (b *Bucket) Allow(key string) (bool, time.Duration)

Allow consumes one token for key: (true, 0) when allowed, or (false, retryAfter) with the time until the next token is available (always > 0 when denied).

type Config

type Config struct {
	Enabled     bool
	IPPerMin    int
	IPBurst     int
	TokenPerMin int
	TokenBurst  int
	BlockAfter  int           // consecutive over-limit rejections (since the last allowed request) before an IP is auto-blocked
	Lockout     time.Duration // how long an auto-block lasts
	Allow       []*net.IPNet  // extra allowlisted CIDRs (loopback is always allowed)

	// OnReject/OnBlock are optional observability hooks fired when the guard
	// throttles (429) or refuses an auto-blocked IP (403). Kept as callbacks so
	// this package stays decoupled from the metrics package.
	OnReject func()
	OnBlock  func()
}

Config is the resolved rate-limit configuration.

func FromEnv

func FromEnv() Config

FromEnv resolves the config from KEN_RATELIMIT* (on by default, generous limits). Non-positive limits/bursts are clamped to the defaults so an "enabled" limiter always limits; the lockout is bounded to guard against an overflowing integer.

func (Config) Describe

func (c Config) Describe() string

Describe returns a one-line summary for the startup log.

func (Config) TokenBucket

func (c Config) TokenBucket() *Bucket

TokenBucket builds the per-token limiter from c (nil when disabled).

type IPGuard

type IPGuard struct {
	// contains filtered or unexported fields
}

IPGuard is the outermost middleware: it resolves the client IP, bypasses the allowlist (loopback + configured CIDRs) and the /healthz probe, rejects an auto-blocked IP with 403, throttles the rest with a per-IP token bucket (429 + Retry-After) and auto-blocks repeat offenders. IP keys are normalized by network (IPv6 -> /64) so address rotation cannot evade the limit.

func NewIPGuard

func NewIPGuard(c Config, resolver *clientip.Resolver) *IPGuard

NewIPGuard builds the per-IP guard, or nil when rate limiting is disabled.

func (*IPGuard) Wrap

func (g *IPGuard) Wrap(next http.Handler) http.Handler

Wrap installs the guard in front of next. A nil *IPGuard returns next unchanged.

type Limiter

type Limiter interface {
	Allow(key string) (bool, time.Duration)
}

Limiter is the per-token limit the MCP layer applies (satisfied by *Bucket and *ReloadableBucket).

type ReloadableBucket

type ReloadableBucket struct {
	// contains filtered or unexported fields
}

ReloadableBucket wraps a token Bucket that can be swapped live.

func (*ReloadableBucket) Allow

func (rb *ReloadableBucket) Allow(key string) (bool, time.Duration)

Allow delegates to the current bucket (a nil bucket is nil-safe -> allowed).

func (*ReloadableBucket) Store

func (rb *ReloadableBucket) Store(b *Bucket)

Store swaps in a new bucket (may be nil to disable — Allow then passes through).

type ReloadableGuard

type ReloadableGuard struct {
	// contains filtered or unexported fields
}

ReloadableGuard is a per-IP guard that can be swapped live (when settings change) behind a stable http.Handler. A nil current guard passes through.

func (*ReloadableGuard) Store

func (rg *ReloadableGuard) Store(g *IPGuard)

Store swaps in a new guard (may be nil to disable).

func (*ReloadableGuard) Wrap

func (rg *ReloadableGuard) Wrap(next http.Handler) http.Handler

Wrap returns a stable handler that dispatches to the current guard each request.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL