You just have to add an additional condition to not match for this IP address, thus not performing NAT in the end:
iifname "br0" ip saddr != 192.168.22.5 udp dport 53 counter dnat to 192.168.22.5:53
BUT... if both client and DNS server are in the same LAN, then the reply would be done directly from the DNS server to the client. For example with a client at 192.168.22.101 being set to use google's public resolver (with all the involved privacy issues) you'd get this:
query: 192.168.22.101 to 8.8.8.8 -> routed and dnat-ed -> 192.168.22.5
reply: 192.168.22.5 -> bridged (or not seen at all by the router, in both cases) not seen by the ip family rules -> 192.168.22.101
But as 192.168.22.101 expects an answer fro 8.8.8.8, not from 192.168.22.5, it will fail.
So you must also snat the query. Any IP address making packets use the router is fine: router's internal IP (masquerade
), because the router owns this IP, or any Internet IP (using snat
) because they are routed through the router. You could even imagine using some TEST-NET IP for this (eg: 192.0.2.2): the IP address will never be seen outside of the local network anyway. The drawback is that the DNS sever can't identify what IP made the query. Newer kernels (upcoming 5.7 or else 5.8) will have the equivalent of iptables's NETMAP available if this is really important.
Let's use masquerade
here for simplicity.
So inside the equivalent postrouting chain which could be created with something similar to (please adapt to your own naming conventions):
nft add chain ip nat postrouting '{ type nat hook postrouting priority 100; policy accept; }'
the rule would be:
ip daddr 192.168.22.5 udp dport 53 counter masquerade
This won't affect any return traffic initiated from the DNS server, because its conntrack entry will not be in state NEW anymore and this nat rule won't be traversed.
If you want to affect only previously dnat-ed traffic from br0 and not from elsewhere, there are a few options:
check the source IP is from 192.168.22.0/24 and dnat-ed
ip saddr 192.168.22.0/24 ip daddr 192.168.22.5 udp dport 53 ct status dnat counter masquerade
With a recent enough kernel (>= 5.6) matching on the incoming interface (still while in postrouting) would be sufficient instead:
iifname "br0" ip daddr 192.168.22.5 udp dport 53 ct status dnat counter masquerade
or else you can use marks in prerouting and postrouting, if they're not conflicting with other uses:
prerouting:
iifname "br0" ip saddr != 192.168.22.5 udp dport 53 counter set mark 1 dnat to 192.168.22.5:53
postrouting:
mark 1 counter masquerade
And finally, as DNS uses TCP as well as UDP, you should probably also apply all the same using tcp dport 53
.