package proxy import ( "sneak.berlin/go/smallwebwaf/internal/config" ) // whole is the percentage of each limit a client gets when no biased // threshold lowers its limits. const whole = 100 // percentage is a client's limit percentage for the rate limits or for // the byte limits, as the biased thresholds give it, and the setting that // gave it: "" with whole when none lowers that kind of limit. type percentage struct { percent int64 setting string } // biasedThresholdsSet reports whether a biased threshold can lower a // client's limits: one of its lists is not empty, // SWWAF_UNKNOWN_LIMIT_PERCENT is below 100, or SWWAF_ASN_LIMIT_PERCENT_URL // is set. The client's lookup is then needed before its request goes on. func biasedThresholdsSet(cfg *config.Config) bool { return len(cfg.ASNLimitPercent) > 0 || len(cfg.CountryLimitPercent) > 0 || len(cfg.ASNBytesPercent) > 0 || len(cfg.CountryBytesPercent) > 0 || cfg.UnknownLimitPercent < whole || cfg.ASNLimitPercentURL != "" } // limitPercentages returns the client's limit percentages, for the rate // limits and for the byte limits, by its AS number and country as looked // up, each "" when unknown, and the blocklists that list it. Each is the // lowest of those the settings give it, the first of them in the order // below when several are lowest: the percentage SWWAF_ASN_LIMIT_PERCENT // gives its AS number, the one the file SWWAF_ASN_LIMIT_PERCENT_URL names // gives it, the one SWWAF_COUNTRY_LIMIT_PERCENT gives its country, for a // client without a country, SWWAF_UNKNOWN_LIMIT_PERCENT, and for a client // a blocklist lists, the percentage of SWWAF_BLOCKLIST_ACTION while it is // limit. For the byte limits, SWWAF_ASN_BYTES_PERCENT and // SWWAF_COUNTRY_BYTES_PERCENT take the place of the first three for an AS // number or a country they list. func (rq *request) limitPercentages() (percentage, percentage) { cfg := rq.h.config asn, country := rq.line.ASN, rq.line.Country unknown := percentage{percent: whole} if country == "" { unknown = percentage{cfg.UnknownLimitPercent, "SWWAF_UNKNOWN_LIMIT_PERCENT"} } fetched := percentage{percent: whole} if percent, listed := rq.h.lists.ASNLimitPercent(asn); listed { fetched = percentage{percent, "SWWAF_ASN_LIMIT_PERCENT_URL"} } listed := percentage{percent: whole} if len(rq.line.Reputation) > 0 && cfg.BlocklistAction == "limit" { listed = percentage{cfg.BlocklistLimitPercent, "SWWAF_BLOCKLIST_ACTION"} } asnRequests := lowest(given(cfg.ASNLimitPercent, asn, "SWWAF_ASN_LIMIT_PERCENT"), fetched) countryRequests := given(cfg.CountryLimitPercent, country, "SWWAF_COUNTRY_LIMIT_PERCENT") asnBytes, countryBytes := asnRequests, countryRequests if _, listed := cfg.ASNBytesPercent[asn]; listed { asnBytes = given(cfg.ASNBytesPercent, asn, "SWWAF_ASN_BYTES_PERCENT") } if _, listed := cfg.CountryBytesPercent[country]; listed { countryBytes = given(cfg.CountryBytesPercent, country, "SWWAF_COUNTRY_BYTES_PERCENT") } return lowest(asnRequests, countryRequests, unknown, listed), lowest(asnBytes, countryBytes, unknown, listed) } // given returns the percentage percents, the setting named setting, gives // code, an AS number or a country, or whole when it does not list code. func given(percents map[string]int64, code, setting string) percentage { percent, listed := percents[code] if !listed { return percentage{percent: whole} } return percentage{percent, setting} } // lowest returns the lowest of percentages below whole, the first of them // when several are lowest, or whole when none is below it. func lowest(percentages ...percentage) percentage { low := percentage{percent: whole} for _, p := range percentages { if p.percent < low.percent { low = p } } return low } // logged returns p as the log line and the notes of a ban give it: its // percent and setting, or nil and "" for whole, which they leave out. func (p percentage) logged() (*int64, string) { if p.percent == whole { return nil, "" } return &p.percent, p.setting }