DNS Leak Despite Forcing OpenDNS in OpenWRT

Setup & Goal:

I am running OpenWRT and attempting to force all DNS queries to OpenDNS while blocking any external DNS servers (e.g., Google 8.8.8.8, Cloudflare 1.1.1.1).

I have configured the firewall with:

  • DNAT rules to redirect all DNS traffic to OpenDNS (208.67.222.222, 208.67.220.220).
  • REJECT rules to block any DNS requests not directed to OpenDNS.
  • IPSet-based filtering to ensure only OpenDNS is allowed.

Problem:

Despite these settings, I am still able to resolve DNS queries using Google (8.8.8.8) and Cloudflare (1.1.1.1) when testing via nslookup from a client machine.
However, HTTP-based DoH services like https://dns.google/resolve are blocked, meaning some part of the setup is working.

Relevant Firewall Rules:

Here are the key parts of my /etc/config/firewall:

root@OpenWrt:~# cat /etc/config/firewall

config defaults
	option input 'REJECT'
	option output 'ACCEPT'
	option forward 'REJECT'
	option synflood_protect '1'

config zone
	option name 'lan'
	option input 'ACCEPT'
	option output 'ACCEPT'
	option forward 'ACCEPT'
	list network 'lan'

config zone
	option name 'wan'
	option input 'REJECT'
	option output 'ACCEPT'
	option forward 'REJECT'
	option masq '1'
	option mtu_fix '1'
	list network 'wan'

config forwarding
	option src 'lan'
	option dest 'wan'

config rule
	option name 'Allow-DHCP-Renew'
	option src 'wan'
	option proto 'udp'
	option dest_port '68'
	option target 'ACCEPT'
	option family 'ipv4'

config rule
	option name 'Allow-Ping'
	option src 'wan'
	option proto 'icmp'
	option icmp_type 'echo-request'
	option family 'ipv4'
	option target 'ACCEPT'

config rule
	option name 'Allow-IGMP'
	option src 'wan'
	option proto 'igmp'
	option family 'ipv4'
	option target 'ACCEPT'

config rule
	option name 'Allow-DHCPv6'
	option src 'wan'
	option proto 'udp'
	option dest_port '546'
	option family 'ipv6'
	option target 'ACCEPT'

config rule
	option name 'Allow-MLD'
	option src 'wan'
	option proto 'icmp'
	option src_ip 'fe80::/10'
	list icmp_type '130/0'
	list icmp_type '131/0'
	list icmp_type '132/0'
	list icmp_type '143/0'
	option family 'ipv6'
	option target 'ACCEPT'

config rule
	option name 'Allow-ICMPv6-Input'
	option src 'wan'
	option proto 'icmp'
	list icmp_type 'echo-request'
	list icmp_type 'echo-reply'
	list icmp_type 'destination-unreachable'
	list icmp_type 'packet-too-big'
	list icmp_type 'time-exceeded'
	list icmp_type 'bad-header'
	list icmp_type 'unknown-header-type'
	list icmp_type 'router-solicitation'
	list icmp_type 'neighbour-solicitation'
	list icmp_type 'router-advertisement'
	list icmp_type 'neighbour-advertisement'
	option limit '1000/sec'
	option family 'ipv6'
	option target 'ACCEPT'

config rule
	option name 'Allow-ICMPv6-Forward'
	option src 'wan'
	option dest '*'
	option proto 'icmp'
	list icmp_type 'echo-request'
	list icmp_type 'echo-reply'
	list icmp_type 'destination-unreachable'
	list icmp_type 'packet-too-big'
	list icmp_type 'time-exceeded'
	list icmp_type 'bad-header'
	list icmp_type 'unknown-header-type'
	option limit '1000/sec'
	option family 'ipv6'
	option target 'ACCEPT'

config rule
	option name 'Allow-IPSec-ESP'
	option src 'wan'
	option dest 'lan'
	option proto 'esp'
	option target 'ACCEPT'

config rule
	option name 'Allow-ISAKMP'
	option src 'wan'
	option dest 'lan'
	option dest_port '500'
	option proto 'udp'
	option target 'ACCEPT'

config zone
	option name 'DNS_Block'
	option input 'REJECT'
	option output 'ACCEPT'
	option forward 'REJECT'

config forwarding
	option src 'DNS_Block'
	option dest 'wan'

config forwarding
	option src 'lan'
	option dest 'DNS_Block'

config ipset
	option name 'opendns'
	option match 'dst_ip'
	option entry '208.67.222.222 208.67.220.220'

config rule
	option name 'Bloquear DNS externo'
	option src 'lan'
	option dest 'wan'
	option dest_port '53'
	option proto 'tcp udp'
	option target 'REJECT'
	option ipset '!opendns'

