[Tutorial] DNS-over-TLS with dnsmasq and stubby (no need for unbound)

Just change the DNS config for the WAN interfaces like shown below.
Then DNS resolution of the router will also go through dnsmasq -> stubby if it is available.
If not DNS requests will go to the other DNS servers (in this example also cloudflare) so the router can sync time etc. during boot until dnsmasq and stubby are running.

config interface 'wan'
	option peerdns '0'
	option dns '127.0.0.1 1.1.1.1 1.0.0.1'

config interface 'wan6'
	option peerdns '0'
	option dns '::1 2606:4700:4700::1111 2606:4700:4700::1001'
2 Likes

Yes, I recommend this if you install stubby to ram, I have a similar setup myself.
If you install normally, it's safer to override all DNS network setting with the loopback connection as the static DNS server.

I will not recommend this setting. There is a great possibility of PLAIN dns request made directly to 1.1.1.1 & 1.0.0.1 without any TLS encryption.

Do you have a different recommendation then? The only non encrypted DNS requests in this setup will the one made by the router itself if it can't reach stubby, however, this situation is required if you are using the install to RAM option.

Now, if you install stubby normally, in a persistent way, I suggest that you set 127.0.0.1 and ::1 as the only static DNS servers in the network configuration.

I think I can confirm that there's an issue with cf's https://1.1.1.1/help website, I've setup a stubby on a Windows 10 VM to test that site with DNSSEC on and off, so a setup that is completely different in terms of operating system, except for stubby.

In the default setup of stubby I only changed the upstream servers to be only cloudflare, turned off round robin, and tried with DNSSEC on and off and the results are the same, when DNSSEC is on the site fails to recognize the connection to 1.1.1.1 and DoT, when it's off it consistently recognizes, even thou in both cases it displays AS name as Cloudflare.

This is what I do

On "/etc/config/dhcp"

#	option resolvfile '/tmp/resolv.conf.auto'  ## --- Make sure you disable (apply "#" in front) this to ignore isp's supplied dns
#	option cachesize '1024' 		## --- If your router have a big RAM, you can enable this option with dnsmasq-full installed
	option noresolv '1' 			## --- This will prevent the use of isp's dns
	list server '/pool.ntp.org/1.1.1.1'	## --- Your router date & time must be correct in order to have sucessful tls init
	list server '127.0.0.1#5453'		## --- Default stubby service port

On "/etc/config/network"

config interface 'wan'
	option ifname 'eth1'
	option proto 'pppoe'
	option username 'username'
	option password 'password'
	option peerdns '0' 	## --- This will prevent the use of isp's dns
	option dns '127.0.0.1' 	## --- this will make sure the router itself can do "ping google.com"

If you have extra ROM+RAM you can assign some of RAM capacity for dns cache.
Bonus: How to install dnsmasq-full

cd /tmp 
opkg update
opkg download dnsmasq-full 			## --- 1st we need to download dnsmasq-full
opkg install dnsmasq-full 			## --- Installation will fail as dnsmasq-full files are conflicting with exisiting dnsmasq
opkg remove dnsmasq 				## --- You will have no access to internet after this
opkg install /tmp/dnsmasq-full(press Tab) 	## --- Internet access will be restored after this

Bonus: You don't have to install ca-bundle/ca-certificates if you don't use cloudflare's (1.1.1.1)
Just make a duplicate of the file "/etc/stubby/stubby.yml.default", pick the desired resolver then rename the file to "stubby.yml"

To test:

  1. Go to https://www.dnsleaktest.com/ or
  2. On your router, go to status/realtime/connections
    make sure ALL dns requests :53 from clients are ONLY goes into the router (NOT directly to internet)
    and dns requests via tls :853 is ONLY goes to configured resolver or
  3. Analyse your wan outgoing port :53 with wireshark

The point of this is to have working DNS resolution for the router itself during boot and shortly after when dnsmasq and stubby aren't running yet (e.g. downloading blocklists for adblock, time sync, opkg stuff, ...).

DNS requests of the connected clients will still be going through dnsmasq and stubby, the settings are for the WAN interfaces only.

