Tip

If you find some discrepancy or missing information - open a GitHub issue

Was this information useful? Then star the repository on GitHub

Warning

Writing of this documentation is still in progress! Read it with a grain of salt!

Firewall - NFTables


Introduction

Chain hooks/Table families

nft_hooks

Packet flow

nft_flow


References


Installation

Kernel Modules

Some functionality of NFTables might not be enabled by default.

To check which was enabled at compile-time - check the config file:

cat "/boot/config-$(uname -r)" | grep -E "CONFIG_NFT|CONFIG_NF_TABLES"

To find all existing modules:

find /lib/modules/$(uname -r) -type f -name '*.ko' | grep -E 'nf_|nft_'

To enable a module:

modprobe nft_nat
modprobe nft_tproxy

Usage

Config File

NFTables can be completely configured from one or more config files.

Most times you might want to use:

  • a main config file: /etc/nftables.conf

  • a configuration directory to include further files: /etc/nft.conf.d/

The systemd service will load the main config file by default:

# /lib/systemd/system/nftables.service
[Unit]
...

[Service]
...
ExecStart=/usr/sbin/nft -f /etc/nftables.conf
ExecReload=/usr/sbin/nft -f /etc/nftables.conf
ExecStop=/usr/sbin/nft flush ruleset
...

Main config file example:

#!/usr/sbin/nft -f
flush ruleset
include "/etc/nft.conf.d/*.conf"

Then you can add your actual configuration in the configuration directory!

To test your configuration:

nft -cf /etc/nftables.conf

CLI

Programmatically

THere are some libraries/modules that enable you to manage NFTables from code directly:

Ansible

See: NFTables Ansible-Role


Troubleshooting

Trace

You can trace traffic that flows through you chains.

See also: NFTables documentation - trace

You need to:

  • Tag traffic you want to trace by adding the meta nftrace set 1 option to a rule.

  • Listen to this traces by running nft monitor trace in a separate terminal.

You may want to start the trace at the point where the traffic enters.

Example for input traffic:

chain input {
    type filter hook input priority 0; policy drop;

    # enable tracing for: tcp-traffic to port 1337 originating from a specific network
    tcp dport 1337 ip saddr 192.168.10.0/24 meta nftrace set 1

    ...

}

Example for output traffic:

chain output {
    type filter hook output priority 0; policy drop;

    # enable tracing for: http+s to a specific target
    tcp dport { 80, 443 } ip daddr 1.1.1.1 meta nftrace set 1

    ...

}

Example monitor information:

nft monitor trace
> trace id a95ea7ef ip filter trace_chain packet: iif "enp0s25" ether saddr 00:0d:b9:4a:49:3d ether daddr 3c:97:0e:39:aa:20 ip saddr 8.8.8.8 ip daddr 192.168.2.118 ip dscp cs0 ip ecn not-ect ip ttl 115 ip id 0 ip length 84 icmp type echo-reply icmp code net-unreachable icmp id 9253 icmp sequence 1 @th,64,96 24106705117628271805883024640
> trace id a95ea7ef ip filter trace_chain rule meta nftrace set 1 (verdict continue)
> trace id a95ea7ef ip filter trace_chain verdict continue
> trace id a95ea7ef ip filter trace_chain policy accept
> trace id a95ea7ef ip filter input packet: iif "enp0s25" ether saddr 00:0d:b9:4a:49:3d ether daddr 3c:97:0e:39:aa:20 ip saddr 8.8.8.8 ip daddr 192.168.2.118 ip dscp cs0 ip ecn not-ect ip ttl 115 ip id 0 ip length 84 icmp type echo-reply icmp code net-unreachable icmp id 9253 icmp sequence 1 @th,64,96 24106705117628271805883024640
> trace id a95ea7ef ip filter input rule ct state established,related counter packets 168 bytes 53513 accept (verdict accept)

Translate IPTables

Most times the behaviour of IPTables and NFTables is pretty much the same.

In some Distributions the default IPTables backend is already migrated to NFTables.

Why translate from IPTables?

There are 1000x more resources related to IPTables out there that might help you get things working.

I would recommend:

  • having a blank VM to test IPTables ruleset

  • save the working minimal-ruleset iptables-save > /etc/iptables/rules.ipt

  • translate the ruleset to nftables iptables-restore-translate -f /etc/iptables/rules.ipt > /etc/iptables/rules.nft

  • test the NFTables ruleset and remove the default chains you don’t need (IPTables is a little more messy with its defaults)

