The inet family in nftables isn’t just a convenience; it’s a fundamental shift that collapses the separate worlds of IPv4 and IPv6 into a single, unified packet-handling engine.
Let’s see it in action. Imagine you want to allow SSH traffic on your server.
# Create a table named 'filter' that handles both IPv4 and IPv6
nft add table inet filter
# Create a chain named 'input' within the 'filter' table, hooked to the input path
nft add chain inet filter input { type filter hook input priority 0 \; }
# Add a rule to accept established and related SSH connections
nft add rule inet filter input ct state established,related accept
# Add a rule to accept new SSH connections on TCP port 22
nft add rule inet filter input tcp dport 22 accept
See? nft add table inet filter and nft add chain inet filter input create structures that can hold rules for both protocols. The subsequent rules, tcp dport 22, don’t specify ip or ip6; they operate on whichever protocol the packet happens to be. If an IPv4 packet arrives on port 22, the tcp dport 22 match applies. If an IPv6 packet arrives on port 22, the exact same tcp dport 22 match also applies.
This unification solves a few headaches. Before inet, you’d typically manage two separate sets of rules: one for ip (IPv4) and one for ip6 (IPv6). This meant duplicating rules, increasing the chance of inconsistencies, and making management a chore. A common scenario was forgetting to update a rule in one family when you updated it in the other. The inet family consolidates this. You write a rule once, and it applies to both IPv4 and IPv6 packets that match its criteria.
Internally, nftables’ inet family works by having a single set of chains and tables that can process packets regardless of their IP version. When a packet arrives, nftables inspects its headers. If it’s an IPv4 packet, it proceeds through the inet ruleset, and matches like tcp dport 22 work as expected. If it’s an IPv6 packet, it also proceeds through the same inet ruleset, and the tcp dport 22 match still works because the TCP header is at a consistent offset within both IPv4 and IPv6 packets. The magic is that nftables handles the underlying protocol differences transparently for you. The inet family’s matching statements (like tcp dport, udp sport, ip saddr, ip6 daddr) are designed to be protocol-agnostic where possible, or to intelligently adapt based on the packet’s actual IP version. For example, ip saddr will match an IPv4 source address for an IPv4 packet, and ip6 daddr will match an IPv6 destination address for an IPv6 packet.
The real power comes from how it handles protocol-specific attributes. When you use a match like tcp dport 22, nftables knows it’s inspecting the TCP header. If the packet is IPv4, it finds the TCP header after the IPv4 header. If the packet is IPv6, it finds the TCP header after the IPv6 header. The offset of the TCP header from the start of the IP payload is consistent. For matches that are IP version specific, you can still use them within an inet family rule. For instance, if you wanted to allow IPv4 traffic from a specific subnet but not IPv6, you’d write:
nft add rule inet filter input ip saddr 192.168.1.0/24 accept
This rule will only match IPv4 packets where the source address is in 192.168.1.0/24. An IPv6 packet would simply not match the ip saddr condition. Conversely, to match an IPv6 address:
nft add rule inet filter input ip6 saddr 2001:db8::/32 accept
This rule would only match IPv6 packets from that specific IPv6 subnet. The inet family elegantly allows you to blend protocol-agnostic rules (like tcp dport 22) with protocol-specific rules (like ip saddr or ip6 saddr) within the same ruleset.
One subtle point is how ct state works across families. When you use ct state established,related accept in an inet family rule, the connection tracking (conntrack) module is capable of tracking connections that might involve both IPv4 and IPv6 endpoints, or just one. For example, if you have a client that initiates a connection via IPv4 and later sends a packet via IPv6 to the same service, conntrack in the inet family can often correlate these as part of the same logical flow, thanks to the unified inet context. This is crucial for stateful firewalling where you want to track established connections regardless of the IP version used for subsequent packets.
The next step is understanding how to leverage nftables’ powerful set and map constructs within the inet family for more complex and efficient rule management.