Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netfilter: nft_connlimit: update the count if add was skipped

Connlimit expression can be used for all kind of packets and not only
for packets with connection state new. See this ruleset as example:

table ip filter {
chain input {
type filter hook input priority filter; policy accept;
tcp dport 22 ct count over 4 counter
}
}

Currently, if the connection count goes over the limit the counter will
count the packets. When a connection is closed, the connection count
won't decrement as it should because it is only updated for new
connections due to an optimization on __nf_conncount_add() that prevents
updating the list if the connection is duplicated.

To solve this problem, check whether the connection was skipped and if
so, update the list. Adjust count_tree() too so the same fix is applied
for xt_connlimit.

Fixes: 976afca1ceba ("netfilter: nf_conncount: Early exit in nf_conncount_lookup() and cleanup")
Closes: https://lore.kernel.org/netfilter/trinity-85c72a88-d762-46c3-be97-36f10e5d9796-1761173693813@3c-app-mailcom-bs12/
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

authored by

Fernando Fernandez Mancera and committed by
Pablo Neira Ayuso
69894e5b c0362b57

+19 -6
+8 -4
net/netfilter/nf_conncount.c
··· 179 179 if (ct && nf_ct_is_confirmed(ct)) { 180 180 if (refcounted) 181 181 nf_ct_put(ct); 182 - return 0; 182 + return -EEXIST; 183 183 } 184 184 185 185 if ((u32)jiffies == list->last_gc) ··· 408 408 int ret; 409 409 410 410 ret = nf_conncount_add_skb(net, skb, l3num, &rbconn->list); 411 - if (ret) 411 + if (ret && ret != -EEXIST) 412 412 count = 0; /* hotdrop */ 413 413 else 414 414 count = rbconn->list.count; ··· 511 511 /* same source network -> be counted! */ 512 512 ret = __nf_conncount_add(net, skb, l3num, &rbconn->list); 513 513 spin_unlock_bh(&rbconn->list.list_lock); 514 - if (ret) 514 + if (ret && ret != -EEXIST) { 515 515 return 0; /* hotdrop */ 516 - else 516 + } else { 517 + /* -EEXIST means add was skipped, update the list */ 518 + if (ret == -EEXIST) 519 + nf_conncount_gc_list(net, &rbconn->list); 517 520 return rbconn->list.count; 521 + } 518 522 } 519 523 } 520 524
+11 -2
net/netfilter/nft_connlimit.c
··· 29 29 30 30 err = nf_conncount_add_skb(nft_net(pkt), pkt->skb, nft_pf(pkt), priv->list); 31 31 if (err) { 32 - regs->verdict.code = NF_DROP; 33 - return; 32 + if (err == -EEXIST) { 33 + /* Call gc to update the list count if any connection has 34 + * been closed already. This is useful for softlimit 35 + * connections like limiting bandwidth based on a number 36 + * of open connections. 37 + */ 38 + nf_conncount_gc_list(nft_net(pkt), priv->list); 39 + } else { 40 + regs->verdict.code = NF_DROP; 41 + return; 42 + } 34 43 } 35 44 36 45 count = READ_ONCE(priv->list->count);