Split DNS behaviour differs from tailscale's #417

Closed
opened 2025-12-29 01:28:37 +01:00 by adam · 0 comments
Owner

Originally created by @dbevacqua on GitHub (Jan 23, 2023).

Bug description

Tailscale allows split DNS configurations common for corporate VPNs:

  • queries for corporate domains go to a resolver on the VPN
  • other queries go to local resolver (because I want to use my pihole)

In tailscale config UI, the following configuration results in exactly the above behaviour:

splitDNS

My tailscale client logs show this:

dns: Set: {DefaultResolvers:[] Routes:{foocorp.com.:[10.1.0.2]} SearchDomains:[foocorp.com.] Hosts:4}
dns: Resolvercfg: {Routes:{} Hosts:4 LocalDomains:[]}
dns: OScfg: {Nameservers:[10.1.0.2] SearchDomains:[foocorp.com.] MatchDomains:[foocorp.com.] Hosts:[]}

and systemd-resolve agrees:

# resolvectl status tailscale0
Link 4 (tailscale0)
    Current Scopes: DNS
         Protocols: -DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 10.1.0.2
       DNS Servers: 10.1.0.2
        DNS Domain: foocorp.com

To Reproduce

Equivalent headscale config would be:

dns_config:
  override_local_dns: false
  restricted_nameservers:
    foocorp.com:
      - 10.1.0.2
  magic_dns: false

However, headscale explictly ignores dns_config.restricted_nameservers unless global nameservers are configured. The closest possible config is:

dns_config:
  override_local_dns: true
  nameservers:
    - 1.1.1.1

  restricted_nameservers:
    foocorp.com:
      - 10.1.0.2

  domains: ["foocorp.com"]

  magic_dns: false

which tailscale sees as:

dns: Set: {DefaultResolvers:[1.1.1.1] Routes:{foocorp.com.:[10.1.0.2]} SearchDomains:[foocorp.com.] Hosts:1}
dns: Resolvercfg: {Routes:{.:[1.1.1.1] foocorp.com.:[10.1.0.2]} Hosts:1 LocalDomains:[]}
dns: OScfg: {Nameservers:[100.100.100.100] SearchDomains:[foocorp.com.] MatchDomains:[] Hosts:[]}

and results in this local resolver config:

# resolvectl status tailscale0
Link 4 (tailscale0)
    Current Scopes: DNS
         Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 100.100.100.100
       DNS Servers: 100.100.100.100
        DNS Domain: foocorp.com ~.

which uses the embedded tailscale DNS server as the default route and therefore bypasses my pihole.

So headscale's behaviour is different to tailscale's and more limited

Context info

  • Version of headscale used: 0.19.0-beta1
  • Version of tailscale client: 1.34.2
  • OS: Ubuntu 22.04.1 LTS
  • Kernel version: 5.15.0-56-generic
Originally created by @dbevacqua on GitHub (Jan 23, 2023). **Bug description** Tailscale allows split DNS configurations common for corporate VPNs: * queries for corporate domains go to a resolver on the VPN * other queries go to local resolver (because I want to use my pihole) In tailscale config UI, the following configuration results in exactly the above behaviour: ![splitDNS](https://user-images.githubusercontent.com/6534306/214037588-dd287251-dcc5-4373-a477-13c65442f9cc.png) My tailscale client logs show this: ``` dns: Set: {DefaultResolvers:[] Routes:{foocorp.com.:[10.1.0.2]} SearchDomains:[foocorp.com.] Hosts:4} dns: Resolvercfg: {Routes:{} Hosts:4 LocalDomains:[]} dns: OScfg: {Nameservers:[10.1.0.2] SearchDomains:[foocorp.com.] MatchDomains:[foocorp.com.] Hosts:[]} ``` and `systemd-resolve` agrees: ```bash # resolvectl status tailscale0 Link 4 (tailscale0) Current Scopes: DNS Protocols: -DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported Current DNS Server: 10.1.0.2 DNS Servers: 10.1.0.2 DNS Domain: foocorp.com ``` **To Reproduce** Equivalent headscale config would be: ```yaml dns_config: override_local_dns: false restricted_nameservers: foocorp.com: - 10.1.0.2 magic_dns: false ``` However, headscale explictly ignores `dns_config.restricted_nameservers` unless global nameservers are configured. The closest possible config is: ```yaml dns_config: override_local_dns: true nameservers: - 1.1.1.1 restricted_nameservers: foocorp.com: - 10.1.0.2 domains: ["foocorp.com"] magic_dns: false ``` which tailscale sees as: ``` dns: Set: {DefaultResolvers:[1.1.1.1] Routes:{foocorp.com.:[10.1.0.2]} SearchDomains:[foocorp.com.] Hosts:1} dns: Resolvercfg: {Routes:{.:[1.1.1.1] foocorp.com.:[10.1.0.2]} Hosts:1 LocalDomains:[]} dns: OScfg: {Nameservers:[100.100.100.100] SearchDomains:[foocorp.com.] MatchDomains:[] Hosts:[]} ``` and results in this local resolver config: ```bash # resolvectl status tailscale0 Link 4 (tailscale0) Current Scopes: DNS Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported Current DNS Server: 100.100.100.100 DNS Servers: 100.100.100.100 DNS Domain: foocorp.com ~. ``` which uses the embedded tailscale DNS server as the default route and therefore bypasses my pihole. So headscale's behaviour is different to tailscale's and more limited **Context info** - Version of headscale used: 0.19.0-beta1 - Version of tailscale client: 1.34.2 - OS: Ubuntu 22.04.1 LTS - Kernel version: 5.15.0-56-generic
adam added the bug label 2025-12-29 01:28:37 +01:00
adam closed this issue 2025-12-29 01:28:37 +01:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/headscale#417