When it comes to packet filtering on Linux, Nftables (a successor to Iptables) has matured sufficiently in recent years for it to be considered the default tool to use today.
However, Iptables is still widely used in modern systems (e.g., Debian buster), with it often being “emulated” by using Nftables as backend, whereupon it becomes merely an alternative syntax to Nftables for defining packet filtering rules. Emulated or not, both Nftables and Iptables are based on the underlying Netfilter framework.
The Netfilter framework provides a series of “hooks” inside the Linux kernel network stack that are traversed by network packets (Figure 1). Other kernel components can register callback functions with those hooks, enabling them to inspect any packets coming in and decide whether to drop or accept them.
Figure 1: Simplified block diagram of Netfilter hooks
When a network packet is received on a network device, it first passes through the Prerouting hook. This is where the routing decision takes place. The kernel decides whether the packet is destined for a local process (e.g., a listening socket on a server in this system) or whether to forward it (system operates as a router). In the first case, the packet passes the Input hook and is then handed over to the local process. If the packet is destined to be forwarded, it traverses the Forward hook and then a final Postrouting hook before being sent out on a network device. For packets that are generated locally (e.g., by a client or server process that likes sending things out), they must first pass the Output hook and then the Postrouting hook before being sent out on a network device.
Hooks have been in the kernel for many years now but overall, their functionality has changed very little since the days of kernel 2.4. Of course, things get somewhat more complex when it comes to the intricate details of a modern kernel (Figure 2): The aforementioned hooks exist independently for the IPv4 and IPv6 protocols. Thus, IPv4 and IPv6 packets each traverse their own hooks. There are also other hooks for ARP packets and for Bridging. And all the hooks exist independently within each network namespace. Additionally, there is an ingress hook for each network device. The list goes on…
Figure 2: Netfilter hooks for IPv4, IPv6, ARP, Bridging and Ingress
Figure 3 shows the API provided by Netfilter for registering callback functions with a hook. Multiple functions can be registered with a hook, and an integer value called priority sorts the functions for said hook in ascending order.
Figure 3: Registering/Unregistering a callback function with a Netfilter hook
Hook traversal and verdict
For each network packet traversing a hook, the registered callback functions are called upon one by one in the order defined by the priority value. Each callback is required to return a “verdict”: NF_ACCEPT or NF_DROP. In case of NF_ACCEPT, the packet will traverse the next callback (if there is one). Thus, if all callbacks return NF_ACCEPT, the packet continues traversing the kernel network stack. NF_DROP, however, causes the packet to be deleted so that no further callbacks are traversed.
Figure 4: Traversal of callback functions and verdict
Nftables vs. Iptables
Both Nftables and Iptables organize their packet filtering rules into tables and chains, with a table being merely a container to group chains with a common purpose. The actual rules reside in the chains. Both frameworks register their (base) chains as callbacks with the Netfilter hooks. Iptables has a predefined, fixed set of tables and chains (see Figure 5), and the chains take their names from the hooks they are registered with. Furthermore, the Iptables suite is split into several distinct command line tools and corresponding kernel modules:
- iptables for IPv4
- ip6tables for IPv6
- arptables for ARP
- ebtables for Bridging
Figure 5: Iptables chains registered with IPv4 Netfilter hooks (+ connection tracking)
With Nftables, there are no predefined tables and chains, so it is up to the user to create them. This is done via a single command line tool called nft,which brings us to the concept of “address families”:
- ip: maps to IPv4 hooks (default)
- ip6: maps to IPv6 hooks
- inet: maps to both IPv4 and IPv6 hooks
- arp: maps to ARP hooks
- bridge: maps to bridging hooks
- netdev: maps to ingress hook
When creating a table, the user has to specify an address family. This then applies to all chains within that table. When creating a chain, the user must specify the Netfilter hook that the chain will be registered with and the priority value.
Example: IPv4 Input hook
Creating a table named filter in the address family ip and two base chains named foo and bar, and registering them with the Netfilter IPv4 hook input with priorities 0 and 50 (Figure 6).
Figure 6: IPv4 Netfilter Input hook
Example: Tiny Edge Router
Doing some simple IPv4 packet filtering and SNAT (masquerading) on an edge router.
Figure 7: Edge Router
Figure 8: Nftables chains registered with an IPv4 Netfilter hook (+ connection tracking)
At Teldat, we consider services like Nftables and Netfilter to be essential basic building blocks to provide the SDN / SD-WAN functionality