The best reference for nftables is at the dedicated wiki wiki nftables. Some other references I found nftables router. The reference at stosb is good, but not for a router Explaining My Configs: nftables.

After a lot of experimenting the following is my NFTables router configuration file. Create the following file called: “router.nft”.

sudo vim /etc/nftables.conf

Router NFtable Setup - ''/etc/nftables.conf''

Some configuration notes

Refer to nftables wiki Matching packets by interface name. The use of interface index of the interface name causes an error on boot of the nftables.service, probably because the interface has not been brought up and indexed. Yet the name index of lo does work, perhaps it is predefined. Using the network interface name works. Use of name indexes is said to be faster than names. One possible way around this would be to have a basic nftables.service script for boot, dropping all connectivity, except local ssh ports and have a separate end of boot script with the full nftables script.

:!: It is important to remember that the order of the commands in a chain is very important. An earlier accept, drop or reject action will terminate further processing of a packet within that chain.
:!: My setup is based upon a static wan ip address. Dynamic ip wan address would require additional configuration and would not be as stable.

Some key related commands:

  • To load a nft configuration file: sudo nft -f router.nft (not used for start configuration)
  • The nftables configuration file can be made into an executable script as follows.
    • Add the following at the top of the file:
    • #!/user/sbin/nft -f
    • chmod +x router.nft
    • Use whereis nft to find where the nft executable is located
    • Remember that if located in same directory use sudo ./router.nft to run.
  • To list currently loaded nft tables: sudo nft list tables
  • To list a loaded table use: sudo nft list table inet firewall, the inet firewall as listed from sudo nft list table
  • To list a loaded table use: sudo nft list table ip nat. (The -a option lists handles.)
  • To add elements to nat tcp_nat_map: sudo nft add element nat tcp_nat_map { 81 : 192.168.1.100, 8080 : 192.168.1.101 }.

There seem to be a number of way to to allow port forwarding from WAN to LAN.

Using type filter hook forward priority 0; policy drop; for forwarding and type nat hook prerouting priority 0; for prerouting.

Prerouting: iif $external tcp dport { http, https } dnat home_srv, where “home_srv” host DNS resolves to IP address

  1. ip daddr home_srv ct status dnat accept
  2. ip daddr home_srv tcp dport {http, https} accept (not checked, taken from IPTables -A FORWARD -p tcp -d 192.168.99.100 –dport 80 -j ACCEPT)
  3. tcp dport {http, https} ct state new accept

After setting up and getting my router to function I found that I could not access my local http pages. This is a NAT problem*. The solution is hairpin NAT also known as NAT loopback or NAT reflection.

The following are some resources that discuss this:

The following commands provide a complete ipv4 NAT solution, including hairpin NAT using NFTables firewall:

  1. prerouting (standard)
    1. sudo nft add rule ip nat prerouting ip daddr 2.3.4.5/32 tcp dport {http, https} dnat 192.168.3.11
  2. postrouting (standard)
    1. Using masquerade: sudo nft add rule ip nat postrouting ip saddr 192.168.3.0/24 ip oifname ppp1 masquerade
    2. Using snat: sudo nft add rule ip nat postrouting ip saddr 192.168.3.0/24 ip oifname ppp1 snat 2.3.4.5/32
  3. postrouting (hairpin)
    1. Using snat to external ip: sudo nft add rule ip nat postrouting ip saddr 192.168.3.0/24 ip daddr 192.168.3.11/32 tcp dport {http, https} counter snat 2.3.4.5/32
    2. Using snat to internal ip: sudo nft add rule ip nat postrouting ip saddr 192.168.3.0/24 ip daddr 192.168.3.11/32 tcp dport {http, https} counter snat 192.168.3.1/32
    3. Using masquerade (to internal ip): sudo nft add rule ip nat postrouting ip saddr 192.168.3.0/24 ip daddr 192.168.3.11/32 tcp dport {http, https} counter masquerade

