This topic bugged me for a very long time! In Germany there a quite a lot ISPs which censor specific content either due law enforcements or to assert their own interests. Since years I wanted to use DNS Servers which I chose myself so I nailed down my laptop to a fixed DNS server.
However every time when I wanted to use a network which either use DNS spoofing for a captive portal or which has some local domains I had to manually change the DNS settings on my laptop. That can get quite annoying also because afterwards I always have to revert the change.
So for quite a time researching about local DNS resolution settings was my hobby during longer train rides and I figured out the following requirements for my setup:

  1. use a static DNS server (always)
  2. fallback to the DNS server announced via DHCP
  3. optional: fallback to other DNS servers
  4. possibility to add local DNS entries

After playing around with dnsmasq and having a rather bad time with resolveconf I ended up using systemd-resolved in combination with systemd-networkd which I already used for my Simple wireless setup

Basic configuration

So I just stepped through the requirements listed above and configured them into /etc/systemd/resolved.conf

[Resolve]
DNS=46.246.46.246
FallbackDNS=1.1.1.1 8.8.4.4 8.8.8.8 2001:4860:4860::8888 2001:4860:4860::8844
Cache=yes

The server set as DNS= is my static DNS server which I trust in this example. The FallbackDNS= servers are other public DNS servers which probably have better availability. systemd-resolved automatically gets the DNS servers announced via DHCP from systemd-networkd and uses them as normal DNS servers.
Pinging google.se will result in the following DNS requests:

01:48:38.670451 IP 10.123.9.194.43043 > 46.246.46.246.53: 25460+% [1au] A? google.se. (61)
01:48:38.670742 IP 10.123.9.194.50049 > 46.246.46.246.53: 25901+% [1au] AAAA? google.se. (61)
01:48:38.670904 IP 10.123.9.194.59267 > 10.123.9.1.53: 35751+% [1au] A? google.se. (38)
01:48:38.671034 IP 10.123.9.194.49471 > 10.123.9.1.53: 57442+% [1au] AAAA? google.se. (38)
01:48:38.708778 IP 10.123.9.1.53 > 10.123.9.194.59267: 35751 1/0/1 A 216.58.207.67 (54)
01:48:38.708779 IP 10.123.9.1.53 > 10.123.9.194.49471: 57442 1/0/1 AAAA 2a00:1450:4001:81e::2003 (66)
01:48:38.762701 IP 46.246.46.246.53 > 10.123.9.194.43043: 25460 1/0/1 A 172.217.20.35 (54)
01:48:38.762702 IP 46.246.46.246.53 > 10.123.9.194.50049: 25901 1/0/1 AAAA 2a00:1450:400f:80a::2003 (66)

This leads to the result that both servers are asked at the same time and therefore the local DNS server is faster in nearly every case and effectively disables the preferred non local DNS server.

On the other hand, if there would be a fixed order in which the DNS servers would be queried then it can happened that the external is queried first about a local domain, like a captive portal. It will then respond with NXDOMAIN and no other DNS servers will be queried.
See https://github.com/systemd/systemd/issues/5755#issuecomment-297005909 for details.

Maybe in the end it’s the best way to disable the DHCP DNS servers in the systemd-network config file for interfaces with changing connections and then reenable them for specific domains. Of course that would mean to maintain lists of domains which should be resolved by DHCP announced DNS servers, which I do not want to do. But it is probably the best way if you don’t trust them!

In the end the setup still misses the requirements from the beginning but it’s the best I’ve got so far and it’s still quite simple.

Links:
https://www.freedesktop.org/software/systemd/man/resolved.conf.html#Options
https://www.freedesktop.org/software/systemd/man/systemd.network.html#%5BNetwork%5D%20Section%20Options
https://www.freedesktop.org/software/systemd/man/systemd.network.html#Domains=