I think I have this working well now by installing only unbound, unbound-control and luci-app-unbound. No stubby, no getdns, no odhcpd.
I first tried according to the Cloudflare blog above, replacing dnsmasq with unbound+odhcpd, but burned a whole evening trying and failing to get it to resolve local DHCP client names. I know my way around dnsmasq pretty well, and really didn't like the idea of having to learn odhcpd WHILE fussing with how UCI/LUCI map their options into odhcpd configuration.
Then I saw the "Serial dnsmasq" section of the unbound+luci documentation here:
Once I uninstalled odhcpd and restored dnsmasq, local name resolution started working again and the parameters on the
Network > DHCP and DNS page in luci of course began working as advertised again.
Then I configured DNSmasq to use unbound as its upstream as described on that github link. unbound listens on 1053, dnsmasq on 53, and LAN resolution goes
client -> dnsmasq(:53) -> unbound(:1053) -> HTTPS encryption -> cloudflare
I also had to change the WAN interface advanced settings to disable "Use DNS servers advertised by peer" and specified 127.0.0.1 (dnsmasq) as the local resolver (and for the wan6 interface the same with loopback address
No more plaintext DNS queries are going out, so I took the added measure of adding a couple firewall rules (
Network > Firewall > Custom Rules) to prevent leaks:
#Block plaintext DNS from router
iptables -I OUTPUT -o eth1 -p udp --dport 53 -j REJECT
iptables -I OUTPUT -o eth1 -p tcp --dport 53 -j REJECT
#Block plaintext DNS from other interfaces
iptables -I FORWARD -o eth1 -p udp --dport 53 -j REJECT
iptables -I FORWARD -o eth1 -p tcp --dport 53 -j REJECT
I like the way it's working so far. I didn't have to monkey with stubby or odhcpd, and won't have to worry about the
DNS and DHCP configuration parameters functioning differently than the typical out-of-the-box lede/dnsmasq experience.
I might write up a step-by-step if there's enough interest? What I ended up doing felt simpler than the cloudflare blog post and a LOT simpler than the process in the original post here.