Caches of outside APIs live in /var/cache/sww (closes #314)
check / check (push) Waiting to run
check / check (push) Waiting to run
Every cache of answers from an outside service has its own file in `SWW_CACHE_DIR` (default `/var/cache/sww`): `lookups.json` (GeoJS), `lists.json` (lists fetched by URL, CrowdSec's decisions included), `dnsbl.json`, `abuseipdb-scores.json` and `routewatch.json` (at most 100,000 answers, each kept an hour). `reputation.json` is gone; the AbuseIPDB checks spent today stay in `SWW_STATE_DIR` as `abuseipdb-checks-spent.json`, since losing them would spend the daily budget twice. A cache file never stops the start: one that does not parse is set aside as `<name>.bad` with a `file_error` alert. An unwritable `SWW_CACHE_DIR` stops the start. The `run` script creates the cache directory and gives it to `sww`. The cache directory needs no volume and can be left out of backups. Model: opus-5-5
This commit was merged in pull request #336.
This commit is contained in:
1 parent
6a59011dee
commit
83525b5e73
30 files changed
+1677
-585
No files matched your search
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -336,3 +337,96 @@ func TestRepeatedRequestsMakeOneQueryAndTheAnswerExpires(t *testing.T) {
|
||||
t.Errorf("%d queries after the answer expired, want 2", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnapshotHoldsTheAnswerUntilItExpires(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f := newFixture(t, answering(routedAnswer))
|
||||
|
||||
f.route()
|
||||
f.waitFor("")
|
||||
|
||||
want := []routewatch.Answer{{
|
||||
Client: netip.MustParsePrefix(clientAddr + "/32"),
|
||||
Netblock: netip.MustParsePrefix("192.0.2.0/24"), ASN: 64496,
|
||||
Answered: f.clk.Now(),
|
||||
}}
|
||||
if got := f.client.Snapshot(); !slices.Equal(got, want) {
|
||||
t.Errorf("snapshot %+v, want %+v", got, want)
|
||||
}
|
||||
|
||||
f.clk.advance(time.Hour)
|
||||
|
||||
if got := f.client.Snapshot(); len(got) != 0 {
|
||||
t.Errorf("snapshot %+v an hour after the answer, want none", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadedAnswersAreUsedForAnHourFromWhenTheyCame(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f := newFixture(t, answering(routedAnswer))
|
||||
now := f.clk.Now()
|
||||
kept := routewatch.Answer{
|
||||
Client: netip.MustParsePrefix(clientAddr + "/32"),
|
||||
Netblock: netip.MustParsePrefix("198.51.100.0/24"), ASN: 64497,
|
||||
Answered: now.Add(-time.Hour + time.Second),
|
||||
}
|
||||
stale := routewatch.Answer{
|
||||
Client: netip.MustParsePrefix("192.0.2.2/32"),
|
||||
Netblock: netip.MustParsePrefix("192.0.2.0/24"), ASN: 64496,
|
||||
Answered: now.Add(-time.Hour),
|
||||
}
|
||||
|
||||
f.client.Load([]routewatch.Answer{kept, stale})
|
||||
|
||||
if got := f.client.Snapshot(); !slices.Equal(got, []routewatch.Answer{kept}) {
|
||||
t.Errorf("kept %+v, want only the answer that came less than an hour ago", got)
|
||||
}
|
||||
|
||||
route, missing := f.route()
|
||||
|
||||
want := routewatch.Route{Netblock: kept.Netblock, ASN: kept.ASN}
|
||||
if route != want || missing != "" {
|
||||
t.Errorf("route %+v (%q), want %+v", route, missing, want)
|
||||
}
|
||||
|
||||
if got := f.queries.Load(); got != 0 {
|
||||
t.Errorf("%d queries for a client with an answer loaded, want none", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDropsTheAnswerThatCameLongestAgoFirst(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const maxAnswers = 100000
|
||||
|
||||
f := newFixture(t, answering(routedAnswer))
|
||||
now := f.clk.Now()
|
||||
|
||||
// routewatch.json lists the answers by client. Here each came a
|
||||
// millisecond before the one listed before it, so the last listed is
|
||||
// the one that came longest ago, and the one dropped.
|
||||
answers := make([]routewatch.Answer, maxAnswers+1)
|
||||
addr := netip.MustParseAddr("10.0.0.0")
|
||||
|
||||
for i := range answers {
|
||||
answers[i] = routewatch.Answer{
|
||||
Client: netip.PrefixFrom(addr, addr.BitLen()),
|
||||
Answered: now.Add(-time.Duration(i) * time.Millisecond),
|
||||
}
|
||||
addr = addr.Next()
|
||||
}
|
||||
|
||||
f.client.Load(answers)
|
||||
|
||||
got := f.client.Snapshot()
|
||||
if len(got) != maxAnswers {
|
||||
t.Fatalf("%d answers kept, want %d", len(got), maxAnswers)
|
||||
}
|
||||
|
||||
if got[0] != answers[0] || got[maxAnswers-1] != answers[maxAnswers-1] {
|
||||
t.Errorf("answers kept from %s to %s, want from %s to %s", got[0].Client,
|
||||
got[maxAnswers-1].Client, answers[0].Client, answers[maxAnswers-1].Client)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user