package servewaf // The heuristic rate_limits. It reads SWWAF_RATE_LIMIT_EXEMPT_NETS, // SWWAF_RATE_LIMIT_EXEMPT_PATHS and SWWAF_BAN_RESPONSE, and the rate limits and // biased thresholds through the limiter and limitPercentages. import ( "context" "time" "sneak.berlin/go/smallwebwaf/internal/requestlog" ) // rateLimits counts the request for the rate limits, unless // countedByRateLimits exempts it, by the client's limit percentages, which it // notes for the log line with the client's counts. A request that takes the // client over a rate limit, as its limit percentage lowers it, breaks it: it is // refused, and bans the client. A request exempt from the rate limits is exempt // from the byte limits too. Its figures are the window, the count and the // limit. type rateLimits struct{} func (rateLimits) name() string { return "rate_limits" } func (rateLimits) check(_ context.Context, rq *request, now time.Time) *verdict { rq.counted = rq.countedByRateLimits() if !rq.counted { return nil } rq.limitPercent, rq.bytesPercent = rq.limitPercentages() rq.line.LimitPercent, rq.line.LimitPercentSetting = rq.limitPercent.logged() rq.line.BytesPercent, rq.line.BytesPercentSetting = rq.bytesPercent.logged() counts, hit, over := rq.h.limiter.Count(rq.h.clientGroup(rq.client), now, rq.limitPercent.percent) rq.line.Counts = counts if !over { return nil } rq.banForLimit(now, hit, rq.h.config.BanResponse) return &verdict{action: requestlog.ActionRateLimited, figures: limitFigures(hit)} } // overAWholeRateLimit reports whether the heuristic rate_limits will refuse the // request at now, whatever its client's limit percentages: the rate limits // count it, and one more request from its client takes it over a whole rate // limit. It counts nothing. func (rq *request) overAWholeRateLimit(now time.Time) bool { return rq.countedByRateLimits() && rq.h.limiter.OverARateLimit(rq.h.clientGroup(rq.client), now) } // countedByRateLimits reports whether the rate limits count the request: its // client is not in SWWAF_RATE_LIMIT_EXEMPT_NETS, and its path is not exempt // under SWWAF_RATE_LIMIT_EXEMPT_PATHS. func (rq *request) countedByRateLimits() bool { cfg := rq.h.config return !isInside(rq.client, cfg.RateLimitExemptNets) && !pathExempt(rq.in, cfg.RateLimitExemptPaths) }