Lower limits for listed AS numbers and countries (closes #21)
check / check (push) Waiting to run
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
This commit was merged in pull request #103.
This commit is contained in:
@@ -174,7 +174,7 @@ type Hit struct {
|
||||
Kind string
|
||||
// Window is "minute", "hour" or "day".
|
||||
Window string
|
||||
// Limit is the window's limit.
|
||||
// Limit is the window's limit, as the client's percentage of it.
|
||||
Limit int64
|
||||
// Count is the client's requests, or bytes, counted in the window,
|
||||
// this request's included.
|
||||
@@ -196,21 +196,24 @@ type Counts struct {
|
||||
|
||||
// Count counts a request from client at now, in every window, whether or
|
||||
// not it is refused, and returns the client's counts in each window. It
|
||||
// reports whether the request takes the client over a rate limit, and the
|
||||
// hit: the window whose limit it goes over, the shortest if it is over
|
||||
// several.
|
||||
func (l *Limiter) Count(client netip.Prefix, now time.Time) (Counts, Hit, bool) {
|
||||
return l.count(client, now, 1, 0)
|
||||
// reports whether the request takes the client over a rate limit, of
|
||||
// which the client gets the percentage percent, rounded down, and the hit:
|
||||
// the window whose limit it goes over, the shortest if it is over
|
||||
// several. A limit that is off stays off.
|
||||
func (l *Limiter) Count(
|
||||
client netip.Prefix, now time.Time, percent int64,
|
||||
) (Counts, Hit, bool) {
|
||||
return l.count(client, now, 1, 0, percent)
|
||||
}
|
||||
|
||||
// CountBytes counts bytes, those of a request from client that has ended,
|
||||
// at now, in every window, and returns the client's counts in each window.
|
||||
// It reports whether the bytes take the client over a byte limit, and the
|
||||
// hit, as Count does.
|
||||
// It reports whether the bytes take the client over a byte limit, of which
|
||||
// the client gets the percentage percent, and the hit, as Count does.
|
||||
func (l *Limiter) CountBytes(
|
||||
client netip.Prefix, now time.Time, bytes int64,
|
||||
client netip.Prefix, now time.Time, bytes, percent int64,
|
||||
) (Counts, Hit, bool) {
|
||||
return l.count(client, now, 0, bytes)
|
||||
return l.count(client, now, 0, bytes, percent)
|
||||
}
|
||||
|
||||
// Reset sets client's counts of requests and of bytes in every window
|
||||
@@ -373,9 +376,10 @@ func (l *Limiter) Load(clients []Client, now time.Time) {
|
||||
// count adds requests and bytes from client at now to its buckets in
|
||||
// every window, and returns its counts. A limit is broken only by what is
|
||||
// added to it, so that a request whose bytes are counted after another of
|
||||
// the client's requests broke a rate limit does not break it too.
|
||||
// the client's requests broke a rate limit does not break it too. The
|
||||
// client gets the percentage percent of each limit.
|
||||
func (l *Limiter) count(
|
||||
client netip.Prefix, now time.Time, requests, bytes int64,
|
||||
client netip.Prefix, now time.Time, requests, bytes, percent int64,
|
||||
) (Counts, Hit, bool) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
@@ -391,16 +395,17 @@ func (l *Limiter) count(
|
||||
for i, w := range l.windows {
|
||||
requestCounts[i] = requestBuckets[i].add(now, w.length, requests)
|
||||
byteCounts[i] = byteBuckets[i].add(now, w.length, bytes)
|
||||
limit, byteLimit := percentOf(w.limit, percent), percentOf(w.byteLimit, percent)
|
||||
|
||||
switch {
|
||||
case hit.Window != "":
|
||||
case requests > 0 && w.limit > 0 && requestCounts[i] > float64(w.limit):
|
||||
case requests > 0 && w.limit > 0 && requestCounts[i] > float64(limit):
|
||||
hit = Hit{
|
||||
Kind: KindRequests, Window: w.name, Limit: w.limit, Count: requestCounts[i],
|
||||
Kind: KindRequests, Window: w.name, Limit: limit, Count: requestCounts[i],
|
||||
}
|
||||
case bytes > 0 && w.byteLimit > 0 && byteCounts[i] > float64(w.byteLimit):
|
||||
case bytes > 0 && w.byteLimit > 0 && byteCounts[i] > float64(byteLimit):
|
||||
hit = Hit{
|
||||
Kind: KindBytes, Window: w.name, Limit: w.byteLimit, Count: byteCounts[i],
|
||||
Kind: KindBytes, Window: w.name, Limit: byteLimit, Count: byteCounts[i],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,6 +451,15 @@ type window struct {
|
||||
byteLimit int64
|
||||
}
|
||||
|
||||
// percentOf returns the percentage percent of limit, rounded down. It is
|
||||
// written as limit's hundreds times percent, plus the rest's share, since
|
||||
// limit*percent can overflow for a byte limit.
|
||||
func percentOf(limit, percent int64) int64 {
|
||||
const hundred = 100
|
||||
|
||||
return limit/hundred*percent + limit%hundred*percent/hundred
|
||||
}
|
||||
|
||||
// add counts n requests, or n bytes, at now in a window of length, and
|
||||
// returns the client's count in the window that ends at now: what is in
|
||||
// the bucket under way, and what is in the bucket before it weighted by
|
||||
|
||||
Reference in New Issue
Block a user