AS number and country looked up for every client (closes #95)
check / check (push) Waiting to run
check / check (push) Waiting to run
GeoJS's geo.json is asked about every new visitor unless SWWAF_LOOKUP_SOURCE is off. A request waits for its client's first answer only while a country list or SWWAF_ADD_LOOKUP_HEADERS needs it; otherwise the answer reaches the client's history and ban notes when it comes. The AS number and name go beside the country in the request log, history, ban notes, alerts and lookups.json, with metrics by AS number; 64512 counts as unknown. A client's own X-Client-ASN and X-Client-Country never reach the app, whatever the setting says, and make example-app sends no address to GeoJS. Judgement call: AS numbers are written AS64496, as SPEC's settings write them. Judgement call: SWWAF_LOOKUP_TIMEOUT is added, default 1s, and cannot be off. Model: opus-5-5
This commit is contained in:
+27
-1
@@ -91,7 +91,11 @@ func (b Ban) ActiveAt(now time.Time) bool {
|
||||
//
|
||||
//nolint:tagliatelle // the state files use snake_case, as the request log does
|
||||
type Notes struct {
|
||||
// Country is the client's country, when it was looked up.
|
||||
// ASN, ASName and Country are the client's AS number, AS name and
|
||||
// country, when they were looked up: when the request that caused the
|
||||
// ban was made, or when GeoJS answered about the client afterwards.
|
||||
ASN string `json:"asn"`
|
||||
ASName string `json:"as_name"`
|
||||
Country string `json:"country"`
|
||||
// Limit, Window and Count are, for a ban for a broken limit, the limit
|
||||
// that was broken, its window, "minute", "hour" or "day", and the
|
||||
@@ -411,6 +415,28 @@ func (l *Ledger) Bans(netblock netip.Prefix) []Ban {
|
||||
return slices.Clone(*bans)
|
||||
}
|
||||
|
||||
// AddLookup gives the notes of netblock's bans that have no AS number, AS
|
||||
// name or country yet those of a client in it, as GeoJS answered about
|
||||
// it. It is not a request from netblock, and leaves when it was last seen
|
||||
// unchanged. It does not have bans.json written at once: the notes are
|
||||
// written with its next write, as the counts in them are.
|
||||
func (l *Ledger) AddLookup(netblock netip.Prefix, asn, asName, country string) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
bans, found := l.netblocks.Peek(netblock)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
for i := range *bans {
|
||||
notes := &(*bans)[i].Notes
|
||||
if notes.ASN == "" && notes.ASName == "" && notes.Country == "" {
|
||||
notes.ASN, notes.ASName, notes.Country = asn, asName, country
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Made returns how many bans for cause have been made since the start:
|
||||
// for CauseLimit and CauseAttack, by the ledger; for CauseAdmin, by an
|
||||
// admin, with BanForAdmin or in an edit of bans.json, as LoadEdit counts
|
||||
|
||||
@@ -454,6 +454,45 @@ func TestRequestTextsAreCutTo256Bytes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLookupFillsTheNotesOfTheNetblocksBansWithoutOne(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ledger := bans.New(defaultRules())
|
||||
netblock := netip.MustParsePrefix("203.0.113.9/32")
|
||||
other := netip.MustParsePrefix("198.51.100.7/32")
|
||||
|
||||
// A ban made with the client's lookup, one made before it came, after
|
||||
// the first ended, and one on another netblock.
|
||||
ledger.BanForLimit(netblock, midnight(), bans.Notes{
|
||||
ASN: "AS64497", ASName: "Other Net", Country: "FR",
|
||||
})
|
||||
ledger.BanForLimit(netblock, midnight().Add(time.Hour), bans.Notes{})
|
||||
ledger.BanForLimit(other, midnight(), bans.Notes{})
|
||||
|
||||
ledger.AddLookup(netblock, "AS64496", "Example Net", "DE")
|
||||
|
||||
held := ledger.Bans(netblock)
|
||||
if len(held) != 2 {
|
||||
t.Fatalf("%s has %d bans, want 2", netblock, len(held))
|
||||
}
|
||||
|
||||
for i, want := range []bans.Notes{
|
||||
{ASN: "AS64497", ASName: "Other Net", Country: "FR"},
|
||||
{ASN: "AS64496", ASName: "Example Net", Country: "DE"},
|
||||
} {
|
||||
got := held[i].Notes
|
||||
if got.ASN != want.ASN || got.ASName != want.ASName || got.Country != want.Country {
|
||||
t.Errorf("ban %d's notes give %q, %q and %q, want %q, %q and %q", i+1,
|
||||
got.ASN, got.ASName, got.Country, want.ASN, want.ASName, want.Country)
|
||||
}
|
||||
}
|
||||
|
||||
if notes := ledger.Bans(other)[0].Notes; notes.ASN != "" || notes.Country != "" {
|
||||
t.Errorf("the ban on %s has %q and %q, want neither",
|
||||
other, notes.ASN, notes.Country)
|
||||
}
|
||||
}
|
||||
|
||||
// defaultRules are the rules at the settings' defaults.
|
||||
func defaultRules() bans.Rules {
|
||||
return bans.Rules{
|
||||
|
||||
Reference in New Issue
Block a user