BTW: one can also restore IPTables rules by using iptables-restore < /etc/iptables/rules.ipt


Config

NFTables base-config example

TPROXY

Quote from the tproxy kernel docs:

Transparent proxying often involves "intercepting" traffic on a router.
This is usually done with the iptables REDIRECT target; however, there are serious limitations of that method.
One of the major issues is that it actually modifies the packets to change the destination address -- which might not be acceptable in certain situations. (Think of proxying UDP for example: you won't be able to find out the original destination address. Even in case of TCP getting the original destination address is racy.)
The 'TPROXY' target provides similar functionality without relying on NAT.

This functionality allows us to send traffic to an userspace process and read/modify it.

This can enable powerful solutions! Per example see: blog.cloudflare.com - Abusing Linux’s firewall

Warning

TPROXY seems to only support local targets.

As one can see in the kernel sources - there is a check if the target port is in use: nft_tproxy.c

References

Usage

One thing you’ll need to know: The TPROXY operation can only be used in the prerouting - filter (mangle) chain!

Traffic that passes this chain/hook by default can easily be proxied.

OUTPUT CHALLENGE:

Because of this - traffic that enters at the ‘output’ (originating from the same host) chain/hook can not be redirected directly.

We need to route it to ‘loopback’ so it passes through ‘prerouting’.

NOTE: This image shows the problem we are facing in a very abstract way. It might not display the traffic-flow in a correct manner!

nft_tproxy

REMOTE PROXY CHALLENGE:

You might want to target a remote proxy server. This does not work with this operation on its own.

One would need to use a proxy-forwarder tool that can handle this for you.

I’ve patched an existing tool for exactly this purpose: proxy-forwarder

With a tool like that you can wrap the plain traffic received from TPROXY and forward or tunnel it.

# NFTables =TCP=> TPROXY (forwarder @ 127.0.0.1) =HTTP[TCP]=> PROXY

> curl https://superstes.eu
# proxy-forwarder
2023-08-29 20:49:10 | INFO | handler | 192.168.11.104:36386 <=> superstes.eu:443/tcp | connection established
# proxy (squid)
NONE_NONE/200 0 CONNECT superstes.eu:443 - HIER_NONE/- -
TCP_TUNNEL/200 6178 CONNECT superstes.eu:443 - HIER_DIRECT/superstes.eu -

> curl http://superstes.eu
# proxy-forwarder
2023-08-29 20:49:07 | INFO | handler | 192.168.11.104:50808 <=> superstes.eu:80/tcp | connection established
# proxy (squid)
TCP_REFRESH_MODIFIED/301 477 GET http://superstes.eu/ - HIER_DIRECT/superstes.eu text/html

Examples


Service

To keep invalid configuration from stopping/failing your nftables.service - you can add a config-validation in it:

# /etc/systemd/system/nftables.service.d/override.conf

[Service]
ExecStartPre=/usr/sbin/nft -cf /etc/nftables.conf

ExecReload=
ExecReload=/usr/sbin/nft -cf /etc/nftables.conf
ExecReload=/usr/sbin/nft -f /etc/nftables.conf

Restart=on-failure
RestartSec=5s

This will catch and log config-errors before doing a reload/restart.

When doing a system-reboot it will still fail if your config is bad.


Addons

NFTables lacks some functionality, that is commonly used in firewalling.

You can add a scheduled scripts that add these functionalities to NFTables!

See: Ansible-managed addons

DNS

It is nice to have variables that hold the IPs of some DNS-record.

NFTables CAN resolve DNS-records - but will throw an error if the record resolves to more than one IP.. (Error: Hostname resolves to multiple addresses)

See: NFTables Addon DNS

IPLists

This addon was inspired by the same functionality provided on OPNSense

It will download existing IPLists and add them as NFTables variables.

IPList examples:

See: NFTables Addon IPList

Failover

See: NFTables Addon Failover


Examples

Ansible

See: Ansible-based examples

IPv4 Baseline

IPv6 Baseline

Security Baseline

Docker host

Proxmox host (PVE)

Forwarder (Router, Network firewall, VPN Server)


Integrations

Fail2Ban

Squid