Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 56 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save brasey/fa2277a6d7242cdf4e4b7c720d42b567 to your computer and use it in GitHub Desktop.
Save brasey/fa2277a6d7242cdf4e4b7c720d42b567 to your computer and use it in GitHub Desktop.
Configure systemd-resolved to use a specific DNS nameserver for a given domain

Configure systemd-resolved to use a specific DNS nameserver for a given domain

Use case

Given

  • I use a VPN to connect to my work network
  • I'm on a Linux computer that uses systemd-resolved
  • I have a work domain called example.com
  • example.com is hosted by both public and private DNS nameservers
  • Both public and private nameservers claim to be authoritative for example.com
  • There are no public hosts in example.com
  • The public resolvers for example.com resolve all queries to a parked hosting webpage
  • The private resolvers for example.com contain all correct DNS records for private hosts

I need to

  • Resolve private hosts in example.com when connected to VPN

(Note that this should also work for pointing DNS-blocked domains at different, non-blocked nameservers)

Solution

systemd-resolved now has the ability to specify nameservers for specific domains. Until recently this was not the case, systemd-resolved leaned on NetworkManager, which used dnsmasq for this purpose.

If you were already doing something like this to accomplish this task, first undo all of that. We're not going to use NetworkManager/dnsmasq.

In your systemd-resolved config, which for me is at /etc/systemd/resolved.conf (Fedora), make sure you have this (assuming example.com private nameservers are 10.0.0.1 and 10.0.0.2)

[Resolve]
DNS=10.0.0.1 10.0.0.2
Domains=~example.com

Note the tilde, that makes systemd-resolved do something special. According to the man page:

Specified domain names may optionally be prefixed with "~". In this case they do not define a search path, but preferably direct DNS queries for the indicated domains to the DNS servers configured with the system DNS= setting (see above), in case additional, suitable per-link DNS servers are known.

Restart systemd-resolved and you should be in business.

@athrunsun
Copy link

If I need to set DNS servers for another domain, can I use [Resolve] section multiple times in /etc/systemd/resolved.conf?

@brasey
Copy link
Author

brasey commented Sep 20, 2021

You mean you have two private domains that both have public and private resolvers set up? Man, what a mess!

I don't know how you solve for two horribly configured domains at the same time, I'd love to know if you figure that out.

@pstef
Copy link

pstef commented Sep 20, 2021

Not claiming this is the answer, but it made me think about something like this:

[Resolve]
DNS=10.0.0.1%vpn0#example1.com 172.16.0.1#example2.com
Domains=~example1.com ~example2.com

Although I haven't tested anything like that. Just wondered if I got the manual page right.

@brasey
Copy link
Author

brasey commented Sep 20, 2021

That does look promising... now I wish I had a bunch of janky networks to test on!

@athrunsun
Copy link

athrunsun commented Sep 21, 2021

@brasey No, I just want to route DNS requests of different domains to different DNS servers, just like what @pstef proposed.

@pstef Where did you find this? I don't see this kind of usage in man resolved.conf.

@pstef
Copy link

pstef commented Sep 21, 2021

@athrunsun I got it exactly from resolved.conf.5. From the options section, specifically DNS= and Domains=. The former gives this example: "111.222.333.444:9953%ifname#example.com" and the latter says that "domains listed here [...] define a search path that preferably directs DNS queries to this interface". A lot of it was me guessing, so I'm still not sure that my interpretation is correct or even that it works either by accident or otherwise.

@athrunsun
Copy link

@pstef Which distro are you using and which version?

@pstef
Copy link

pstef commented Sep 26, 2021

I sometimes use Ubuntu 20.04, but the manual page I read was https://man.archlinux.org/man/resolved.conf.5 and also checked against https://manpages.debian.org/testing/systemd/resolved.conf.5.en.html but they seem to be in sync.

@george-angel
Copy link

Why do you need Domains=~example.com here, setting DNS=10.0.0.1 10.0.0.2 - sets 10.0.0.1 to be your default resolver, so it will be queried regardless of the domains?

@brasey
Copy link
Author

brasey commented Nov 2, 2021

If we did that, then when we weren't on the VPN we wouldn't be able to hit the resolver at all, right?

@george-angel
Copy link

Correct - but I don't see what purpose Domains=~example.com serves here. From my understanding the behaviour would be the same without it?

From what I understand, how it works in the original config:

  • You are setting your preferred resolvers: 10.0.0.1 10.0.0.2
  • When they are available, they are used for all domains
  • When you are not on VPN and those resolves aren't reachable, you would use FallbackDNS

You can verify what is happening with resolvectl.

@brasey
Copy link
Author

brasey commented Nov 2, 2021

Without the tilde, your resolvers are set for every DNS lookup. That's a pretty big hammer.

With the tilde, the configured DNS resolvers are used for that domain. Other domains use default resolvers.

@george-angel
Copy link

Other domains use default resolvers.

But setting DNS=10.0.0.1 10.0.0.2 is a global configuration, its true for all queries.

We can check this by doing some tcpdumping and running drill queries.

Here is my config:

[Resolve]
DNS=10.253.253.253
Domains=~tp.private ~telecomplus.internal

And resolvectl shows:

$ resolvectl
Global
           Protocols: +LLMNR +mDNS -DNSOverTLS DNSSEC=no/unsupported
    resolv.conf mode: stub
  Current DNS Server: 10.253.253.253
         DNS Servers: 10.253.253.253
