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

A question waits on GeoJS at most 800ms, the new default of
SWWAF_LOOKUP_TIMEOUT. It does not wait at all when the checks will refuse
it whatever GeoJS answers. 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 is contained in:
2026-11-05 03:15:00 +00:00
parent 2eeea136e8
commit a959df50ad
19 changed files with 781 additions and 45 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")