Score:1

nftables - limit rate behavior (error or misusage ?)

nl flag

I am trying to set up a firewall with nftables but I failed to understand and implement a simple rate limit based on the documentation I have found.

  • OS : Ubuntu 20.04 LTS
  • nftables version : 0.9.3 (Topsy)
  • kernel release: 5.8.0-53-generic

I have built the test firewall with the below sequence of commands:

nft 'add table inet testnetwork'
nft 'add chain inet testnetwork INPUT { type filter hook input priority 0; policy drop; }'
nft 'add set inet testnetwork SSH { type ipv4_addr; flags dynamic, timeout; size 65536; }'
nft 'add rule inet testnetwork INPUT ct state related,established counter accept'
nft 'add rule inet testnetwork INPUT ip saddr @SSH ct state new tcp dport 22 counter drop'
nft 'add rule inet testnetwork INPUT ct state new tcp dport 22 limit rate over 10/minute add @SSH {ip saddr timeout 60s} counter'
nft 'add rule inet testnetwork INPUT ct state new tcp dport 22 tcp sport 1024-65535 counter accept'

When I list the initial ruleset I get :

table inet testnetwork {
    set SSH {
        type ipv4_addr
        size 65536
        flags dynamic,timeout
    }

    chain INPUT {
        type filter hook input priority filter; policy drop;
        ct state established,related counter packets 0 bytes 0 accept
        ip saddr @SSH ct state new tcp dport 22 counter packets 0 bytes 0 drop
        ct state new tcp dport 22 limit rate over 10/minute add @SSH { ip saddr timeout 1m } counter packets 0 bytes 0
        ct state new tcp dport 22 tcp sport 1024-65535 counter packets 0 bytes 0 accept
    }
}

With such a configuration, I would expect an IP to get added to the SSH set on its 11th (new) connection trial within 1 minute and to get blocked (for 1 minute) starting from the 12th attempt.

However, when I open a second terminal window and sequentially initiate and then close less then 10 ssh connections to 127.0.0.1, I get the IP added to the SSH set and then blocked.

Below ruleset status at the 7th attempt:

table inet testnetwork {
    set SSH {
        type ipv4_addr
        size 65536
        flags dynamic,timeout
        elements = { 127.0.0.1 timeout 1m expires 54s564ms }
    }

    chain INPUT {
        type filter hook input priority filter; policy drop;
        ct state established,related counter packets 156 bytes 28692 accept
        ip saddr @SSH ct state new tcp dport 22 counter packets 3 bytes 180 drop
        ct state new tcp dport 22 limit rate over 10/minute add @SSH { ip saddr timeout 1m } counter packets 1 bytes 60
        ct state new tcp dport 22 tcp sport 1024-65535 counter packets 6 bytes 360 accept
    }
}

At this point, either I do not understand the limit rate mechanism correctly, or I have made a mistake somewhere else.

Could someone please help me by pointing out if my expectation is wrong or where the mistake could come from?

Kind regards and thanks for your time

A.B avatar
cl flag
A.B
The kernel probably doesn't choose to wait 1mn to do something. It's a rate. I'd consider 10/mn <=> 1/6s. So to not trigger it, you should space attempts by 10s, that should be far enough from the 6s per attempt. Can't grasp exactly how it's done: https://elixir.bootlin.com/linux/v5.12.13/source/net/netfilter/nft_limit.c
A.B avatar
cl flag
A.B
I guess this can help too: https://en.wikipedia.org/wiki/Token_bucket
keeplearningtogether avatar
nl flag
Hi @A.B, Many thanks for your answer and your time, this is very much appreciated. This token bucket approach explains quite a few things. I will make some tests to try to get the exact behavior as I am not a C guru but at least I know where to start now and this is a big help.
A.B avatar
cl flag
A.B
I know I left only a comment, but if you get conclusive results, you could consider making your own answer about this later.
keeplearningtogether avatar
nl flag
I am new on this platform, should I click on "Answer Your Question" and make a summary or how does it work exactly ? Sorry I did not read through yet to see which are the best practices and so
Score:0
us flag

On my VPS

sshPort=2222
nft add table ip sshGuard
nft add chain ip sshGuard input { type filter hook input priority 0 \; }
nft add set ip sshGuard denylist { type ipv4_addr \; flags dynamic, timeout \; timeout 5m \; }
nft add rule ip sshGuard input tcp dport $sshPort ct state new ip saddr @denylist reject
nft add rule ip sshGuard input tcp dport $sshPort ct state new limit rate over 2/minute burst 3 packets add @denylist { ip saddr } reject
nft list table ip sshGuard

I test a few times (each by nft flush set ip sshGuard denylist), the 4th connection will be rejected. If I change the limit rate over 2/minute burst 3 packets to limit rate over 2/minute, a rejection comes after 7-9 connections. Hence, I do not know the exact mechanism of limit rate over 2/minute, but a burst 3 packets seems make things more certain.

The default burst is set by #define NFT_LIMIT_PKT_BURST_DEFAULT 5

Score:0
nl flag

I would like to apologize for my late feedback, I had some other urgencies on the stack and finally ended up with the task that required this question.

First of all, thank you very much @A.B for your answers and help. I did not know the "token bucket algorithm" and helped to understand the overall process, in particular the "rate" in the sense that the kernel as you mentioned does not wait 1 minute to make something.

Also thanks to @Chen Deng-Ta for your tests.

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.