As soon as all services are running the requests made by the router will also go through dnsmasq and stubby. Plain requests will only be made if dnsmasq is not running but only for the router itself not the clients. The requests made by the clients will simply fail.

You can ofcourse also assign certain domains like "pool.ntp.org" to a certain resolver (like you did) to ensure a smooth boot process. But if you do that they will always be plain DNS requests since dnsmasq will not use stubby.

I also had issues with adblock failing to download some lists if it had to wait for dnsmasq and stubby to be running. So i switched to this config it at seems to work fine so far.

DNS requests made by the router during boot -> unencrypted
DNS requests made by the clients during boot -> failing
DNS requests made by the router when dnsmasq/stubby is up -> encrypted
DNS requests made by the clients when dnsmasq/stubby is up -> encrypted

You may also try to tweak the starting priorities of the services a bit to ensure dnsmasq / stubby are running earlier. But I don't see a need to mess with that.

If you really want to make sure all requests from within your LAN will go through the router, you can intercept connections to port 53 with iptables and redirect them (or block them outright).

You right about dns leak during ntp requests.
But I failed to mention that I reduced it by setting
"/etc/config/system"

config timeserver 'ntp'
	option enabled '1'
	option enable_server '1'
	list server 'pool.ntp.org'

and "/etc/firewall.user"
iptables -t nat -A PREROUTING -p udp --dport 123 -j REDIRECT --to-ports 123

These will force lan clients to use ONLY the router provided ntp service

I've tried it my self, if I do it as above.

One of the example of dns leak is when we open up http://your-router-ip/cgi-bin/luci/admin/status/realtime/connections
Some dns requests made (round robin?) are PLAIN [one.one.one.one:53]. I think its because our router's luci try to resolve all IPs displayed to be presentable as domain names.

Yes, it's likely luci/netstat doing name resolving, which is the router itself making these requests and not clients of the network.

I believe option dns '127.0.0.1 1.1.1.1 1.0.0.1' works as this, first tries 127.0.0.1 if it fails or timeouts then tries 1.1.1.1, etc. However this sequence does not affect the clients, clients only go through dnsmasq if they do not set static DNS servers (127.0.0.1 also goes trough dnsmasq).

https://www.dnsleaktest.com/ shows there is no leakage on clients.

About routing DNS requests, in the future browsers and apps will likely have built-in DNS-over-HTTPS which will make DNS lookup traffic undistinguishable from web traffic, also unreroutable, but totally encrypted.

Yeah not a fan of every application bringing their own solution for DNS resolution so I hope it won't be the default and can be disabled.

DNS over HTTPS is also planned for stubby 0.3 so we'll have that covered too: https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Daemon+-+Stubby#DNSPrivacyDaemon-Stubby-KeyFeatures

Maybe we'll even have operating system support by default for DNS over TLS / DNS over HTTPS at some point.

Yes, I'm looking forward to implement DoH with stubby and maybe write a turorial on that too.

This routes it to cf, but not over TLS from what I can tell. i.e., If i connect to my router via ssh -D 1080 router for the sock proxy, the dns goes to regular cf. dnsleaktest detects several servers, although all of them cf (which is how regular cf behaves). With dns over tls, dnsleaktest detects only 1 server. Also, 1.1.1.1/help detects the connection to cf, but not DoT.

Then this might not work for your use case with the SSH proxy.

Try setting only '127.0.0.1' and '::1' as your DNS servers on the WAN infterfaces and remove the ones form your ISP (option peerdns '0').

This should make sure that all DNS requests made by the router itself go through dnsmasq / stubby.

The downside of this is that the router will be unable to to perform DNS resolution until dnsmasq and stubby are running.

I am using the config I posted myself and "dnsleaktest" + "1.1.1.1/help" show the correct results.
But I am not using the router as SSH proxy.

cloudflare's support for dnssec seems problematic. It seems to break for some sites. Are any of you able to do nslookup login.comcast.net from a client ? I get a SERVFAIL with cf+dnssec. It works with quad9+dnssec, and cf without dnssec.

