User Tools

Site Tools


docs:guide-user:firewall:misc:nftables

nftables

nftables project is an enhancement to netfilter, re-using most of the existing code but enhancing/streamlining based on experience.

As with iptables, there is a large amount of information and examples available on the web for nftables. Some links include:

nftables in OpenWrt

nftables are not currently the primary form of firewall and NAT in OpenWrt, that role is taken by iptables - and that is what is set via the web interface in OpenWrt. However nftables have been in the kernel for many years, and expected to take over from iptables. Indeed in version 1.8.0 of iptables, although the user side is the same as before, the backend which sets the kernel is now based on nftables backend. OpenWrt currently uses fw3 to set the firewall, and that is not compatible with nftables; hence in OpenWrt it is not currently possible to use nftables from the web interface. However if users are comfortable with line interface, nftables are supported by OpenWrt. The rest of this article describes how to use them, users should do this with care, and read the general documentation for nftables.

Firstly nftables kernel modules, are in part not compatible with iptables, in particular the kernel module iptable_nat the nftables equivalents nf_nat* and nf_masq; so before using nftables it is best to remove (via opkg remove) iptables. In particular:

  • kmod-ipt-*
  • iptables*
  • ip6*

Then the nftable equivalents will need to be installed

  • nftables
  • libnftnl
  • kmod-nf*

These are available on the openwrt packages. For the example below you propbably need:

  • kmod-nf-conntrack
  • kmod-nf-conntrack6
  • kmod-nf-nat
  • kmod-nf-reject
  • kmod-nf-reject6
  • kmod-nfnetlink
  • kmod-nft-core
  • kmod-nft-nat
  • libnftnl
  • nftables

nftables can be configured via a line command, just like iptables, all be it with a different syntax. However nftables can also read a “c” like script - and this script is far more readable, and the suggested way to use nftables. This needs to be saved in a file, and the suggested location is /etc/nftables.conf. An example script is given at the bottom of this page, and worth studying. The rest of this page uses this script as an example. In this example there are 4 different ip inputs:

  • lo - for internal packets
  • br-lan - for the ethernet, four different ports, but joined in a switch
  • wlan0 - the WiFi connection
  • pppoa-wan - the WAN connection, across ADSL

The aim here is to join together the first three, so they can communicate. To set up firewall towards the WAN. To set up NAT for ipv4. And to let in a few ports on the WAN.

First:

flush ruleset

this is usually used on the command like nft flush ruleset to clear out the nftables currently loaded in the kernel. It is good to start the script with this, as otherwise when a script is loaded it loads over what is already in the kernel; so starting with this line, and if the script is loaded twice is only in the kernel once. Although in the script, this command will not be loaded into the kernel, instead operates on the kernel rules.

table ip nat {
	chain prerouting {
		type nat hook prerouting priority 0; policy accept;
	}

	chain postrouting {
		type nat hook postrouting priority 100; policy accept;
	}
}

This introduces several concepts. The script is made up of tables, than contain chains, that contain rules. In iptables tables also exist, but in only certain types. nftables is more flexible, in that the tables can be called anything. Convention is though to use the iptables names by default. So in this case the table is call “nat” as it contains NAT rules, the NAT nature though is only set up in the chains with the type nat. The table contains two chains, one for “prerouting” and one for “postrouting” again these are just names, their behaviour is only set up with the “hook {pre|post}routing”. The last point is this acts on the family “ip”, there are only six families in nftables:

  • ip - ipv4 packets
  • ip6 - ipv6 packets
  • inet - both ipv4 and ipv6 packets
  • arp - arp packets
  • bridge - bridge rules
  • netdev - for netdev and ingress

This is similar to iptables family of commands, but under nftables there are all under the same command. Also nftables contains the concept inet that applies to all IP packets, which means one set of rules can cover both. The one exception to inet packets in in NAT tables (like this) where ip and ipv6 need to be separate.

table ip nat {
	chain prerouting {
		type nat hook prerouting priority 0; policy accept;
		tcp dport 12345 dnat to 192.168.2.111:ssh
	}
}

This is the rule, it says router when a packet arrives for tcp port 12345, that it is forward only the ssh port on a machine on the LAN. This is forwarded using dnat - destination NAT, as its a ssh connection, and anything back from the machine on LAN is send back to machine stending the original packet. This is done in prerouting it is done as soon as the packet enters the machine. A similar command would used if you wanted a WWW server on a local machine exported to the WAN so people could connect over the internet.

table ip nat {
	chain postrouting {
		type nat hook postrouting priority 100; policy accept;
		oiftype ppp masquerade
	}
}

