published on 19 December 2023
I’m currently using systemd-networkd, systemd-resolved and iwd together with my ad blocking DNS-over-TLS server for my networking, and it works quite well for most networks, even the pesky 802.1x types such as eduroam. But now picture this: you’re at a hotel, a public place or in a train and you want to use the free Wi-Fi but you can’t seem to get online because your custom config using some minimal network configuration doesn’t work, and you’re on the brink of just installing NetworkManager and rolling with the defaults. Fear not! This is how to make an exception for your DNS-over-TLS config for certain networks.
But first, let’s talk about why we need to do this.
First you need to know that Wi-Fi access points always provide connected devices with a default DNS server over DHCP, which is usually your access point itself. Using this default DNS server, the AP can block all traffic to external sites by just making every domain name resolve to the captive portal login page (it then keeps track of your login status using your device’s MAC address). Note that there are also captive portals using HTTP traffic interception instead of DNS redirection, however, I found that these aren’t that common anymore, because they have issues with HTTPS traffic, which is most traffic nowadays.
Your config files probably look something like this if your setup is similar to mine:
/etc/systemd/network/20-wlan0.network
[Match]
Name=wlan0
[DHCPv4]
# dont use dns servers advertised by dhcp server (router)
UseDNS=no
[DHCPv6]
# dont use dns servers advertised by dhcp server (router)
UseDNS=no
/etc/systemd/resolved.conf
[Resolve]
# insert your dns over tls server ip and domain name below, keep the '#'
DNS=1.2.3.4#dnsovertls.resolver.tld
# only use provided dns server, no fallback
FallbackDNS=
# use dns for all domains
Domains=~.
DNSOverTLS=yes
ReadEtcHosts=yes
Cache=yes
LLMNR=no
MulticastDNS=no
# hacky way to only make resolved listen on udp 127.0.0.1:53
DNSStubListener=no
DNSStubListenerExtra=udp:127.0.0.1:53
Note that by default iwd does network configuration such as handling DHCP itself, but I chose to let systemd-networkd do this, because I’m not sure if it’s possible to do such high-level config distinctions (Wi-Fi access point names, etc.) in the iwd config. So here’s my iwd config, too:
/etc/iwd/main.conf
[General]
# dhcp is done by systemd-networkd
EnableNetworkConfiguration=false
# random mac address
AddressRandomization=once
AddressRandomizationRange=full
[Network]
NameResolvingService=systemd
We need to create a config file that overrides the default interface settings shown above. To do this, just create the following file:
/etc/systemd/resolved.conf
[Match]
Name=wlan0
SSID="free_wifi"
[Network]
DHCP=yes
# because the dns advertised by routers is not encrypted
DNSOverTLS=no
# i dont expect hotspots to support dnssec
DNSSEC=no
# reset options set in global systemd-resolved conf
DNS=
Domains=~.
[DHCPv4]
UseDNS=yes
[DHCPv6]
UseDNS=yes
And exclude the SSID in your default config file:
/etc/systemd/network/20-default.network
[Match]
Name=wlan0
SSID=! "free_wifi"
If you want to connect to a new open Wi-Fi, just add its SSID to the two config files, explicitly including it in one file and explicitly excluding it in the other, like so:
/etc/systemd/network/20-default.network
[Match]
Name=wlan0
SSID=! "free_wifi_2"
/etc/systemd/network/30-captiveportal.network
[Match]
Name=wlan0
SSID="free_wifi_2"
Then restart all the relevant services:
sudo systemctl restart iwd systemd-resolved systemd-networkd
And go to http://captive.apple.com, which should be intercepted by the captive portal and redirect to its login page.
You should now also be able to see that you’re using a different DNS server when you run resolvectl status
.
Now that you’re authenticated to the captive portal, you should be able to change back your DNS server using this command:
sudo systemd-resolve --interface wlan0 --set-dns="1.2.3.4#dnsovertls.resolver.tld" --set-domain "~." --set-dnsovertls=true
To verify this, run sudo ngrep port 853
(853 is the port used for DNS-over-TLS) and see the encrypted DNS traffic to the server address you used.