2

Is it possible to construct a nftables map based on timestamp?

Currently I use:

numgen random mod 2 map {
            0: 10.10.10.1,
            1: 10.10.10.2,
        }

But how to convert the random mod 2 expression to (timestamp / 1800) mod 2, i.e. to produce keys for map like 0 and 1 alternating every 30 minutes?

mvorisek
  • 515
  • 1
  • 9
  • 19

1 Answers1

1

It's possible to work around nftables limitations by using a pre-computed lookup named map.

Required:

  • kernel >= 5.4: for meta hour
  • nftables >= 0.9.4 for the newer typeof that can be used instead of type and which appears to be the only syntax supported in a map for meta hour. type meta hour or type hour won't be accepted, even if typeof meta hour is read back as type hour with an older nftables.

nftables cannot do arbitrary arithmetic (or logical, etc.) operations. Currently it's limited to performing a few operations on the left hand side (LHS) on data which must come from the packet path or a few extensions like numgen, and compare it to a constant right hand side (RHS), including getting the RHS value from sets and maps.

There's no way to have nftables compute an arbitrary division in the LHS (using right shift to divide by a power of two works, but see later). And it appears there is also no way to express the result of such computation as a data type accepted as a key in a named map, because the "unqualified" integer type which is often the result of such operations is not a valid key type in a named map, at least currently. For example I'm not sure a numgen expression can be used with a named map, even if it works with an anonymous map.

For this case, Linux kernel 5.4 introduces meta statements related to the packet's timestamp in kernel:

  • Introduce meta matches in the kernel for time, day, and hour commit

Here, meta hour provides the missing required operation "mod 86400" which is related enough to OP's requirement of "timestamp / 1800": gives the time of the packet since the start of the day. Moreover it has a specific type, which allows it to be used in a named map.

As a method similar to loop unrolling to simplify complex operations, values that can't be computed in expressions can be pre-computed and stored in a map table, as long as the type is valid for a named map. This is acceptable because the number of entries will be known in advance, and still limited: for this case, there are 48 half hours in a day.

The mapping can then provide the final result: an IPv4 address.


Example ruleset for a loadbalancer (doing dnat) which forwards all traffic received on wan0 to 10.10.10.1 the first half hour of each hour and to 10.10.10.2 the second half hour:

table ip mytable
delete table ip mytable

table ip mytable {
        map hour2ip {
                typeof meta hour : ip daddr
                flags interval
        }
        chain mylb {
                type nat hook prerouting priority dstnat; policy accept;
                iif wan0 dnat to meta hour map @hour2ip
        }
}

Script generating nftables commands to populate the map hour2ip (use ./script.sh | nft -f -):

#!/bin/sh

for h in $(seq 0 23); do
        for m in 0 30; do
        printf 'add element ip mytable hour2ip { "%02d:%02d:00"-"%02d:%02d:59" : %s }\n' $h $m $h $((m+29)) '$ip'
        done
done |
        sed 's/$ip/10.10.10.X/g' |
        awk '{ printf "%s\n",gensub("X",(NR-1)%2+1,1) }'

which would generate this kind of commands:

add element ip mytable hour2ip { "00:00:00"-"00:29:59" : 10.10.10.1 }
add element ip mytable hour2ip { "00:30:00"-"00:59:59" : 10.10.10.2 }
[...]
add element ip mytable hour2ip { "23:00:00"-"23:29:59" : 10.10.10.1 }
add element ip mytable hour2ip { "23:30:00"-"23:59:59" : 10.10.10.2 }

notes:

  • the local timezone affects the way data of type meta hour (which is a modulo) are sent to or displayed back from the kernel which as usual is only using UTC time. That's why ordering will appear shifted depending on the current timezone, but will behave as expected. You can check the effect of using or not the environment variable TZ=UTC when displaying back nft list map ip mytable hour2ip to understand it better.
  • there's no gap between 00:29:59 and 00:30:00: the timestamp is rounded down, so for example 00:29:59.800ms still matches 00:29:59.

If you prefer to use a mark to get more flexibility (by reusing the mark elsewhere than just the dnat rule, including saving it to a connmark), you could use for example two maps: one to map hour to mark and an other to map mark to an IP address.

To do this replace for example the previous ruleset with:

table ip mytable
delete table ip mytable

table ip mytable {
        map hour2mark {
                typeof meta hour : meta mark
                flags interval
        }

        map mark2ip {
                typeof meta mark : ip daddr
                elements = { 1 : 10.10.10.1, 2 : 10.10.10.2 }
        }

        chain mylb {
                type nat hook prerouting priority dstnat; policy accept;
                iif wan0 meta mark set meta hour map @hour2mark  dnat to meta mark map @mark2ip
        }
}

and replace in the previous script generating the entries:

        sed 's/$ip/10.10.10.X/g' |

with:

        sed 's/hour2ip/hour2mark/;s/$ip/X/g' |

to create the commands to populate hour2mark, like:

add element ip mytable hour2mark { "00:00:00"-"00:29:59" : 1 }
add element ip mytable hour2mark { "00:30:00"-"00:59:59" : 2 }
[...]
add element ip mytable hour2mark { "23:00:00"-"23:29:59" : 1 }
add element ip mytable hour2mark { "23:30:00"-"23:59:59" : 2 }
A.B
  • 11,090
  • 2
  • 24
  • 45