My current setup (which I haven't touched for years) is below. If I were to start from scratch, I'd replace putty/DoT with http-dns-proxy/DoH and reconfigure my firewall rules accordingly, but if it ain't broke, don't fix it...
But, since you like to tinker, here's a bunch of details for you to think about. No claims about "best practice", but it seemed like a pretty solid system when I put it together.
-
dnsmasq using putty as its upstream resolver.
-
putty implementing DoT, it listens on port 5453 and talks upstream to Quad9 on port 853.
-
adblock in a simple setup without any of the "hijacking options" turned on, it simply builds a block list for dnsmasq and does nothing else (https://github.com/openwrt/packages/blob/master/net/adblock/files/README.md).
-
Custom firewall rules to
a) redirect all LAN DNS/53 requests locally,
b) block all LAN requests to DoT/853, and
c) attempt to block all DoH/443.
No.1 is accomplished by (all examples are just snippets without any unrelated settings, of which there are many):
$ cat /etc/config/dhcp
config dnsmasq
list server '127.0.0.1#5453'
list server '::1#5453'
No.2 looks like this:
config stubby 'global'
list listen_address '127.0.0.1@5453'
list listen_address '0::1@5453'
config resolver
option address '9.9.9.9'
option tls_auth_name "dns.quad9.net"
... repeat 'resolver' for all of their ipv4 and ipv6 servers ...
The only thing notable about no.3 adblock config is that I use blocklists that include the Firefox DoH canary domain.
4.a grabs all local lookups and makes sure they go through my local server (i.e., on any devices on the LAN, nslookup something.com google.com does not use google.com, it uses my resolver).
$ nft list ruleset
chain dstnat_lan {
meta l4proto { tcp, udp } th dport 53 counter redirect comment "DNS: Redirect all standard DNS to local server."
4.b is the rule just after the above one
meta l4proto { tcp, udp } th dport 853 counter reject comment "DNS: Block all DoT."
4.c has a couple sets, but the rules are again just after 4.b
set doh_ipv4 {
typeof ip daddr
size 65535
flags dynamic,timeout
timeout 7d
gc-interval 6h
}
set doh_ipv6 {
typeof ip6 daddr
size 65535
flags dynamic,timeout
timeout 7d
gc-interval 6h
}
...
meta l4proto { tcp, udp } th dport 443 ip daddr @doh_ipv4 counter update @doh_ipv4 { ip daddr } reject with icmp port-unreachable comment "DNS: Block IPv4 DoH by selective IPv4 address."
meta l4proto { tcp, udp } th dport 443 ip6 daddr @doh_ipv6 counter update @doh_ipv6 { ip6 daddr } reject with icmpv6 port-unreachable comment "DNS: Block IPv6 DoH..."
The sets are updated nightly by a script that downloads from https://raw.githubusercontent.com/dibdot/DoH-IP-blocklists/master, then updates the elements.