nftables maps offer a surprisingly powerful way to dynamically route or mark packets based on arbitrary keys derived from packet data.

Let’s see this in action. Imagine we want to apply a specific firewall mark to all outgoing packets originating from our web server’s IP address, 192.168.1.100, so we can later shape their traffic.

First, we define a map that associates IP addresses with firewall marks:

nft add table ip mangle
nft add chain ip mangle postrouting { type nat hook postrouting priority -100 \; }
nft add map ip mangle ip_src_to_mark { type ipv4_addr : integer \; }
nft add element ip mangle ip_src_to_mark { 192.168.1.100 : 10 }
nft add rule ip mangle postrouting ip saddr @ip_src_to_mark meta mark set $ip_src_to_mark

Here’s what’s happening:

  • nft add table ip mangle: We create a new table named mangle for IP packets. The mangle table is where we can modify packet headers, including setting firewall marks.
  • nft add chain ip mangle postrouting { type nat hook postrouting priority -100 \; }: We add a chain named postrouting to the mangle table. This chain hooks into the postrouting stage of the network stack, meaning it processes packets just before they are sent out onto the network. A priority of -100 ensures it runs relatively early.
  • nft add map ip mangle ip_src_to_mark { type ipv4_addr : integer \; }: This is the core of the map. We declare a map named ip_src_to_mark within our mangle table. Its type ipv4_addr : integer specifies that it takes an IPv4 address as a key and an integer as its value.
  • nft add element ip mangle ip_src_to_mark { 192.168.1.100 : 10 }: We add an entry to our map. This entry states that when the key is the IP address 192.168.1.100, the corresponding value is the integer 10. This integer 10 will be our firewall mark.
  • nft add rule ip mangle postrouting ip saddr @ip_src_to_mark meta mark set $ip_src_to_mark: This rule is attached to our postrouting chain. It says: "If the source IP address (ip saddr) of a packet is found as a key in the @ip_src_to_mark map, then take the corresponding value from the map and set the packet’s firewall mark (meta mark set) to that value ($ip_src_to_mark)."

Now, any outgoing packet from 192.168.1.100 will have the firewall mark 10.

The real power of maps lies in their ability to store a large number of key-value pairs efficiently. Instead of writing a separate rule for every IP address you want to mark, you define a single rule that references the map. This is crucial for performance when dealing with hundreds or thousands of entries.

You can use maps for a variety of purposes beyond just marking packets. For instance, you could create a map to translate source IP addresses to different NAT destinations, or to associate VLAN IDs with specific interface names. The key is that you have a piece of data within the packet (or packet metadata) that you want to use to look up a corresponding action or value.

The type declaration for a map is critical. It dictates what kind of data can be used as keys and what kind of data can be stored as values. Common types include ipv4_addr, ipv6_addr, integer, string, macaddr, and combinations thereof. For example, a map to route packets based on both source IP and destination port would have a type like ipv4_addr : integer : integer (source IP, destination port, value).

When you use a map in a rule, you reference it using the @ symbol followed by the map name, like @ip_src_to_mark. The value retrieved from the map is then used in the rule’s action. In our example, $ip_src_to_mark is a special nftables variable that expands to the value found in the map for the current packet’s source IP.

A common misconception is that maps are just a more compact way to write many identical rules. While they do offer compactness, their performance advantage comes from the underlying implementation. nftables uses efficient data structures (like hash tables or tries, depending on the map type and size) to perform lookups, making them significantly faster than iterating through a long list of individual rules, especially as the number of entries grows.

The next logical step is to explore how to dynamically update these maps without restarting your firewall or even interrupting existing connections.

Want structured learning?

Take the full Nftables course →