config rule
	option name 'Bloquear DNS Externo Totalmente'
	option src 'lan'
	option dest 'wan'
	option proto 'tcp udp'
	option dest_port '53'
	option target 'REJECT'

config redirect
	option name 'Forçar OpenDNS'
	option src 'lan'
	option proto 'tcp udp'
	option src_dport '53'
	option target 'DNAT'
	option dest 'lan'
	option dest_ip '192.168.1.1'
	option dest_port '53'

root@OpenWrt:~#

Troubleshooting So Far:

  1. Firewall rules appear active (nft list ruleset | grep -i "reject") confirms DNS blocking rules are in place.
  2. HTTP-based DNS (DoH) is blocked: https://dns.google/resolve fails, and https://one.one.one.one/help/ is redirecting to OpenDNS block page (I have ruled it on blacklist on openDNS).
  3. UDP/TCP DNS queries via nslookup still resolve with external DNS servers (Google & Cloudflare).
  4. Terminal tests:
marcelo@marcelo:~$ nslookup openwrt.org 8.8.8.8
Server:		8.8.8.8
Address:	8.8.8.8#53

Non-authoritative answer:
Name:	openwrt.org
Address: 64.226.122.113
Name:	openwrt.org
Address: 2a03:b0c0:3:d0::1a51:c001

marcelo@marcelo:~$ nslookup openwrt.org 1.1.1.1
Server:		1.1.1.1
Address:	1.1.1.1#53

Non-authoritative answer:
Name:	openwrt.org
Address: 64.226.122.113
Name:	openwrt.org
Address: 2a03:b0c0:3:d0::1a51:c001

marcelo@marcelo:~$
  1. OpenWRT tests:
root@OpenWrt:~# nslookup openwrt.org 8.8.8.8
Server:		8.8.8.8
Address:	8.8.8.8:53

Non-authoritative answer:
Name:	openwrt.org
Address: 2a03:b0c0:3:d0::1a51:c001

Non-authoritative answer:
Name:	openwrt.org
Address: 64.226.122.113

root@OpenWrt:~# nslookup openwrt.org 1.1.1.1
Server:		1.1.1.1
Address:	1.1.1.1:53

Non-authoritative answer:
Name:	openwrt.org
Address: 64.226.122.113

Non-authoritative answer:
Name:	openwrt.org
Address: 2a03:b0c0:3:d0::1a51:c001

