The feature doesn't exist ... yet?
Currently, nftables can only use one register (in its virtual state machine): it applies bitwise operations on the left hand side (LHS) to compare the result with a constant or a set on the right hand side (RHS). It cannot use two variable operands (meaning: both from packet) in LHS and RHS.
There's WIP about improving this in these patch series (they are not accepted yet): nf-next libnftnl nftables:
Currently bitwise boolean operations (AND, OR and XOR) can only have
one variable operand. [...] We add support for evaluating these
operations directly in kernel space on one register and either an
immediate value or a second register.
This will probably still take additional iterations and some time before it is made available (the idea has been floating around since 2019 and maybe earlier, but it's still not available). Once done, one can image that OP's precise rule:
ip6 saddr & ffff:ffff:ffff:ffff:: == ip6 daddr & ffff:ffff:ffff:ffff:: tcp dport 8080 accept
would work as expected.
Workaround
That said, what could be done today?
One can use an external tool that reacts to the host changing address and updates a set so it has the host's IPv6 network/netmask as content. A set is fine to use as RHS. In the end it's not dynamic as in variable operands but it's still dynamic enough for the need.
Add a set to OP's ruleset (I won't put a full ruleset, this is beyond the scope of the question. Just remember that usually ct state related,established accept
as well as allowing the loopback interface should be present and that a table of family inet
rather than ip6
could merge some rules for IPv4 plus IPv6 when relevant).
ip6firewall.nft
:
table ip6 firewall #for idempotence
delete table ip6 firewall #for idempotence
table ip6 firewall {
set myip6net {
typeof ip6 saddr
flags interval
}
chain acceptmyip6netsrc {
ip6 saddr @myip6net counter accept
}
}
This could be called from a base input chain with:
tcp dport 8080 jump acceptmyip6netsrc
I'll assume the network interface name is eth0
. The script below uses a very simple event loop with ip monitor
and will keep running: use it as a service, not in crontab. It will trigger whenever an address event happens (most of the time uselessly when a Router Advertisement that refreshes timeouts and changes nothing happens). ip monitor
's output isn't easy to parse, so just ignore it and use ip -json addr
to retrieve actual values. The script has room for improvement but does the job.
Requires tools which are usually available in distributions:
jq
for efficient JSON parsing
netmask
(handles correctly any abbreviation of an IPv6 address, so 2001:db8::4:5:6:7:8/64
is correctly transformed into 2001:db8:0:4::/64
).
updatemyip6net.sh
:
#!/bin/sh
{ echo init; ip -6 -o monitor address dev eth0; } | while read dummy; do
myip6addr=$(ip -json -6 addr show dev eth0 scope global |
jq -j '.[].addr_info[] | if .local then .local,"/",.prefixlen,"\n", halt else empty end'
)
myip6net=$(netmask $myip6addr)
nft -f - <<EOF
flush set ip6 firewall myip6net
add element ip6 firewall myip6net { $myip6net }
EOF
done
Above,
ip -json -6 addr show dev eth0 scope global | jq -j '.[].addr_info[] | if .local then .local,"/",.prefixlen,"\n", halt else empty end'
is quite long, but replacing it with the simpler:
ip -j -6 route get 2001:4860:4860::8888 | jq -r '.[].prefsrc'
doesn't get the netmask, and hardcoding /64 everywhere should be avoided.