[user@v-fed-1 ~]$ dig login.comcast.net +dnssec @1.1.1.1 +short
login.g.comcast.net.
CNAME 5 3 7200 20180830172144 20180823141644 26550 comcast.net. KDBvpzYCCFvgJbLhpcoQUHUGlUAiPprwfsKSQnrOKZ12e7JtInUxIRYz VCXXv2Eh1L+VQMrBaSCLt1voaFtEVu0Jheo2W7Ybw5ChK04YszhlRPqy 2Dh7YN0VZ7ASfKKOkmiPQRlueGbR16+omGoG7Wj24VhDday6XncM99bw GPg=
76.96.69.84

[user@v-fed-1 ~]$ dig login.comcast.net +dnssec @9.9.9.9 +short
login.g.comcast.net.
CNAME 5 3 7200 20180830172144 20180823141644 26550 comcast.net. KDBvpzYCCFvgJbLhpcoQUHUGlUAiPprwfsKSQnrOKZ12e7JtInUxIRYz VCXXv2Eh1L+VQMrBaSCLt1voaFtEVu0Jheo2W7Ybw5ChK04YszhlRPqy 2Dh7YN0VZ7ASfKKOkmiPQRlueGbR16+omGoG7Wj24VhDday6XncM99bw GPg=
76.96.69.84

[user@v-fed-1 ~]$ dig login.comcast.net +dnssec @8.8.8.8 +short
login.g.comcast.net.
CNAME 5 3 7200 20180830172144 20180823141644 26550 comcast.net. KDBvpzYCCFvgJbLhpcoQUHUGlUAiPprwfsKSQnrOKZ12e7JtInUxIRYz VCXXv2Eh1L+VQMrBaSCLt1voaFtEVu0Jheo2W7Ybw5ChK04YszhlRPqy 2Dh7YN0VZ7ASfKKOkmiPQRlueGbR16+omGoG7Wj24VhDday6XncM99bw GPg=
76.96.69.84

This is over regular dns. I meant client using dns over tls as described in this thread. It seems to fail when cf is the server, and stubby has dnssec_return_status: GETDNS_EXTENSION_TRUE. Change your server to 192.168.1.1 in your dig or nslookup

[user@v-fed-1 ~]$ dig login.comcast.net +dnssec @172.16.9.1 -p 5453 +short
login.g.comcast.net.
CNAME 5 3 7200 20180830172144 20180823141644 26550 comcast.net. KDBvpzYCCFvgJbLhpcoQUHUGlUAiPprwfsKSQnrOKZ12e7JtInUxIRYz VCXXv2Eh1L+VQMrBaSCLt1voaFtEVu0Jheo2W7Ybw5ChK04YszhlRPqy 2Dh7YN0VZ7ASfKKOkmiPQRlueGbR16+omGoG7Wj24VhDday6XncM99bw GPg=
76.96.69.84
root@gw1:~# cat /etc/stubby/stubby.yml
resolution_type: GETDNS_RESOLUTION_STUB

dns_transport_list:
  - GETDNS_TRANSPORT_TLS

tls_authentication: GETDNS_AUTHENTICATION_REQUIRED

tls_query_padding_blocksize: 128

edns_client_subnet_private: 0

round_robin_upstreams: 1

idle_timeout: 10000

listen_addresses:
  - 127.0.0.1@5453
  - 0::1@5453
  - 172.16.9.1:5453

upstream_recursive_servers:
  - address_data: 2606:4700:4700::1111
    tls_auth_name: "cloudflare-dns.com"
  - address_data: 2606:4700:4700::1001
    tls_auth_name: "cloudflare-dns.com"

  - address_data: 1.1.1.1
    tls_auth_name: "cloudflare-dns.com"
  - address_data: 1.0.0.1
    tls_auth_name: "cloudflare-dns.com"

dnssec_return_status: GETDNS_EXTENSION_TRUE

What’s important in this case is if the server response is NOERROR or SERVFAIL. In case of servfail which the common DNSSEC reply in case of validation fail, you aren’t able to open the site.