My current Linux distributions (Ubuntu 23.04 and the Ubuntu-derived Pop!_OS 22.04) use NetworkManager for managing connections, and
systemd-resolved for resolving DNS queries. I’ve set up Cloudflare’s public DNS service with DoT (DNS over TLS) support twice… and I don’t really have a solid conclusion. Is one “better?” 🤷🏻
- Per-connection mode with NetworkManager only
- Globally with systemd-resolved / NetworkManager
- Useful background info
Per-Connection Mode with NetworkManager
First, I set the IP addresses according to instructions via the NetworkManager GUI, although I could have used the command line as well. The latter would have been like:
$ nmcli c modify id "AirTubes WiFi" \ ipv4.dns 184.108.40.206,220.127.116.11
And similar for the IPv6 addresses with the
ipv6.dns setting. The “AirTubes WiFi” is the connection name, visible as the “NAME” column of
nmcli c show. Using
nmcli c is short for
nmcli connection. For brevity, this post will always use the abbreviation.
The laptop also has an outbound firewall, so I added a rule to allow port 853 out:
$ sudo ufw allow out \ proto tcp to any port 853 \ comment "DNS over TLS"
Finally, activating DNS over TLS required the NetworkManager command line (this setting is not available in other interfaces):
$ nmcli c modify id "AirTubes WiFi" \ connection.dns-over-tls opportunistic
I chose “opportunistic” mode for DoT instead of “yes”, reasoning that for the most part, I’m at home. I’ll forget about this entire thing if I’m out, and that’s when I need the internet to just work, even if the network is blocking port 853.
Only then did I realize the flaw in per-connection settings: these would not apply if I were actually out. There’s no way a public network would be called “AirTubes WiFi,” so NetworkManager would consider it a different connection.
I put the nmcli settings back to their defaults:
$ nmcli c modify id "AirTubes WiFi" \ connection.dns-over-tls "" $ nmcli c modify id "AirTubes WiFi" \ ipv4.dns ""
And one more time, for
Then, I went for global mode.
Globally with systemd-resolved / NetworkManager
The firewall settings to allow port 853 out, above, remained in place.
This approach uses a drop-in file for
systemd-resolved, configuring it to do DNS over TLS with CloudFlare by default. The file looks like this, without comments, and with the DNS line abbreviated:
[Resolve] DNS=18.104.22.168#cloudflare-dns.com ... ... DNSOverTLS=yes
In fact, the DNS line is from the example value for CloudFlare, given in
/etc/systemd/resolve.conf. It was abbreviated here for the blog to display better on phones.
Also note the capitalization here.
DNSoverTLS with lowercase
O is wrong, and will be ignored.
Once it’s ready, the file gets copied into place:
$ sudo mkdir /etc/systemd/resolve.conf.d $ sudo cp local-resolve.conf \ /etc/systemd/resolve.conf.d
I changed NetworkManager from fully “Automatic” mode to “Automatic (addresses only)”, then left the DNS Servers configuration blank. It appears that the command-line method to do this (if necessary) is:
$ nmcli c modify id "AirTubes WiFi" \ ipv4.ignore-auto-dns yes $ nmcli c modify id "AirTubes WiFi" \ ipv6.ignore-auto-dns yes
Then, it was a matter of reloading the configurations:
$ sudo systemctl restart systemd-resolved.service $ nmcli c down id "AirTubes WiFi" $ nmcli c up id "AirTubes WiFi"
After that, using tcpdump to show traffic to port 853 started reporting packets captured! I was in business.
The limitation of this approach is that I have to remember to set up any new network connections this way: addresses only, no DNS. Otherwise, NetworkManager will tell
systemd-resolved what DNS settings it wants to use, and they will be applied to the connection.
Useful Background Info
As mentioned in passing above, a NetworkManager setting can be reverted to its default value by setting the empty string:
$ nmcli c modify id "AirTubes WiFi" \ connection.dns-over-tls ""
A detailed listing of all of a connection’s settings can be seen with:
$ nmcli c show id "AirTubes WiFi" | less
More information about the settings are also available via man page.
$ man nm-settings-nmcli
Getting somewhat unrelated, there is a command to search man pages for keywords. This is how I discovered that there is an
nmtui (text user interface) command, which gives a terminal-based menu similar to the GUI. Anyway, to search the man pages for NetworkManager:
$ man -k NetworkManager
Firewall rules can be restricted by IP. It was straightforward enough to do IPv4, but for IPv6, the address needs to be “complete” (note the double colon):
$ sudo ufw allow out proto tcp \ to 22.214.171.124/15 port 853 \ comment 'CloudFlare DoT' $ sudo ufw allow out proto tcp \ to 2606:4700:4700::/112 port 853 \ comment 'CloudFlare DoT v6'
I chose the network masks so that one rule would cover both respective addresses, without allowing the port to the entire Internet. (Discussion of network masks and CIDR notation for them—the /15 and /112—are out of scope for this blog post.)
systemd-resolved for name resolution, the status can be inspected with its own command:
$ resolvectl status
Here, the output should say things like
+DNSOverTLS, and there shouldn’t be any per-link overrides of the “Global” section. It should be more like (reformatted to reduce width):
Global Protocols: -LLMNR -mDNS +DNSOverTLS DNSSEC=no/unsupported resolv.conf mode: stub DNS Servers 126.96.36.199#cloudflare-dns.com 188.8.131.52#cloudflare-dns.com 2606:4700:4700::1111#cloudflare-dns.com 2606:4700:4700::1001#cloudflare-dns.com Link 2 (enp2s0) Current Scopes: none Protocols: -DefaultRoute +LLMNR -mDNS +DNSOverTLS DNSSEC=no/unsupported Link 3 (wlx........) Current Scopes: none Protocols: -DefaultRoute +LLMNR -mDNS +DNSOverTLS DNSSEC=no/unsupported
I hope that covers it!