This is another NAT, its the main NAT so that LAN can get out to the internet on the WAN, but looking like the WAN connection. masquerade is a type of snat - Source NAT. On packets it changes the source address, in a snat the address can be set, but on a masquerade the address is always set to the port it is going out on. In this case the port is set by oiftype ppp, this means the WAN, but the reason isn't easy to see. The WAN is on an ADSL connection, this is a serial connection, which packets are sent across by using ppp. Now the oiftype ppp says only run this command on the output port if it is running ppp. However how are you to know this - you won't find this command described almost everywhere on the www. The simple way to learn about it though, is the command nft describe oiftype, and that shows what types of port nftable can detect, and includes ppp. This part of the code could also be written oifname “pppoa-wan” masquerade however testing for ppp is the simpler difference between the WAN and LAN ports.

table inet filter {
	chain input {
		type filter hook input priority 0; policy drop;
	}

	chain forward {
		type filter hook forward priority 0; policy drop;
	}

	chain output {
		type filter hook output priority 0; policy accept;
	}
}

This is the main filter table, it is set up in the same way as the nat table. The chain names (and table name) can be anything, but they only become tied to what they do on the type line which sets up their description.

The rules for input and forward are exactly the same, this is saying we treat packets from the LAN as the same for going on to the internet, as going to the router. Also in reverse, anything from the WAN needs to pass either through the input or forward chain, so we treat both the same. The code is:

		ct state { related, established } accept
		ct state invalid drop  
		iiftype != ppp accept
		tcp dport ssh accept
		ip protocol icmp accept     
		ip6 nexthdr ipv6-icmp accept
		iiftype ppp drop

The first ct state says that any packet we get back, that is related to something we sent, that it should be accepted. So when you ask for something from the WAN, you get to see the reply. The second ct state just drops bad packets. the iiftype != ppp accept and we see iiftype again - it this says that any packet that comes from somewhere that isn't a ppp port (e.g. the WAN) then we should accept this. This allows everything from the LAN to propagate.

Next tcp dport ssh accept says accept all ssh connections, even from the WAN. This is needed as the dnat is forwarding ssh connections across the router.

The ip proticol icmp accept says we take icmp packets from anywhere, these are mainly used for testing the internet, so do things like ping packets - note this only works on IPv4 packets. During testing you probably want this, but do you want ping responded to on the WAN. The ip6 nexthdr ipv6-icmp accept does exactly the same on IPv6 packets, this is needed for things like Neighbour Discovery Protocol - needed for stateless addresses to be set. However an important point with these two command, although the table acts on inet and this means both IPv4 and IPv6, internal roles can be set to work on one or the other.

And finally the iiftype ppp drop just says that anything from the WAN, that we havn't yet accepted, we should drop. So its setting up the firewall. Note that with the icmp packets, that if we wanted to drop these on the WAN, we could just remove the icmp tests - as on the LAN all packets have already been accepted.

So this gives the full script, saved in /etc/nftables.conf as:

flush ruleset

table ip nat {
	chain prerouting {
		type nat hook prerouting priority 0; policy accept;
		tcp dport 12345 dnat to 192.168.2.111:ssh
	}

	chain postrouting {
		type nat hook postrouting priority 100; policy accept;
		oiftype ppp masquerade
	}
}
table inet filter {
	chain input {
		type filter hook input priority 0; policy drop;
		ct state { related, established } accept
		ct state invalid drop
		iiftype != ppp accept
		tcp dport ssh accept
		ip protocol icmp accept
		ip6 nexthdr ipv6-icmp accept
		iiftype ppp drop
	}

	chain forward {
		type filter hook forward priority 0; policy drop;
		ct state { related, established } accept
		ct state invalid drop  
		iiftype != ppp accept
		tcp dport ssh accept
		ip protocol icmp accept     
		ip6 nexthdr ipv6-icmp accept
		iiftype ppp drop
	}

	chain output {
		type filter hook output priority 0; policy accept;
	}
}

Finally how is this loaded into the kernel. The easyiest way is to add a line to /etc/rc.local - nft -f /etc/nftable.conf. This file is run at boot, and the command just set read the nft commands from the file just set up.

Reflections

And so some reflections on using nftables, why use it over iptables. Well it some ways its a choice, of which there are many , e.g {uClibc|musl libc} or {busybox|toybox}, this is the same {iptables|nftables}. Both do similar things, and I've used both professionally. For me, I prefer nftables and for me its the c like script that is used to set up the tables - I find this far more readable than the command line that you have to use in iptables. Now this doesn't say you should use nftables, but you have a choice between iptables and nftables.

docs/guide-user/firewall/misc/nftables.txt · Last modified: 2018/10/22 10:08 by summers