What is the different between "iifname" and "iif" in nftables?

Solution 1:

iif looks up and compares the interface index of the received packet, while iifname does string comparison with the interface name. Both have advantages and drawbacks.

So iif is using less ressources, because the interface index is a simple number already stored in the packet traversing the network stack and nftables and thus immediately available for comparison. But its drawback is that if the interface is deleted (and most probably recreated, but with a newer index value), then the corresponding rule in nftables won't match anymore. The only garanteed interface index at all times is the loopback interface (named lo by default): it's always the first created in the namespace and can't be deleted (nor added a second time), so its index value is always 1.

iifname on the other hand, just like iptables' --in-interface does a string comparison with the current interface's name. This uses more ressources, but allows to create a rule for a non-existing interface, in advance, with a deterministic name, which iif won't do easily. iifname can also do wildcard match, like in iifname "ppp*", which iif can't do and can be handy when interfaces get created and deleted often, but keeping a naming convention (like starting with ppp for PPP links).

Example (using nftables 0.9.2):

$ unshare -r -n
# ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
# nft add table ip filter
# nft add chain ip filter input '{ type filter hook input priority 0; policy accept; }'
# nft add rule ip filter input iif lo counter
# nft add rule ip filter input iif dummy0 counter
Error: Interface does not exist
add rule ip filter input iif dummy0 counter
                             ^^^^^^
# nft add rule ip filter input iifname dummy0 counter
# ip link add name dummy0 type dummy
# ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: dummy0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 4a:09:68:3a:34:91 brd ff:ff:ff:ff:ff:ff
# nft add rule ip filter input iif dummy0 counter
# nft list ruleset
table ip filter {
    chain input {
        type filter hook input priority filter; policy accept;
        iif "lo" counter packets 0 bytes 0
        iifname "dummy0" counter packets 0 bytes 0
        iif "dummy0" counter packets 0 bytes 0
    }
}
# ip link delete dev dummy0
# nft list ruleset
table ip filter {
    chain input {
        type filter hook input priority filter; policy accept;
        iif "lo" counter packets 0 bytes 0
        iifname "dummy0" counter packets 0 bytes 0
        iif 2 counter packets 0 bytes 0
    }
}

The interface being gone, its index value is displayed instead. Likewise, an index value can be used, even in advance, when adding a rule:

# nft add rule ip filter input iif 3 counter
# nft list ruleset
table ip filter {
    chain input {
        type filter hook input priority filter; policy accept;
        iif "lo" counter packets 0 bytes 0
        iifname "dummy0" counter packets 0 bytes 0
        iif 2 counter packets 0 bytes 0
        iif 3 counter packets 0 bytes 0
    }
}
# ip link add name dummy4 type dummy
# ip link add name dummy0 type dummy
# nft list ruleset
table ip filter {
    chain input {
        type filter hook input priority filter; policy accept;
        iif "lo" counter packets 0 bytes 0
        iifname "dummy0" counter packets 0 bytes 0
        iif 2 counter packets 0 bytes 0
        iif "dummy4" counter packets 0 bytes 0
    }
}

Because interface index values are not recycled within the namespace but only increasing, the rule about index 2 is "lost" (it will never have a chance to match anymore). The next created interface, dummy4 got assigned index 3, which now resolves into dummy4 in the ruleset. dummy0 received index value 4, which is not referenced in any iif rule but will still match with the iifname rule.


What is recommended?

My advice:

You should use iif for "stable" interfaces that won't change once created, like physical ethernet interfaces available at boot, before the rule is applied (if using the newer physical interface stable naming convention, that won't change if enumeration order changes), and of course the lo interface. It wasn't shown in the example, but you can still match a list of interfaces with iif too, like iif { lo, dummy4 }. So you still can have a single iif statement matching multiple interfaces.

You should use iifname for dynamic interfaces not known at boot (and at rules creation) but expected to appear later, or to match a group of interfaces with a naming convention when using a wildcard match.

Not very known, to optimize, instead of using wildcards, you could assign a group to each newly created interface, (eg using ip link set dev interface group 99) and then match the interface group with iifgroup rather than iifname + wildcard. But this requires some extra mechanism to group tag newly created interfaces.

You can also use interface named sets, thus keeping generic rules using iif and iifname and changing the contents of the named sets, rather than the rules themselves. Note that the previous wiki link doesn't tell about using interfaces in sets, but it's simply not up to date. More informations about this in my UL SE answer to this question: How do I create a named set of strings in nftables (for interface names)?.

Solution 2:

This is not an answer, just a comment on a previous answer. (My bad reputation won't let me comment.)

But this requires some extra mechanism to group tag newly created interfaces.

I use systemd-networkd to do this^^^. It has a [Link] section where you can set the interface group for each interface. (This is the [Link] section read from .network files by systemd-networkd, not the [Link] section read from .link files by systemd-udevd.)

So I'd set something like this in /etc/systemd/network/something.network

[Match]
MACAddress=my:in:te:rf:ac:e0
...

[Link]
Group=99
...

…and then ip link show group 99 can be used to check that interfaces are (indeed) tagged correctly. (Those without a group are in group 0.) (Also ip link show prints the group numbers.)

This makes it possible to use iifgroup and oifgroup in nftables.conf.