root@OpenWrt:~# nft list ruleset | grep -i "reject"
		jump handle_reject
		jump handle_reject
	chain handle_reject {
		meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic"
		reject comment "!fw4: Reject any other traffic"
		tcp dport 53 ip daddr != @opendns counter packets 0 bytes 0 jump reject_to_wan comment "!fw4: Bloquear DNS externo"
		udp dport 53 ip daddr != @opendns counter packets 0 bytes 0 jump reject_to_wan comment "!fw4: Bloquear DNS externo"
		tcp dport 53 counter packets 0 bytes 0 jump reject_to_wan comment "!fw4: Bloquear DNS Externo Totalmente"
		udp dport 53 counter packets 0 bytes 0 jump reject_to_wan comment "!fw4: Bloquear DNS Externo Totalmente"
		jump reject_from_wan
		jump reject_to_wan
	chain reject_from_wan {
		iifname "pppoe-wan" counter packets 439 bytes 22118 jump handle_reject comment "!fw4: reject wan IPv4/IPv6 traffic"
	chain reject_to_wan {
		oifname "pppoe-wan" counter packets 0 bytes 0 jump handle_reject comment "!fw4: reject wan IPv4/IPv6 traffic"
		jump reject_from_DNS_Block
		jump reject_to_DNS_Block
	chain reject_from_DNS_Block {
	chain reject_to_DNS_Block {
root@OpenWrt:~#

Questions:

  • Why is nslookup openwrt.org 8.8.8.8 still succeeding despite my REJECT rules?
  • Is there something missing in my firewall rules that would prevent external DNS from bypassing my setup?
  • Would adding iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to-destination 208.67.222.222 help, or is there a better way in nftables?

Any suggestions would be greatly appreciated!

have you seen https://openwrt.org/docs/guide-user/firewall/fw3_configurations/intercept_dns ?

as long as you don't block the DNS name you're querying, a client will believe it's talking to 8.8.8.8, even if the traffic gets intercepted by the router.

block some FQDN, point it to 0.0.0.0 in your router's hosts file, if the nslookup FQDN 8.8.8.8 fails, you'll know the request was actually intercepted.

1 Like

Maybe you have the DNS hijacking rules which changes the DNS address to the router?

2 Likes

dst lan is worng, it is as if you want to capture everything going to different lan subnet.

config redirect 'dns_int'
        option name 'Intercept-DNS'
        option src 'lan'
        option src_dport '53'
        option proto 'tcp udp'
        option family 'any'
        option target 'DNAT'
        option enabled '0'

Or excessively optimized
drop in /etc/nftables.d/whatever.nft

chain dstnat {
        type nat hook prerouting priority dstnat; policy accept;
        iif $lan_devices meta l4proto {tcp,udp} ct original proto-dst 53 counter redirect to :53
}

If you are concerned about DNS leaks keep the following in mind:

Firefox CLAIMS that this "protects users" but they DON'T tell you that this allows them to get a list of all sites that all firefox users access - which is of course, quite valuable marketing data which they undoubtedly are selling out the back end and making a mint on. That's why there's no easy way for users to turn this off and it's why Firefox hides these queries, to block admins like you who are attempting to force DNS to specific DNS servers by using firewall rules.

Good luck, this marketing data is quite valuable and we are going to see more applications attempting to use tricks like Firefox is using to gain access to it.

Your Intercept/ redirect rule is what is preventing you from seeing the time outs you are expecting, turn off the redirect rule temporarily and you'll see nslookup fail to resolve using e.g. 8.8.8.8. The behavior you are seeing shows that the redirect is working as intended.

2 Likes

You can stuff canary domain, one for ff chromium, other for safari cloud

Updated Firewall Configuration

Here is my current /etc/config/firewall configuration:

root@OpenWrt:~# cat /etc/config/firewall 

config defaults
	option input 'REJECT'
	option output 'ACCEPT'
	option forward 'REJECT'
	option synflood_protect '1'

config zone
	option name 'lan'
	option input 'ACCEPT'
	option output 'ACCEPT'
	option forward 'ACCEPT'
	list network 'lan'

config zone
	option name 'wan'
	option input 'REJECT'
	option output 'ACCEPT'
	option forward 'REJECT'
	option masq '1'
	option mtu_fix '1'
	list network 'wan'

config forwarding
	option src 'lan'
	option dest 'wan'

config rule
	option name 'Allow-DHCP-Renew'
	option src 'wan'
	option proto 'udp'
	option dest_port '68'
	option target 'ACCEPT'
	option family 'ipv4'

config rule
	option name 'Allow-Ping'
	option src 'wan'
	option proto 'icmp'
	option icmp_type 'echo-request'
	option family 'ipv4'
	option target 'ACCEPT'

config rule
	option name 'Allow-IGMP'
	option src 'wan'
	option proto 'igmp'
	option family 'ipv4'
	option target 'ACCEPT'

config rule
	option name 'Allow-DHCPv6'
	option src 'wan'
	option proto 'udp'
	option dest_port '546'
	option family 'ipv6'
	option target 'ACCEPT'

config rule
	option name 'Allow-MLD'
	option src 'wan'
	option proto 'icmp'
	option src_ip 'fe80::/10'
	list icmp_type '130/0'
	list icmp_type '131/0'
	list icmp_type '132/0'
	list icmp_type '143/0'
	option family 'ipv6'
	option target 'ACCEPT'

config rule
	option name 'Allow-ICMPv6-Input'
	option src 'wan'
	option proto 'icmp'
	list icmp_type 'echo-request'
	list icmp_type 'echo-reply'
	list icmp_type 'destination-unreachable'
	list icmp_type 'packet-too-big'
	list icmp_type 'time-exceeded'
	list icmp_type 'bad-header'
	list icmp_type 'unknown-header-type'
	list icmp_type 'router-solicitation'
	list icmp_type 'neighbour-solicitation'
	list icmp_type 'router-advertisement'
	list icmp_type 'neighbour-advertisement'
	option limit '1000/sec'
	option family 'ipv6'
	option target 'ACCEPT'

config rule
	option name 'Allow-ICMPv6-Forward'
	option src 'wan'
	option dest '*'
	option proto 'icmp'
	list icmp_type 'echo-request'
	list icmp_type 'echo-reply'
	list icmp_type 'destination-unreachable'
	list icmp_type 'packet-too-big'
	list icmp_type 'time-exceeded'
	list icmp_type 'bad-header'
	list icmp_type 'unknown-header-type'
	option limit '1000/sec'
	option family 'ipv6'
	option target 'ACCEPT'

config rule
	option name 'Allow-IPSec-ESP'
	option src 'wan'
	option dest 'lan'
	option proto 'esp'
	option target 'ACCEPT'

config rule
	option name 'Allow-ISAKMP'
	option src 'wan'
	option dest 'lan'
	option dest_port '500'
	option proto 'udp'
	option target 'ACCEPT'

config zone
	option name 'DNS_Block'
	option input 'REJECT'
	option output 'ACCEPT'
	option forward 'REJECT'

config forwarding
	option src 'DNS_Block'
	option dest 'wan'

config forwarding
	option src 'lan'
	option dest 'DNS_Block'

config ipset
	option name 'opendns'
	option match 'dst_ip'
	option entry '208.67.222.222 208.67.220.220'

config redirect 'dns_int'
	option name 'Intercept-DNS'
	option family 'any'
	option proto 'tcp udp'
	option src 'lan'
	option src_dport '53'
	option target 'DNAT'
	option dest_ip '208.67.222.222'
	option dest_port '53'

config rule 'dot_fwd'
	option name 'Deny-DoT'
	option src 'lan'
	option dest 'wan'
	option dest_port '853'
	option proto 'tcp udp'
	option target 'REJECT'

# The rule below blocks all external DNS queries, but when enabled, it also blocks all internet traffic (HTTP/HTTPS, nslookup, etc.)
# config rule                                        
#	option name 'Bloquear DNS Externo Totalmente'
#	option src 'lan'                             
#	option dest 'wan'                            
#	option proto 'tcp udp'                       
#	option dest_port '53'                        
#	option target 'REJECT'                       

root@OpenWrt:~#

Issue with the Bloquear DNS Externo Totalmente Rule

If I uncomment the rule at the end of the file, all traffic is blocked—meaning no internet access (HTTP(S) or even command-line tools like nslookup).

/etc/nftables.d/10-custom-filter-chains.nft Content

@brada4, I found the following file: /etc/nftables.d/10-custom-filter-chains.nft:

root@OpenWrt:~# cat /etc/nftables.d/10-custom-filter-chains.nft 
## The firewall4 input, forward, and output chains are registered with
## priority `filter` (0).

## Uncomment the chains below if you want to stage rules *before* the
## default firewall input, forward, and output chains.

# chain user_pre_input {
#     type filter hook input priority -1; policy accept;
#     tcp dport ssh ct state new log prefix "SSH connection attempt: "
# }
#
# chain user_pre_forward {
#     type filter hook forward priority -1; policy accept;
# }
#
# chain user_pre_output {
#     type filter hook output priority -1; policy accept;
# }

## Uncomment the chains below if you want to stage rules *after* the
## default firewall input, forward, and output chains.

# chain user_post_input {
#     type filter hook input priority 1; policy accept;
#     ct state new log prefix "Firewall4 accepted ingress: "
# }
#
# chain user_post_forward {
#     type filter hook forward priority 1; policy accept;
#     ct state new log prefix "Firewall4 accepted forward: "
# }
#
# chain user_post_output {
#     type filter hook output priority 1; policy accept;
#     ct state new log prefix "Firewall4 accepted egress: "
# }

root@OpenWrt:~#

Questions:

  1. @brada4, should I add a chain dstnat in this file as per your previous suggestions? Would that help to improve the firewall?
  2. @tmittelstaedt, I have added a rule to block DoT (DNS over TLS), but I haven't blocked DoH (DNS over HTTPS) yet. Do you have any recommendations on effectively blocking DoH?

Final Thoughts

I am trying to prevent a teenager from bypassing content filtering during school hours.
The goal is to enforce OpenDNS for all DNS queries while blocking access to gaming and VPN/anonymizer services.

I appreciate any guidance on refining these firewall rules to ensure the best possible setup.

Thanks in advance for all suggestions!

It works just fine in Max Protection mode with a custom local DNS server that does DoH.

In fact, it looks like you can turn it off completely:
image

You can install banIP to block doh and vpn's

1 Like

This depends on how smart the teen is. If he understands computers he will make a list of IP addresses of vpn/anonymizer sites and dns filtering will not work. If he's slightly less smart he will pull out his phone and turn on tethering on the phone then connect his laptop to the phone's wifi. If he's less smart than that he will "go over to a friends house to play after school" and surf the forbidden sites there.

This isn't for him. This is for YOU to make you comfortable.

As the father of 2 teens what worked the best was not letting them "work on their schoolwork" in their rooms - they had to do it in the living room, at workstations that I set up against the wall with the screens facing the center of the room. And we went over the schoolwork assignments with them after dinner and they stayed in the living room working on them until they were done, or bedtime, whichever came first.

Definitely it sort of uglified the living room but it was only until they graduated high school.

And for sure, my wife and I hated doing it. It was like going through school all over again. And it ruined our evening time especially since one of them really hated school and was enormously creative in dragging it out.

Interesting I don't recall seeing that in earlier versions. They must have taken a beating from EFF then. I wonder if it actually works. I also wonder if it's controllable via GPO.