I have been searching, but cannot seem to find an answer to my specific issue. I have the following rule today under prerouting: iifname "br0" udp dport 53 counter dnat to

However, I have one issue, the IP address is also on "br0". So the question at hand is, how can I make nftables ignore this IP and allow it access port 53 on the net (this is my pihole), but redirect all other port 53 udp data to it?

Appreciate your replies!


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 != udp dport 53 counter dnat to

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 being set to use google's public resolver (with all the involved privacy issues) you'd get this:

query: to -> routed and dnat-ed ->

reply: -> bridged (or not seen at all by the router, in both cases) not seen by the ip family rules ->

But as expects an answer fro, not from, 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: 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 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 and dnat-ed

    ip saddr ip daddr 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 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:


    iifname "br0" ip saddr != udp dport 53 counter set mark 1 dnat to


    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.

  • Thanks for a really through reply. It now solves the issues related to br0. However, I still cannot seem to properly pass traffic destined for properly when it is coming from other adapters. It is NATed not "just routed", with the result I cannot see where the traffic is originating from on the DNS, it all appears to come from How would I have to modify the setup below to only counter and NAT port 53 if it is not destined for pastebin.com/FycjXefv – Henrik_er May 14 '20 at 19:06
  • To be clear, quoting from my answer: "If you want to affect only previously dnat-ed traffic from br0 and not from elsewhere, there are a few options". There are 3 choices to pick from, pick one (but choice 2 is probably not available). – A.B May 14 '20 at 19:24

Traffics originated from the host itself doesn't get matched by iifname $outgoing_interface (but iifname lo), so your concern doesn't exist.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.