Commit dba27ec1 authored by Pablo Neira Ayuso's avatar Pablo Neira Ayuso

netfilter: nft_limit: convert to token-based limiting at nanosecond granularity

Rework the limit expression to use a token-based limiting approach that refills
the bucket gradually. The tokens are calculated at nanosecond granularity
instead jiffies to improve precision.
Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
parent 09e4e42a
...@@ -20,10 +20,11 @@ ...@@ -20,10 +20,11 @@
static DEFINE_SPINLOCK(limit_lock); static DEFINE_SPINLOCK(limit_lock);
struct nft_limit { struct nft_limit {
u64 last;
u64 tokens; u64 tokens;
u64 tokens_max;
u64 rate; u64 rate;
u64 unit; u64 nsecs;
unsigned long stamp;
}; };
static void nft_limit_pkts_eval(const struct nft_expr *expr, static void nft_limit_pkts_eval(const struct nft_expr *expr,
...@@ -31,18 +32,23 @@ static void nft_limit_pkts_eval(const struct nft_expr *expr, ...@@ -31,18 +32,23 @@ static void nft_limit_pkts_eval(const struct nft_expr *expr,
const struct nft_pktinfo *pkt) const struct nft_pktinfo *pkt)
{ {
struct nft_limit *priv = nft_expr_priv(expr); struct nft_limit *priv = nft_expr_priv(expr);
u64 now, tokens, cost = div_u64(priv->nsecs, priv->rate);
s64 delta;
spin_lock_bh(&limit_lock); spin_lock_bh(&limit_lock);
if (time_after_eq(jiffies, priv->stamp)) { now = ktime_get_ns();
priv->tokens = priv->rate; tokens = priv->tokens + now - priv->last;
priv->stamp = jiffies + priv->unit * HZ; if (tokens > priv->tokens_max)
} tokens = priv->tokens_max;
if (priv->tokens >= 1) { priv->last = now;
priv->tokens--; delta = tokens - cost;
if (delta >= 0) {
priv->tokens = delta;
spin_unlock_bh(&limit_lock); spin_unlock_bh(&limit_lock);
return; return;
} }
priv->tokens = tokens;
spin_unlock_bh(&limit_lock); spin_unlock_bh(&limit_lock);
regs->verdict.code = NFT_BREAK; regs->verdict.code = NFT_BREAK;
...@@ -58,25 +64,29 @@ static int nft_limit_init(const struct nft_ctx *ctx, ...@@ -58,25 +64,29 @@ static int nft_limit_init(const struct nft_ctx *ctx,
const struct nlattr * const tb[]) const struct nlattr * const tb[])
{ {
struct nft_limit *priv = nft_expr_priv(expr); struct nft_limit *priv = nft_expr_priv(expr);
u64 unit;
if (tb[NFTA_LIMIT_RATE] == NULL || if (tb[NFTA_LIMIT_RATE] == NULL ||
tb[NFTA_LIMIT_UNIT] == NULL) tb[NFTA_LIMIT_UNIT] == NULL)
return -EINVAL; return -EINVAL;
priv->rate = be64_to_cpu(nla_get_be64(tb[NFTA_LIMIT_RATE])); priv->rate = be64_to_cpu(nla_get_be64(tb[NFTA_LIMIT_RATE]));
priv->unit = be64_to_cpu(nla_get_be64(tb[NFTA_LIMIT_UNIT])); unit = be64_to_cpu(nla_get_be64(tb[NFTA_LIMIT_UNIT]));
priv->stamp = jiffies + priv->unit * HZ; priv->nsecs = unit * NSEC_PER_SEC;
priv->tokens = priv->rate; if (priv->rate == 0 || priv->nsecs < unit)
return -EOVERFLOW;
priv->tokens = priv->tokens_max = priv->nsecs;
priv->last = ktime_get_ns();
return 0; return 0;
} }
static int nft_limit_dump(struct sk_buff *skb, const struct nft_expr *expr) static int nft_limit_dump(struct sk_buff *skb, const struct nft_expr *expr)
{ {
const struct nft_limit *priv = nft_expr_priv(expr); const struct nft_limit *priv = nft_expr_priv(expr);
u64 secs = div_u64(priv->nsecs, NSEC_PER_SEC);
if (nla_put_be64(skb, NFTA_LIMIT_RATE, cpu_to_be64(priv->rate))) if (nla_put_be64(skb, NFTA_LIMIT_RATE, cpu_to_be64(priv->rate)) ||
goto nla_put_failure; nla_put_be64(skb, NFTA_LIMIT_UNIT, cpu_to_be64(secs)))
if (nla_put_be64(skb, NFTA_LIMIT_UNIT, cpu_to_be64(priv->unit)))
goto nla_put_failure; goto nla_put_failure;
return 0; return 0;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment