check / check (push) Waiting to run
SWWAF_ASN_LIMIT_PERCENT and SWWAF_COUNTRY_LIMIT_PERCENT give the clients of the AS numbers and countries they list that percentage of every rate and byte limit, rounded down; SWWAF_ASN_BYTES_PERCENT and SWWAF_COUNTRY_BYTES_PERCENT take its place for the byte limits of those they list; SWWAF_UNKNOWN_LIMIT_PERCENT (100) covers clients without a country. The lowest applies. While one lowers a limit, a request waits for its client's lookup, and SWWAF_LOOKUP_SOURCE=off stops the start. Log lines give limit_percent and bytes_percent with their settings; ban notes, and so alerts, give the broken limit's. Judgement call: a client without a country is unknown, whatever its AS number. Judgement call: bytes_percent and its setting are log fields SPEC does not name. Rule suppressed: funlen on FromEnvironment, one line per setting. Model: opus-5-5
72 lines
2.4 KiB
Go
72 lines
2.4 KiB
Go
package proxy
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/netip"
|
|
|
|
"sneak.berlin/go/smallwebwaf/internal/lookup"
|
|
)
|
|
|
|
// The headers in which the app is passed the client's AS number and
|
|
// country while SWWAF_ADD_LOOKUP_HEADERS is set. Go writes every header
|
|
// name in this form, as it sends it and as it receives it, so X-Client-ASN
|
|
// arrives as X-Client-Asn, and Del removes a client's own whatever their
|
|
// case; header names are not case-sensitive.
|
|
const (
|
|
asnHeader = "X-Client-Asn"
|
|
countryHeader = "X-Client-Country"
|
|
)
|
|
|
|
// lookUp looks up the client's AS number and country, in the lookup
|
|
// database or through GeoJS, and notes them for the log line, unless
|
|
// SWWAF_LOOKUP_SOURCE is off or the client is on a private, loopback or
|
|
// link-local address, which no lookup can place. The lookup database
|
|
// answers at once. With GeoJS, while a setting needs the answer, such as a
|
|
// country list or a biased threshold, a new client's request waits for it.
|
|
// ctx is the request's own context.
|
|
func (rq *request) lookUp(ctx context.Context) {
|
|
if rq.h.config.LookupSource == "off" || !canBePlaced(rq.client) {
|
|
return
|
|
}
|
|
|
|
if rq.h.config.LookupSource == "file" {
|
|
rq.lookupAnswer = rq.h.lookupFile.LookUp(clientGroup(rq.client))
|
|
} else {
|
|
rq.lookupAnswer = rq.h.geojs.LookUp(ctx, clientGroup(rq.client))
|
|
}
|
|
|
|
rq.lookedUp = true
|
|
rq.line.ASN = rq.lookupAnswer.ASN
|
|
rq.line.ASName = rq.lookupAnswer.ASName
|
|
rq.line.Country = rq.lookupAnswer.Country
|
|
}
|
|
|
|
// addLookup adds answer, an answer about a client from the lookup
|
|
// database or GeoJS, to the client's history, and to the notes of the bans
|
|
// on its netblock that have no AS number, AS name or country yet.
|
|
func (h *handler) addLookup(answer lookup.Answer) {
|
|
h.limiter.AddLookup(answer.Client, answer.Answered,
|
|
answer.ASN, answer.ASName, answer.Country)
|
|
h.ledger.AddLookup(h.netblock(answer.Client.Addr()),
|
|
answer.ASN, answer.ASName, answer.Country)
|
|
}
|
|
|
|
// setLookupHeaders sets the headers in which the app is passed the
|
|
// client's AS number and country, leaving out one that is unknown.
|
|
func setLookupHeaders(header http.Header, asn, country string) {
|
|
if asn != "" {
|
|
header.Set(asnHeader, asn)
|
|
}
|
|
|
|
if country != "" {
|
|
header.Set(countryHeader, country)
|
|
}
|
|
}
|
|
|
|
// canBePlaced reports whether a lookup can place addr: private, loopback
|
|
// and link-local addresses have no AS number or country.
|
|
func canBePlaced(addr netip.Addr) bool {
|
|
return !addr.IsPrivate() && !addr.IsLoopback() && !addr.IsLinkLocalUnicast()
|
|
}
|