Nftables rate/connection limiting

Hi!
I'm trying to get along with nftables...
How to setup do a simple rate/connection limiter?
The goals are:

  • Limit tcp connections to 3 connection/sec per IP to ports 80,443 on a local web server
  • Limit tcp connections to a global max of 10 connections per IP to ports 80,443 on to a local web server

I came up with this:

nft insert rule inet fw4 prerouting iifname eth1 ip daddr < local ipv4 > tcp dport { 80, 443 } ct state new meter connlimit_per_sec_v4 { ip saddr limit rate over 3/second } counter drop
nft insert rule inet fw4 prerouting iifname eth1 ip daddr < local ipv4 > tcp dport { 80, 443 } meter connlimit_global_v4 { ip saddr ct count over 10 } counter drop
nft insert rule inet fw4 prerouting iifname eth1 ip6 daddr < local ipv6 > tcp dport { 80, 443 } ct state new meter connlimit_per_sec_v6 { ip6 saddr limit rate over 3/second } counter drop
nft insert rule inet fw4 prerouting iifname eth1 ip6 daddr < local ipv6 > tcp dport { 80, 443 } meter connlimit_global_v6 { ip6 saddr ct count over 10 } counter drop

But it doesn't seem to work...
I was thinking that DNAT is maybe the problem here...
But it seems like the processing order is
DNAT (Prio -100 ) > Prerouting
So matching by destination address and port should work.
And I tested this with a simple deny rule,
blocking everything that is going to the local addresses and ports 80,443
and it works.
Maybe my meter setup is bad?

Also, how to use the fw4 nft include thing...
I created a file, pasting my above code with out the
nft insert rule inet fw4 prerouting
part, but it doesn't work...

IMO, you should use the forward (or forward_wan) chain.

# /etc/config/firewall
config include
        option type 'nftables'
        option path '/etc/conn_limit.nft'
        option position 'chain-pre'
        option chain 'forward_wan'

# /etc/conn_limit.nft
ip daddr < local ipv4 > tcp dport { 80, 443 } ct state new meter connlimit_per_sec_v4 { ip saddr limit rate over 3/second } counter drop
...
...

The forward chains do not work.
I guess, this is considered as "local" traffic as indicated here:
https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks
So maybe input_wan is the better choice.
The rules seem to work though as I managed to get the counters to increase yesterday.

How to prevent fw4 to readd the include on every fw restart?

When you mention DNAT, the logical conclusion is that the service is hosted by a device on your local network, not the router itself. That's why the forward chain was suggested.

From the nftables wiki:

Apparently the meter keyword causes some problems.
Check out the solution from the thread below:

https://forum.openwrt.org/t/22-03-2-firewall4-custom-nftables-rule-resource-busy/ - #4 by hamelg

yeah sorry maybe I should have mentioned that the service is running on the router itself.

I read that part about meters too....
But do I miss understand the meter parameter?
It automatically removes the entry when the condition is no longer met or not?
How to do this with a set?
There is the timeout parameter but that seems kinda useless for this purpose.

# /etc/config/firewall
config include
        option type 'nftables'
        option path '/etc/limit_v4.nft'
        option position 'chain-pre'
        option chain 'input_wan'
        
config include
        option type 'nftables'
        option path '/etc/dyn_set_v4.nft'
        option position 'table-pre'


# /etc/limit_v4.nft
ip daddr <ip address> tcp dport { 80, 443 } ct state new add @dyn_set_v4 { ip saddr timeout 300s limit rate over 3/second } counter drop

# /etc/dyn_set_v4.nft
set dyn_set_v4 { type ipv4_addr; flags timeout, dynamic; }

The idea of the timeout is to clear the entry (the recorded source IP address) from the dynamic set after the timeout has expired.

root@MikroTik:~# nft list set inet fw4 dyn_set_v4
table inet fw4 {
        set dyn_set_v4 {
                type ipv4_addr
                size 65535
                flags dynamic,timeout
                elements = { 37.157.X.X limit rate over 3/second timeout 5m expires 4m54s250ms }
        }
}

Thanks...
But my question was how to remove the entry instantly from the set like the meter option does when the condition isn't true anymore.

And what's the point of that?
Either way, all elements will be removed from the set after the condition is no longer valid and/or the timeout has expired. Set a shorter timeout value if you believe that's so important.

Hmm, I don't know but...
Limiting to 3 new connection per second, using a timeout higher >1sec will not work as intended.
Because every time the condition is met the timeout is reset and will block connections endlessly.
Using a timeout of 1sec will work somewhat, I guess...
But I would rather have this working like the meter function does.
Can this be done with the dynamic flag and not using a timeout ?
I can't find any info about what the dynamic flag actually does.

I found it at https://www.netfilter.org/projects/nftables/manpage.html (the nftables wiki appears to be out of date). Search down the page to "SET STATEMENT":

The set statement is used to dynamically add or update elements in a set from the packet path. The set setname must already exist in the given table and must have been created with one or both of the dynamic and the timeout flags. The dynamic flag is required if the set statement expression includes a stateful object. The timeout flag is implied if the set is created with a timeout, and is required if the set statement updates elements, rather than adding them. Furthermore, these sets should specify both a maximum set size (to prevent memory exhaustion), and their elements should have a timeout (so their number will not grow indefinitely) either from the set definition or from the statement that adds or updates them. The set statement can be used to e.g. create dynamic blacklists.

1 Like

Thanks,
so there is no way to make sets + map work like the same as the meters option?