Introduction to nftables on Linux

Comment on Mastodon

Introduction

Linux kernel has an integrated firewall named netfilter, but you manipulate it through command lines such as the good old iptables, or nftables which will eventually superseed iptables.

Today, I'll share my experience in using nftables to manage my Linux home router, and my workstation.

I won't explain much in this blog post because I just want to introduce nftables and show what it looks like, and how to get started.

I added comments in my configuration files, I hope it's enough to get a grasp and make you curious to learn about nftables if you use Linux.

Configurations

nftables works by creating a file running `nft -f` in the shebang, this allows atomic replacement of the ruleset if it's valid.

Depending on your system, you may need to run the script at boot, but for instance on Gentoo, a systemd service is provided to save rules upon shutdown and restore them at boot.

Router

#!/sbin/nft -f
flush ruleset

table inet filter {

    # defines a list of networks for further reference
    set safe_local {
	type ipv4_addr
	flags interval

	elements = { 10.42.42.0/24 }
    }

    chain input {
        # drop by default
        type filter hook input priority 0; policy drop;
        ct state invalid drop comment "early drop of invalid packets"

        # allow connections to work when initiated from this system
        ct state {established, related} accept comment "accept all connections related to connections made by us"

        # allow loopback
        iif lo accept comment "accept loopback"

        # remove weird packets
        iif != lo ip daddr 127.0.0.1/8 drop comment "drop connections to loopback not coming from loopback"
        iif != lo ip6 daddr ::1/128    drop comment "drop connections to loopback not coming from loopback"

        # make ICMP work
        ip protocol icmp accept comment "accept all ICMP types"
        ip6 nexthdr icmpv6 accept comment "accept all ICMP types"

        # only for known local networks
        ip saddr @safe_local tcp dport {22, 53, 80, 2222, 19999, 12344, 12345, 12346} accept
        ip saddr @safe_local udp dport {53} accept

        # allow on WAN
        iif eth0 tcp dport {80} accept
        iif eth0 udp dport {7495} accept
    }

    # allow NAT to get outside
    chain lan_masquerade {
        type nat hook postrouting priority srcnat;
        meta nfproto ipv4 oifname "eth0" masquerade
    }

    # port forwarding
    chain lan_nat {
        type nat hook prerouting priority dstnat;
        iif eth0 tcp dport 80 dnat ip to 10.42.42.102:8080
    }

}

Workstation

#!/sbin/nft -f

flush ruleset

table inet filter {

    set safe_local {
	type ipv4_addr
	flags interval

	elements = { 10.42.42.0/24, 10.43.43.1/32 }
    }

    chain input {
        # drop by default
        type filter hook input priority 0; policy drop;
        ct state invalid drop comment "early drop of invalid packets"

        # allow connections to work when initiated from this system
        ct state {established, related} accept comment "accept all connections related to connections made by us"

        # allow loopback
        iif lo accept comment "accept loopback"

        # remove weird packets
        iif != lo ip daddr 127.0.0.1/8 drop comment "drop connections to loopback not coming from loopback"
        iif != lo ip6 daddr ::1/128    drop comment "drop connections to loopback not coming from loopback"

        # make ICMP work
        ip protocol icmp accept comment "accept all ICMP types"
        ip6 nexthdr icmpv6 accept comment "accept all ICMP types"

        # only for known local networks
        ip saddr @safe_local tcp dport 22 accept comment "accept SSH"
        ip saddr @safe_local tcp dport {7905, 7906} accept comment "accept musikcube"
        ip saddr @safe_local tcp dport 8080 accept comment "accept nginx"
        ip saddr @safe_local tcp dport 1714-1764 accept comment "accept kdeconnect TCP"
        ip saddr @safe_local udp dport 1714-1764 accept comment "accept kdeconnect UDP"
        ip saddr @safe_local tcp dport 22000 accept comment "accept syncthing"
        ip saddr @safe_local udp dport 22000 accept comment "accept syncthing"
        ip saddr @safe_local tcp dport {139, 775, 445} accept comment "accept samba"
        ip saddr @safe_local tcp dport {111, 775, 2049} accept comment "accept NFS TCP"
        ip saddr @safe_local udp dport 111 accept comment "accept NFS UDP"

        # for my public IP over VPN
        ip daddr 78.224.46.36 udp dport 57500-57600 accept comment "accept mosh"
        ip6 daddr 2a00:5854:2151::1 udp dport 57500-57600 accept comment "accept mosh"

    }

    # drop anything that looks forwarded
    chain forward {
        type filter hook forward priority 0; policy drop;
    }

}

Some commands

If you need to operate a firewall using nftables, you may use `nft` to add/remove rules on the go instead of using the script with the ruleset.

However, let me share a small cheatsheet of useful commands:

List rules

If you need to display the current rules in use:

nft list ruleset

Flush rules

If you want to delete all the rules, just use:

nft flush ruleset

Going further

If you want to learn more about nftables, there is the excellent man page of the command `nft`.

I used some resources from Arch Linux and Gentoo that you may also enjoy:

Gentoo Wiki: Nftables

Gentoo Wiki: Nftables examples

Arch Linux Wiki: Nftables