Fallback DNS Servers: 1.1.1.1 9.9.9.10 8.8.8.8 2606:4700:4700::1111 2620:fe::10 2001:4860:4860::8888
          DNS Domain: ~telecomplus.internal ~tp.private

Now I fire off a drill query: $ drill mer-prd-rdsh14.tp.private

And see it come though on my wireguard interface:

$ sudo tcpdump -n -i wg5 -v dst 10.253.253.253
tcpdump: listening on wg5, link-type RAW (Raw IP), snapshot length 262144 bytes
14:02:38.696029 IP (tos 0x0, ttl 64, id 58942, offset 0, flags [DF], proto UDP (17), length 71)
    10.90.208.3.51989 > 10.253.253.253.53: 25402+ A? mer-prd-rdsh14.tp.private. (43)

Now I query some other domain: $ drill porkiepie.com

And the query still goes to my globally set resolver: 10.253.253.253

14:03:04.832655 IP (tos 0x0, ttl 64, id 63171, offset 0, flags [none], proto UDP (17), length 70)
    10.90.208.3.53537 > 10.253.253.253.53: 37446+ [1au] A? porkiepie.com. (42)

If I drop my routes to 10.253.253.253, and try to query: $ drill exmaple.com

14:16:49.444783 lo    In  IP (tos 0x0, ttl 64, id 25792, offset 0, flags [DF], proto UDP (17), length 57)
    127.0.0.1.43650 > 127.0.0.53.53: 25378+ A? exmaple.com. (29)
14:16:49.445140 wlan0 Out IP (tos 0x0, ttl 64, id 7787, offset 0, flags [none], proto UDP (17), length 57)
    192.168.1.76.50415 > 10.253.253.253.53: 36000+ A? exmaple.com. (29)
14:16:49.445208 wlan0 Out IP (tos 0x0, ttl 64, id 61977, offset 0, flags [none], proto UDP (17), length 68)
    192.168.1.76.43582 > 217.169.20.21.53: 48708+ [1au] A? exmaple.com. (40)
14:16:49.475123 wlan0 In  IP (tos 0x0, ttl 61, id 22812, offset 0, flags [none], proto UDP (17), length 100)
    217.169.20.21.53 > 192.168.1.76.43582: 48708 2/0/1 exmaple.com. A 104.18.145.11, exmaple.com. A 104.18.144.11 (72)
14:16:49.475337 lo    In  IP (tos 0x0, ttl 1, id 64858, offset 0, flags [DF], proto UDP (17), length 89)
    127.0.0.53.53 > 127.0.0.1.43650: 25378 2/0/0 exmaple.com. A 104.18.145.11, exmaple.com. A 104.18.144.11 (61)

We still try to resolve against 10.253.253.253, fail and try 217.169.20.21 which is the resolved I get from my home router for the wifi link:

Link 4 (wlan0)
    Current Scopes: DNS LLMNR/IPv4 LLMNR/IPv6
         Protocols: +DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 217.169.20.21
       DNS Servers: 217.169.20.21 217.169.20.20 2001:8b0::2021 2001:8b0::2020

@dreua
Copy link

dreua commented Dec 20, 2021

Yeah I don't see how this will work which is quite unfortunate -.-

@george-angel
Copy link

You want a systemd-networkd.service, like this:

[Match]
Name=wg5

[Network]
DNS=10.253.253.253
Domains=~telecomplus.internal ~tp.private

You still need to tie this config to an interface, but interface doesn't need to exist for the service to be "active".

@dreua
Copy link

dreua commented Dec 21, 2021

@george-angel can you elaborate on how to set this up and why it works? What does wg5 stand for? I'm using NetworkManager and a dummy interface in the meantime which seems to work fine for my use case. It is important to me that only the matching requests are sent to that DNS server, when I tried the suggested setup with resolved.conf I could resolve the needed Domains but only because all DNS requests were sent to the specific DNS server. (It happily resolves everything but I don't want to use it for everything.)

@george-angel
Copy link

The way it works with networkd - the specific resolver is tied to an interface, I don't really like it, but thats the way it is.

In my case wg5 is the interface name for the wireguard interface I create that routes my private subnets. I think you can replace that name with your "dummy" reference.

More info here: https://wiki.archlinux.org/title/systemd-networkd

@leiless
Copy link

leiless commented Dec 30, 2022

sudo resolvectl dns eth0 8.8.8.8 8.8.4.4
resolvectl dns eth0

@JackThird
Copy link

thanks, you save my day ;)

@BastienFaure
Copy link

I am really surprised it works that way, is that intentional ? You explicitly specify a DNS server and instruct systemd-resolved to use that server for a certain domain only. The output of resolvectl does not even displays DefaultRoute so I am really trying to understand why would the DNS server receive all queries

@george-angel
Copy link

I think because DNS in [Resolve] is global config. Even if you specify some Domains there, what is it supposed to use for everything else?

The config its related to: https://wiki.archlinux.org/title/Systemd-networkd#[Network] - which makes sense there, but makes it less obvious when used in global context, like in /etc/systemd/resolved.conf .

It makes more sense if any of your "per link" config has Domains=~., in which case the more specific Domains config in /etc/systemd/resolved.conf would work as intended.

@bio-informatician
Copy link

Well
sudo resolvectl dns ens3 8.8.8.8 8.8.4.4
solved the issue, but after restart dns list disappears again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment