Compare commits

..
1 Commits
Author SHA1 Message Date
clawbot 669524cf8e Per-client request rate limits over a minute, an hour and a day (closes #43)
check / check (push) Successful in 2m20s
Each client, one IPv4 address or one IPv6 /64, is counted in two buckets
per window, the earlier weighted by how much of it the window covers; at
most 20,000 clients are kept, least recently seen dropped first. A
request over SWWAF_RATE_LIMIT_PER_MINUTE, _HOUR or _DAY (1000, 10000,
50000, or off) gets 429 before reaching the app. Refused requests count,
413s included. A clock set back over a second behind a bucket's start
restarts that window. The log line gains limit_hit and the action
rate_limited.

Deviation from SPEC.md, per the issue: the 20,000 bound and /64 are fixed.
Judgement call: golang-lru/v2 holds the table; httprate does not count refused requests.
Deviation: go.mod and go.sum hand-written; no make target tidies them.

Model: opus-5-5
2026-10-04 01:57:57 +00:00
2 changed files with 24 additions and 4 deletions
+5 -4
View File
@@ -109,12 +109,13 @@ bytes). Rate limits are whole numbers of requests. Netblocks are in CIDR form,
and a bare address stands for itself alone. `off` switches a timeout, a size and a bare address stands for itself alone. `off` switches a timeout, a size
limit or a rate limit off. limit or a rate limit off.
Two limits are fixed rather than settings: the request line and headers may take Four limits are fixed rather than settings. The request line and headers may
up to 32 KiB, above which the answer is `431` and nothing reaches the app, and a take up to 32 KiB, above which the answer is `431` and nothing reaches the app.
kept-open connection that sends nothing for 120 seconds is closed. That is A kept-open connection that sends nothing for 120 seconds is closed. That is
longer than the 90 seconds after which traefik closes a connection it is not longer than the 90 seconds after which traefik closes a connection it is not
using, so traefik never sends a request on a connection `smallwebwaf` is using, so traefik never sends a request on a connection `smallwebwaf` is
closing. closing. At most 20,000 clients are kept for the rate limits, and an IPv6 client
is counted by its /64.
## Request log ## Request log
+19
View File
@@ -54,6 +54,25 @@ func TestEachWindowRefusesAtItsLimitAndLetsTheClientBack(t *testing.T) {
} }
} }
func TestClientBackAfterAWholeBucketIsWithinTheLimitAtOnce(t *testing.T) {
t.Parallel()
limiter := ratelimit.New(ratelimit.Limits{PerHour: limit})
client := netip.MustParsePrefix("203.0.113.9/32")
start := midnight()
for range limit {
wantCount(t, limiter, client, start, "")
}
wantCount(t, limiter, client, start, hour)
// No request in the whole next bucket, so a quarter into the one after
// it the window covers none of the four requests: 1 is within the
// limit. Were they counted as the bucket before, 3 + 1 would be over.
wantCount(t, limiter, client, start.Add(2*time.Hour+time.Hour/4), "")
}
func TestRefusedRequestsCount(t *testing.T) { func TestRefusedRequestsCount(t *testing.T) {
t.Parallel() t.Parallel()