Match IPv6 protocol using nftables

In nftables I can use follwoing rule to match IPv4 UDP DNS packets.

ip protocol udp udp dport 53 accept

but IPv6 variant

ip6 protocol udp udp dport 53 accept

fails and nftables says

v0001.nft:39:5-12: Error: syntax error, unexpected protocol
ip6 protocol udp udp dport 53 accept
    ^^^^^^^^

According to examples in documentation ip6 has no protocol field but how can I match these packets based on protocol using nftables? Why protocol is not present there?


TL;DR:

  • Be sure to use an inet family table and not the default ip family, as described in the wiki:

    Both IPv4/IPv6 packets will traverse the same rules. Rules for IPv4 packets won't affect IPv6 packets. Rules for both L3 protocol will affect both.

  • Don't use duplicate rules. The goal of the inet family is to factorize common rules:

    # nft add rule inet mytable myuserchain udp dport 53 accept
    

    will affect both IPv4 and IPv6.

  • If you really want to distinguish them, use meta nfproto XX, don't use ipXX protocol YY:

    # nft add rule inet mytable myuserchain meta nfproto ipv4 udp dport 53 accept
    # nft add rule inet mytable myuserchain meta nfproto ipv6 udp dport 53 accept
    

Also, while OP's example rules don't show this case,

  • If you want to match entirely a whole layer 4 protocol without using an associated specific match (tcp, udp...) likewise, you can do:

    # nft add rule inet mytable myuserchain meta nfproto ipv4 meta l4proto udp
    # nft add rule inet mytable myuserchain meta nfproto ipv6 meta l4proto udp
    

For details, read below.


Your question doesn't include an actual important information: the table this rule is added into.

I'll assume you are really using an inet family table which handles both IPv4 and IPv6 together.

Not like this:

nft add ip table mytable

or this (which is the same):

nft add table mytable

but like this:

nft add table inet mytable

Every command you write later must include inet or it will still attempt to add ip. Like:

nft add chain inet mytable myuserchain

Now because the layout of IPv4 and IPv6 is a bit different, so are their associated keywords in the rule syntax. The IPv6 Fixed Header doesn't include directly what protocol it is carrying. It includes the next header's protocol in "Next Header" and its associated keyword in nftables is nexthdr. But the next header is actually not always the one with the data payload, it could be any other Extension Header, and you could have an IPv6 UDP packet where the IPv6's Fixed Header's next header value is not UDP.

So while IPv4 would work correctly like this:

nft add rule inet mytable myuserchain ip protocol udp udp dport 53 accept

its direct IPv6 equivalent would appear to work, but not always with:

nft add rule inet mytable myuserchain ip6 nexthdr udp udp dport 53 accept

because it wouldn't match packets which include Extension Headers. Of course this would be a nightmare for the end user of nftables to have to handle this considering also there can be multiple extension headers.

As the system already knows all this, the information you actually care about: IPv4 vs IPv6 (not the fact that it's IPv4 UDP vs IPv6 UDP, which is a syntax workaround to get ip and ip6 accepted: UDP is handled separately in the next (final) match keyword udp), is available as meta information rather than packet's content information:

nfproto <protocol>        

meta nfproto ipv4
meta nfproto != ipv6
meta nfproto { ipv4, ipv6 }

You were just trying to solve a syntax workaround when it wasn't the one to be used (it would check twice for UDP when checking once is enough). The correct syntax for both cases should be:

nft add rule inet mytable myuserchain meta nfproto ipv4 udp dport 53 accept
nft add rule inet mytable myuserchain meta nfproto ipv6 udp dport 53 accept

The same exists for the Layer 4 protocol in case there's no specific match used later:

l4proto <protocol>        

meta l4proto 22
meta l4proto != 233
meta l4proto 33-45
meta l4proto { 33, 55, 67, 88 }
meta l4proto { 33-55 }

here 22 doesn't mean port 22, it means protocol 22, aka xns-idp.

Note that using more specific matches already implictly include those meta filters in the resulting bytecode (which can be checked with nft -a --debug=netlink list ruleset). They become explictly needed only when there's no need to filter further.

That was about the syntax. Of course the whole goal of the inet table is to avoid duplication of rules. So in this case, both should be replaced simply with:

nft add rule inet mytable myuserchain udp dport 53 accept

which will handle both IPv4 and IPv6: because that's what this table family exists for.

Note: protocol is probably best used in the bridge family table case.