1

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

  • 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 Jun 28 '21 at 20:05
  • I guess this can help too: https://en.wikipedia.org/wiki/Token_bucket – A.B Jun 28 '21 at 20:17
  • 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. – keeplearningtogether Jul 01 '21 at 07:16
  • I know I left only a comment, but if you get conclusive results, you could consider making your own answer about this later. – A.B Jul 01 '21 at 07:21
  • 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 – keeplearningtogether Jul 01 '21 at 07:26

2 Answers2

0

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

Chen Deng-Ta
  • 111
  • 2
0

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.

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community May 20 '22 at 09:28