check / check (push) Successful in 3m24s
smallwebwaf now copies its state to bans.json, clients.json and lookups.json in SWWAF_STATE_DIR, as "Persistent state" in SPEC.md describes, and reads them back at start, so a restart lifts no ban and gives no client a fresh allowance. Each client gains a history, and a ban's notes count the netblock's requests. bans.json is written SWWAF_STATE_WRITE_DELAY after a ban, and every file every SWWAF_STATE_COUNTER_INTERVAL and at the stop. A ban read back is masked to its netblock and refuses every client in it. A file that does not parse, an unknown version, an entry without a field it needs, or an unwritable directory stops the start. Deviation: no AS number or name, and no ban cause, reason or lifting yet. Model: opus-5-5
97 lines
2.5 KiB
Go
97 lines
2.5 KiB
Go
package lookup_test
|
|
|
|
import (
|
|
"net/netip"
|
|
"slices"
|
|
"testing"
|
|
"time"
|
|
|
|
"sneak.berlin/go/smallwebwaf/internal/lookup"
|
|
)
|
|
|
|
func TestSnapshotHoldsEachAnswerAndWhenItWasLastUsed(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, clock, g := start(t)
|
|
placed := netip.MustParsePrefix("203.0.113.9/32")
|
|
notPlaced := netip.MustParsePrefix(unplaced + "/32")
|
|
asked := clock.Now()
|
|
|
|
wantCountry(t, g, placed, germany)
|
|
wantCountry(t, g, notPlaced, "")
|
|
|
|
clock.advance(time.Hour)
|
|
wantCountry(t, g, placed, germany)
|
|
|
|
want := []lookup.Answer{
|
|
{Client: notPlaced, Country: "", Answered: asked, Used: asked},
|
|
{Client: placed, Country: germany, Answered: asked, Used: asked.Add(time.Hour)},
|
|
}
|
|
if got := g.Snapshot(); !slices.Equal(got, want) {
|
|
t.Errorf("snapshot\n%+v\nwant\n%+v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestLoadedAnswersAreKeptFor7DaysFromWhenGeoJSGaveThem(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
geojs, clock, g := start(t)
|
|
now := clock.Now()
|
|
kept := lookup.Answer{
|
|
Client: netip.MustParsePrefix("203.0.113.9/32"),
|
|
Country: "FR",
|
|
Answered: now.Add(-week + time.Second),
|
|
Used: now.Add(-time.Hour),
|
|
}
|
|
stale := lookup.Answer{
|
|
Client: netip.MustParsePrefix("203.0.113.10/32"),
|
|
Country: "FR",
|
|
Answered: now.Add(-week),
|
|
Used: now.Add(-time.Hour),
|
|
}
|
|
|
|
g.Load([]lookup.Answer{kept, stale})
|
|
|
|
if got := g.Snapshot(); !slices.Equal(got, []lookup.Answer{kept}) {
|
|
t.Errorf("kept %+v, want only the answer GeoJS gave less than 7 days ago", got)
|
|
}
|
|
|
|
wantCountry(t, g, kept.Client, "FR")
|
|
wantRequests(t, geojs, 0)
|
|
}
|
|
|
|
func TestLoadDropsTheAnswerUsedLongestAgoFirst(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const maxAnswers = 100000
|
|
|
|
_, clock, g := start(t)
|
|
now := clock.Now()
|
|
|
|
// lookups.json lists the answers by client. Here each was last used a
|
|
// second before the one listed before it, so the last listed is the
|
|
// one used longest ago, and the one dropped.
|
|
answers := make([]lookup.Answer, maxAnswers+1)
|
|
addr := netip.MustParseAddr("10.0.0.0")
|
|
|
|
for i := range answers {
|
|
answers[i] = lookup.Answer{
|
|
Client: netip.PrefixFrom(addr, addr.BitLen()),
|
|
Country: germany,
|
|
Answered: now,
|
|
Used: now.Add(-time.Duration(i) * time.Second),
|
|
}
|
|
addr = addr.Next()
|
|
}
|
|
|
|
g.Load(answers)
|
|
|
|
got := g.Snapshot()
|
|
if len(got) != maxAnswers || got[0] != answers[0] ||
|
|
got[maxAnswers-1] != answers[maxAnswers-1] {
|
|
t.Errorf("%d answers kept, from %s to %s; want %d, from %s to %s",
|
|
len(got), got[0].Client, got[len(got)-1].Client, maxAnswers,
|
|
answers[0].Client, answers[maxAnswers-1].Client)
|
|
}
|
|
}
|