Files
mc-proxy/internal/firewall/ratelimit_test.go
Kyle Isom b25e1b0e79 Add per-IP rate limiting and Unix socket support for gRPC admin API
Rate limiting: per-source-IP connection rate limiter in the firewall layer
with configurable limit and sliding window. Blocklisted IPs are rejected
before rate limit evaluation to avoid wasting quota. Unix socket: the gRPC
admin API can now listen on a Unix domain socket (no TLS required), secured
by file permissions (0600), as a simpler alternative for local-only access.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 14:37:21 -07:00

96 lines
1.8 KiB
Go

package firewall
import (
"net/netip"
"sync/atomic"
"testing"
"time"
)
func TestRateLimiterAllow(t *testing.T) {
rl := newRateLimiter(3, time.Minute)
defer rl.Stop()
addr := netip.MustParseAddr("10.0.0.1")
for i := 0; i < 3; i++ {
if !rl.Allow(addr) {
t.Fatalf("call %d: expected Allow=true", i+1)
}
}
if rl.Allow(addr) {
t.Fatal("call 4: expected Allow=false (over limit)")
}
}
func TestRateLimiterDifferentIPs(t *testing.T) {
rl := newRateLimiter(2, time.Minute)
defer rl.Stop()
a := netip.MustParseAddr("10.0.0.1")
b := netip.MustParseAddr("10.0.0.2")
// Exhaust a's limit.
for i := 0; i < 2; i++ {
rl.Allow(a)
}
if rl.Allow(a) {
t.Fatal("a should be rate limited")
}
// b should be independent.
if !rl.Allow(b) {
t.Fatal("b should not be rate limited")
}
}
func TestRateLimiterWindowReset(t *testing.T) {
rl := newRateLimiter(2, time.Minute)
defer rl.Stop()
var fakeNow atomic.Int64
fakeNow.Store(time.Now().UnixNano())
rl.now = func() time.Time {
return time.Unix(0, fakeNow.Load())
}
addr := netip.MustParseAddr("10.0.0.1")
// Exhaust the limit.
rl.Allow(addr)
rl.Allow(addr)
if rl.Allow(addr) {
t.Fatal("should be rate limited")
}
// Advance past the window.
fakeNow.Add(int64(2 * time.Minute))
// Should be allowed again.
if !rl.Allow(addr) {
t.Fatal("should be allowed after window reset")
}
}
func TestRateLimiterCleanup(t *testing.T) {
rl := newRateLimiter(10, 50*time.Millisecond)
defer rl.Stop()
addr := netip.MustParseAddr("10.0.0.1")
rl.Allow(addr)
// Entry should exist.
if _, ok := rl.entries.Load(addr); !ok {
t.Fatal("entry should exist")
}
// Wait for 2*window + a cleanup cycle to pass.
time.Sleep(200 * time.Millisecond)
// Entry should have been cleaned up.
if _, ok := rl.entries.Load(addr); ok {
t.Fatal("stale entry should have been cleaned up")
}
}