This is what I'm using now:
mkdir -p /usr/share/ipv6-masq
cat > /usr/share/ipv6-masq/update-rules.sh <<"EOF"
#! /bin/sh
ubus call network.interface dump | jq --raw-output '
def clamp(minValue; maxValue):
[minValue, .] | max |
[maxValue, .] | min ;
def toHexString:
. as $input |
if $input < 16 then ("0123456789abcdef" | split("") | .[$input] )
else ($input / 16 | floor | toHexString) + ($input % 16 | toHexString) end
;
def toSuffixMask:
. as $maskLength |
[range(0; 8)] |
map(
# calculate remaining zeros
$maskLength - . * 16 |
clamp(0; 16) |
# calculate set bits
16 - . |
# generate mask part
pow(2; .) - 1 |
toHexString
) |
join(":");
.interface |
map({
name: .device,
prefixList:
(."ipv6-prefix") |
(if . == null then [] else . end) | # this list is sometimes null
map({
address: .address,
mask: .mask,
addressWithMask: "\(.address)/\(.mask)"
})
}) |
(map(.prefixList) | flatten | map(.addressWithMask)) as $prefixList |
map(select((.prefixList | length) == 1)) |
map({ name: .name } + (.prefixList | .[])) |
map(
(
$prefixList - [.addressWithMask] |
# skip rules if there are no other addresses to rewrite
# this happens if there is only one bound interface
select((. | length) > 0) |
"{ " + join(", ") + " }"
) as $saddrList |
" ip6 saddr \($saddrList) oifname \"\(.name)\" snat ip6 to ip6 saddr and \(.mask | toSuffixMask) or \(.address)"
) |
join("\n") |
(
"# make sure that the empty table exists\n" +
"table inet ip6mangle {\n" +
"}\n" +
"\n" +
"delete table inet ip6mangle;\n\n" +
"table inet ip6mangle {\n" +
" chain srcnat {\n" +
" type nat hook postrouting priority srcnat; policy accept;\n" +
. + "\n" +
" }\n" +
"}"
)
' | nft -f /dev/stdin
EOF
chmod u+x /usr/share/ipv6-masq/update-rules.sh
cat > /etc/hotplug.d/iface/21-ipv6-masq <<"EOF"
/usr/share/ipv6-masq/update-rules.sh
EOF
cat > /etc/odhcp6c.user.d/mwan-ipv6-masq <<"EOF"
/usr/share/ipv6-masq/update-rules.sh
EOF
This automatically adjusts the generated firewall rules when the addresses change. Moreover, it handles some cases that occurred during testing like null instead of a empty prefix list and only one uplink that needs no rules instead of rules with an empty source address filter.
I'm using that now and it works reliable.
I'm not sure if that prefix rewriting would be a useful feature for the fw4.