Notes:

  1. Where options for snat or masquerade are given, either snat or masquerade can be used, not both.
  2. Hairpin snat to the router/gateway external address is expected to function correctly.
  3. masquerade versus snat:
    1. Masquerade operates directly with dynamic wan ip.
    2. Dynamic wan ip does not operate with snat reliably.
    3. Masquerade requires more router resources than snat

This only affects local networks that use NAT which is basically mandatory for IPv4 and not required for IPv6, hence unless NAT is used in a IPv6 local network hairpin.

* Whilst investigating this matter commentary it was often stated that this problem is better solved using DNS. However the root cause of the problem is fundamentally NAT related. I tried the DNS solution (dual horizon DNS) in Bind9, but was fundamentally unhappy with this solution. This is not to say that Hairpin NAT is the best solution in all cases. The DNS solution may be a better solution for larger organisations.

Diagram 1
DNAT, Simple, Hairpin, not working and Hairpin working
(Where the gateway (router) is on the network device ppp1)

  • sudo nft list table inet firewall -a -n ; where:
    • -a : show handle number (line numbers)
    • -n : shows names (default), e.g. dport ssh
    • -nn : shows numbers e.g. dport 22
  • sudo nft list table ip nat -a : list table ip nat with handles
  • sudo nft delete rule ip nat postrouting handle 12 : deletes rule in table ip nat postrouting handle 12
  • sudo nft delete rule inet firewall forward handle 32 : deletes rule table inet firewall forward handle 32
  • sudo nft add rule inet firewall input position 39 tcp dport domain counter : within table inet firewall input, before handle 39, adds rule tcp port domain counter

A good reference for this is in wiki.nftables.org Quick reference-nftables in 10 minutes

sudo nft add rule inet firewall forward iifname “he-ipv6” counter jump base_checks

sudo nft add rule inet firewall forward iifname “br0” oifname “he-ipv6” counter accept

The following rule did not work: sudo nft add rule inet firewall forward iifname “he-ipv6” oifname “br0” counter jump base_checks

  • To load the nft configuration file: sudo nft -f router.nft
  • The nftables configuration file can be made into an executable script as follows.
    • Add the following at the top of the file:
    • #!/user/sbin/nft -f
    • chmod +x router.nft
    • Use whereis nft to find where the nft executable is located
    • Remember that if located in same directory use sudo ./router.nft to run.
  • To list currently loaded nft tables: “sudo nft list tables”
  • To list a loaded table use: sudo nft list table inet firewall, the inet firewall as listed from sudo nft list tables
  • To list a loaded table use: sudo nft list table ip nat. (The -a option lists handles.)
  • To add elements to nat tcp_nat_map: sudo nft add element nat tcp_nat_map { 81 : 192.168.1.100, 8080 : 192.168.1.101 }. (For some reason the version of nft I have will not read in the elements via a nft -f command.)

Some info on NAT with NFTables and be seen in the following references: wiki.nftables.org - Performing Network Address Translation (NAT), Ars Technica SNAT vs. DNAT vs. Masquerading and wiki.gentoo.org Nftables/Examples. It is interesting to see that the nftables wiki states that iptables NAT must be turned off with sudo rmmod iptable_nat.

The systemd service nftables can be used to auto start nftables and is setup to load the nftable configuration file at /etc/nftables.conf. Use sudo systemctl status/start/stop/reload/enable/disable nftables.

Start and reload basically run /usr/sbin/nft -f /etc/nftables.conf, whereas stop runs /usr/sbin/nft flush ruleset

The command systemctl show -p FragmentPath nftables shows where the systemctl script is located; /lib/systemd/system/nftables.service, in my case.

See nftables-systemd on github.com


  • /mnt/shared/www/dokuwiki/data/pages/linux_router/nftables.txt
  • Last modified: 2021-06-30 Wed wk26 20:29
  • by baumkp