I have a lot of subnets 10.x.y.0/24, where 0 ≤ x ≤ 50 and 0 ≤ y ≤ 20.

I would like to allow traffic between 10.x.y.0/24 and 10.z.y.0/24 for every value of (x, y, z).

I could do a script using the nft executable, but I was wondering if it was possible to do it using a single rule and binary operations.

I am able to "extract" the "y byte" of the address using ip saddr & but when I try to use the == operator, nftables tells me that the syntax is invalid:

$ cat /etc/nftables.conf
# [...]
        ip saddr & == daddr & accept
# [...]
$ sudo nft -f /etc/nftables
/etc/nftables.conf:113:27-31: Error: syntax error, unexpected daddr
        ip saddr & == daddr & accept

I've tried a few variations, but none of them seems to work, and the NFTables wiki never compares different fields of a packet.

Is there a way to do that?

1 Answer 1


I don't think that currently nftables supports an arbitrary non-constant expression on the right hand side of a comparison. I don't know if this limit is on the userspace side on how to generate the adequate bytecode, or also if kernel side's bytecode isn't able to handle such thing. There can be a constant or a set/map lookup, but not much more. This might evolve in the future.

Anyway, I propose an alternate workaround solution using a set with concatenations as lookup table. Still using your specific netmask operations, this will require only y elements (21) which is much better than an exhaustive ruleset or its equivalent complete set version (which would still be faster than simple rulesets because it's hashed): they would probably require more rules or elements for an exhaustive match list.

I'll use the table ip myfilter and its type filter hook forward chain ip myfilter forward in the examples, please adapt:

nft add table myfilter
nft add chain ip myfilter forward '{ type filter hook forward priority 0; policy drop; }'

Add a set made of a concatenation of two IP addresses types:

table ip myfilter {
    set same-y {
        type ipv4_addr . ipv4_addr

created with:

nft add set ip myfilter same-y  '{ type ipv4_addr . ipv4_addr; }'

The lookup will match a concatenation of two IP addresses. Now it's still possible to apply operators on the left hand side of the comparison, so your custom netmask operators will still be used: just provide the modified source address and modified destination address and if there's a matching element in the set, the result will be true (and the rule will run the accept statement).

nft 'add ip myfilter forward ip saddr & . ip daddr & @same-y accept'

Again: the difference making it work is that here, both expressions depending on the packet's content are on the left hand side of the comparison, and are thus allowed.

Now populate the set with entries of type 0.0.y.0 : 0.0.y.0 to complete the logical comparison's part that can't be done purely during the packet path traversal, for example with this shell loop:

for i in $(seq 0 20); do printf 'add element ip myfilter same-y { 0.0.%d.0 . 0.0.%d.0 }\n' $i $i; done | nft -f -

which will add 21 elements in @same-y: ., .,
[...] .

If the network expands and receives sub-networks of type 10.x.21.0/24, it just requires one element to be added, and this can still be done at runtime.

It's possible to transform the set above into a verdict map in case you need a few exceptions to the filter while keeping it generic. Most verdict elements would end with : accept and the exceptions would be like . : jump exception-net-15 (with such user chain also having to be defined in the same ruleset).

with this verdict map:

table ip myfilter {
    map accept-same-y {
        type ipv4_addr . ipv4_addr : verdict

which should be populated with this kind of elements: . : accept, . : accept,
[...] . : accept

and be used along this rule where vmap will execute a verdict from the map if found:

ip saddr & . ip daddr & vmap @accept-same-y
  • Brillant solution Commented Jun 6, 2020 at 23:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .