servewaf always answers rproxy well within its time limit (closes #182)
check / check (push) Successful in 8m31s

A question waits on GeoJS at most 800ms, the new default of
SWWAF_LOOKUP_TIMEOUT. In enforce mode it does not wait at all when it is
over the header limits, on a fetched blocklist with deny, on the CrowdSec
decision list, or over a whole rate limit. Not waiting never lowers a
client's limit. Just before the Core Rule Set, servewaf refuses more than
50 cookies or 100 header fields with 431.

Shared default changed: SWWAF_LOOKUP_TIMEOUT also applies to smallwebwaf
with no command.
Judgement call: SWWAF_REPUTATION_TIMEOUT stays 2s, since no request waits
on the DNS blocklists or AbuseIPDB.

Model: opus-5-5
This commit was merged in pull request #183.
This commit is contained in:
2026-11-05 05:48:50 +01:00
parent 2eeea136e8
commit 4a4874ae2f
22 changed files with 937 additions and 55 deletions
+12 -4
View File
@@ -179,7 +179,7 @@ func New(params Params) *GeoJS {
// own address for IPv4, and an address in the same place for an IPv6
// netblock.
func (g *GeoJS) LookUp(ctx context.Context, client netip.Prefix) Answer {
answer, asked := g.answerOrWait(ctx, client)
answer, asked := g.answerOrWait(ctx, client, g.wait)
if asked == nil {
return answer
}
@@ -209,6 +209,14 @@ func (g *GeoJS) LookUp(ctx context.Context, client netip.Prefix) Answer {
return answer
}
// LookUpWithoutWaiting is LookUp for a request that does not need the answer,
// whatever Wait says: it returns the zero Answer at once when none is kept.
func (g *GeoJS) LookUpWithoutWaiting(ctx context.Context, client netip.Prefix) Answer {
answer, _ := g.answerOrWait(ctx, client, false)
return answer
}
// Kept returns client's answer, if one is kept, without asking GeoJS.
func (g *GeoJS) Kept(client netip.Prefix) (Answer, bool) {
g.mu.Lock()
@@ -263,9 +271,9 @@ func (g *GeoJS) Load(answers []Answer) {
// answerOrWait returns client's kept answer if it has one. Otherwise it
// puts the client among those waiting if there is room, has GeoJS asked
// about them if it can be, and returns what to wait on for the answer, or
// nil when there is nothing to wait for.
// nil when there is nothing to wait for or mustWait is false.
func (g *GeoJS) answerOrWait(
ctx context.Context, client netip.Prefix,
ctx context.Context, client netip.Prefix, mustWait bool,
) (Answer, <-chan struct{}) {
g.mu.Lock()
defer g.mu.Unlock()
@@ -283,7 +291,7 @@ func (g *GeoJS) answerOrWait(
g.ask(ctx)
if !g.wait {
if !mustWait {
return Answer{}, nil // the answer is not needed before the request goes on
}
+35 -2
View File
@@ -72,6 +72,39 @@ func TestKeptAnswerIsUsedFor7DaysThenAskedAgain(t *testing.T) {
})
}
func TestLookUpWithoutWaitingAsksGeoJSAndGoesOnAtOnce(t *testing.T) {
t.Parallel()
synctest.Test(t, func(t *testing.T) {
geojs, clock, g := start()
client := netip.MustParsePrefix("203.0.113.9/32")
// Wait is set, and GeoJS does not answer.
geojs.set(hanging)
began := time.Now()
answer := g.LookUpWithoutWaiting(t.Context(), client)
if waited := time.Since(began); waited != 0 || answer != (lookup.Answer{}) {
t.Errorf("got %+v after %s, want no answer at once", answer, waited)
}
waitForRequests(t, geojs, 1)
wantAsked(t, geojs, 0, "203.0.113.9")
// That request is abandoned, and has failed once this returns. GeoJS
// answers when it is asked again, and the answer is kept.
time.Sleep(timeout)
synctest.Wait()
geojs.set(answering)
waitForCountry(t, g, clock, client, germany)
if got := g.LookUpWithoutWaiting(t.Context(), client).Country; got != germany {
t.Errorf("the kept answer gives the country %q, want %q", got, germany)
}
})
}
func TestNewClientWaitsAtMostOneSecondThenCountsAsNotFound(t *testing.T) {
t.Parallel()
@@ -123,8 +156,8 @@ func TestRequestWaitsAsLongAsTheTimeoutSaysAndGeoJSIsAbandonedAfterIt(t *testing
t.Parallel()
synctest.Test(t, func(t *testing.T) {
// A timeout longer than the default second, and a GeoJS that does
// not answer.
// A timeout longer than the default, and a GeoJS that does not
// answer.
const longerTimeout = 3 * time.Second
m := metrics.New(1, "app")