From 336719378296bf9b364a6e981617baf84b325111 Mon Sep 17 00:00:00 2001 From: Florian Preinstorfer Date: Thu, 20 Feb 2025 16:25:11 +0100 Subject: [PATCH] Restore support for "Override local DNS" Tailscale allows to override the local DNS settings of a node via "Override local DNS" [1]. Restore this flag with the same config setting name `dns.override_local_dns` but disable it by default to align it with Tailscale's default behaviour. Tested with Tailscale 1.80.2 and systemd-resolved on Debian 12. With `dns.override_local_dns: false`: ``` Link 12 (tailscale0) Current Scopes: DNS Protocols: -DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported DNS Servers: 100.100.100.100 DNS Domain: tn.example.com ~0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa [snip] ``` With `dns.override_local_dns: true`: ``` Link 12 (tailscale0) Current Scopes: DNS Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported DNS Servers: 100.100.100.100 DNS Domain: tn.example.com ~. ``` [1] https://tailscale.com/kb/1054/dns#override-local-dns Fixes: #2256 --- CHANGELOG.md | 2 ++ config-example.yaml | 4 ++++ hscontrol/types/config.go | 11 +++++++++-- hscontrol/types/config_test.go | 8 ++++++-- hscontrol/types/testdata/dns_full.yaml | 1 + hscontrol/types/testdata/dns_full_no_magic.yaml | 1 + integration/hsic/config.go | 1 + 7 files changed, 24 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0571150b5..5a61fad20d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,8 @@ Note that if an exit route is approved (0.0.0.0/0 or ::/0), both IPv4 and IPv6 w - It is now possible to inspect running goroutines and take profiles - View of config, policy, filter, ssh policy per node, connected nodes and DERPmap +- Restore support for "Override local DNS" + [#2438](https://github.com/juanfont/headscale/pull/2438) ## 0.25.1 (2025-02-25) diff --git a/config-example.yaml b/config-example.yaml index f6e043c6ef..03d7511a56 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -274,6 +274,10 @@ dns: # `hostname.base_domain` (e.g., _myhost.example.com_). base_domain: example.com + # Whether to use the local DNS settings of a node (default) or override the + # local DNS settings and force the use of Headscale's DNS configuration. + override_local_dns: false + # List of DNS servers to expose to clients. nameservers: global: diff --git a/hscontrol/types/config.go b/hscontrol/types/config.go index 0b69a1a439..5e5cf472e3 100644 --- a/hscontrol/types/config.go +++ b/hscontrol/types/config.go @@ -102,6 +102,7 @@ type Config struct { type DNSConfig struct { MagicDNS bool `mapstructure:"magic_dns"` BaseDomain string `mapstructure:"base_domain"` + OverrideLocalDNS bool `mapstructure:"override_local_dns"` Nameservers Nameservers SearchDomains []string `mapstructure:"search_domains"` ExtraRecords []tailcfg.DNSRecord `mapstructure:"extra_records"` @@ -287,6 +288,7 @@ func LoadConfig(path string, isFile bool) error { viper.SetDefault("dns.magic_dns", true) viper.SetDefault("dns.base_domain", "") + viper.SetDefault("dns.override_local_dns", false) viper.SetDefault("dns.nameservers.global", []string{}) viper.SetDefault("dns.nameservers.split", map[string]string{}) viper.SetDefault("dns.search_domains", []string{}) @@ -351,9 +353,9 @@ func validateServerConfig() error { depr.fatalIfNewKeyIsNotUsed("policy.path", "acl_policy_path") // Move dns_config -> dns - depr.warn("dns_config.override_local_dns") depr.fatalIfNewKeyIsNotUsed("dns.magic_dns", "dns_config.magic_dns") depr.fatalIfNewKeyIsNotUsed("dns.base_domain", "dns_config.base_domain") + depr.fatalIfNewKeyIsNotUsed("dns.override_local_dns", "dns_config.override_local_dns") depr.fatalIfNewKeyIsNotUsed("dns.nameservers.global", "dns_config.nameservers") depr.fatalIfNewKeyIsNotUsed("dns.nameservers.split", "dns_config.restricted_nameservers") depr.fatalIfNewKeyIsNotUsed("dns.search_domains", "dns_config.domains") @@ -616,6 +618,7 @@ func dns() (DNSConfig, error) { dns.MagicDNS = viper.GetBool("dns.magic_dns") dns.BaseDomain = viper.GetString("dns.base_domain") + dns.OverrideLocalDNS = viper.GetBool("dns.override_local_dns") dns.Nameservers.Global = viper.GetStringSlice("dns.nameservers.global") dns.Nameservers.Split = viper.GetStringMapStringSlice("dns.nameservers.split") dns.SearchDomains = viper.GetStringSlice("dns.search_domains") @@ -721,7 +724,11 @@ func dnsToTailcfgDNS(dns DNSConfig) *tailcfg.DNSConfig { cfg.Proxied = dns.MagicDNS cfg.ExtraRecords = dns.ExtraRecords - cfg.Resolvers = dns.globalResolvers() + if dns.OverrideLocalDNS { + cfg.Resolvers = dns.globalResolvers() + } else { + cfg.FallbackResolvers = dns.globalResolvers() + } routes := dns.splitResolvers() cfg.Routes = routes diff --git a/hscontrol/types/config_test.go b/hscontrol/types/config_test.go index 511528df58..1547ca1471 100644 --- a/hscontrol/types/config_test.go +++ b/hscontrol/types/config_test.go @@ -36,6 +36,7 @@ func TestReadConfig(t *testing.T) { want: DNSConfig{ MagicDNS: true, BaseDomain: "example.com", + OverrideLocalDNS: false, Nameservers: Nameservers{ Global: []string{ "1.1.1.1", @@ -70,7 +71,7 @@ func TestReadConfig(t *testing.T) { want: &tailcfg.DNSConfig{ Proxied: true, Domains: []string{"example.com", "test.com", "bar.com"}, - Resolvers: []*dnstype.Resolver{ + FallbackResolvers: []*dnstype.Resolver{ {Addr: "1.1.1.1"}, {Addr: "1.0.0.1"}, {Addr: "2606:4700:4700::1111"}, @@ -101,6 +102,7 @@ func TestReadConfig(t *testing.T) { want: DNSConfig{ MagicDNS: false, BaseDomain: "example.com", + OverrideLocalDNS: false, Nameservers: Nameservers{ Global: []string{ "1.1.1.1", @@ -135,7 +137,7 @@ func TestReadConfig(t *testing.T) { want: &tailcfg.DNSConfig{ Proxied: false, Domains: []string{"example.com", "test.com", "bar.com"}, - Resolvers: []*dnstype.Resolver{ + FallbackResolvers: []*dnstype.Resolver{ {Addr: "1.1.1.1"}, {Addr: "1.0.0.1"}, {Addr: "2606:4700:4700::1111"}, @@ -254,6 +256,7 @@ func TestReadConfigFromEnv(t *testing.T) { configEnv: map[string]string{ "HEADSCALE_DNS_MAGIC_DNS": "true", "HEADSCALE_DNS_BASE_DOMAIN": "example.com", + "HEADSCALE_DNS_OVERRIDE_LOCAL_DNS": "false", "HEADSCALE_DNS_NAMESERVERS_GLOBAL": `1.1.1.1 8.8.8.8`, "HEADSCALE_DNS_SEARCH_DOMAINS": "test.com bar.com", @@ -274,6 +277,7 @@ func TestReadConfigFromEnv(t *testing.T) { want: DNSConfig{ MagicDNS: true, BaseDomain: "example.com", + OverrideLocalDNS: false, Nameservers: Nameservers{ Global: []string{"1.1.1.1", "8.8.8.8"}, Split: map[string][]string{ diff --git a/hscontrol/types/testdata/dns_full.yaml b/hscontrol/types/testdata/dns_full.yaml index 62bbd3ab57..d27e0fee70 100644 --- a/hscontrol/types/testdata/dns_full.yaml +++ b/hscontrol/types/testdata/dns_full.yaml @@ -7,6 +7,7 @@ dns: magic_dns: true base_domain: example.com + override_local_dns: false nameservers: global: - 1.1.1.1 diff --git a/hscontrol/types/testdata/dns_full_no_magic.yaml b/hscontrol/types/testdata/dns_full_no_magic.yaml index 2f35c3dbd4..4fb25d65cd 100644 --- a/hscontrol/types/testdata/dns_full_no_magic.yaml +++ b/hscontrol/types/testdata/dns_full_no_magic.yaml @@ -7,6 +7,7 @@ dns: magic_dns: false base_domain: example.com + override_local_dns: false nameservers: global: - 1.1.1.1 diff --git a/integration/hsic/config.go b/integration/hsic/config.go index 256fbd7605..297cbd9f5b 100644 --- a/integration/hsic/config.go +++ b/integration/hsic/config.go @@ -23,6 +23,7 @@ func DefaultConfigEnv() map[string]string { "HEADSCALE_PREFIXES_V6": "fd7a:115c:a1e0::/48", "HEADSCALE_DNS_BASE_DOMAIN": "headscale.net", "HEADSCALE_DNS_MAGIC_DNS": "true", + "HEADSCALE_DNS_OVERRIDE_LOCAL_DNS": "false", "HEADSCALE_DNS_NAMESERVERS_GLOBAL": "127.0.0.11 1.1.1.1", "HEADSCALE_PRIVATE_KEY_PATH": "/tmp/private.key", "HEADSCALE_NOISE_PRIVATE_KEY_PATH": "/tmp/noise_private.key",