From b4f991b3814144702371213b01152cb91b22a5b1 Mon Sep 17 00:00:00 2001 From: Saleh Date: Wed, 9 Sep 2026 19:56:05 +0000 Subject: [PATCH] types: lowercase DNS extra record names DNS names are case-insensitive, but clients match extra records against the lowercased query name, so records with mixed-case names (for example "Printer.fritz.box" in an extra_records_path file) never resolved and queries fell through to the global nameserver. Normalize record names to lowercase where the records enter the tailcfg DNS config, covering both dns.extra_records and extra_records_path. Fixes #2782 (cherry picked from commit 95ba1f0566a37f4e6849f91a5a234ba459e4a1b2) --- hscontrol/types/config.go | 20 ++++++++++++++++++-- hscontrol/types/config_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/hscontrol/types/config.go b/hscontrol/types/config.go index 6a6d59dc..28259e25 100644 --- a/hscontrol/types/config.go +++ b/hscontrol/types/config.go @@ -967,7 +967,7 @@ func dnsToTailcfgDNS(dns DNSConfig) *tailcfg.DNSConfig { cfg.Proxied = dns.MagicDNS - cfg.ExtraRecords = dns.ExtraRecords + cfg.ExtraRecords = lowercaseRecordNames(dns.ExtraRecords) if dns.OverrideLocalDNS { cfg.Resolvers = dns.globalResolvers() } else { @@ -1512,6 +1512,22 @@ func (c *Config) SetExtraRecords(records []tailcfg.DNSRecord) { defer tailcfgDNSMu.Unlock() if c.TailcfgDNSConfig != nil { - c.TailcfgDNSConfig.ExtraRecords = records + c.TailcfgDNSConfig.ExtraRecords = lowercaseRecordNames(records) } } + +// lowercaseRecordNames normalizes DNS record names to lowercase, as DNS names +// are case-insensitive and clients match extra records by exact name. +func lowercaseRecordNames(records []tailcfg.DNSRecord) []tailcfg.DNSRecord { + if len(records) == 0 { + return records + } + + normalized := make([]tailcfg.DNSRecord, len(records)) + for i, record := range records { + record.Name = strings.ToLower(record.Name) + normalized[i] = record + } + + return normalized +} diff --git a/hscontrol/types/config_test.go b/hscontrol/types/config_test.go index 07756b55..b54916f7 100644 --- a/hscontrol/types/config_test.go +++ b/hscontrol/types/config_test.go @@ -601,3 +601,32 @@ func TestTrustedProxies(t *testing.T) { }) } } + +// DNS names are case-insensitive, but MagicDNS resolution in clients matches +// extra records by exact name, so mixed-case record names never resolve. +func TestExtraRecordsAreLowercased(t *testing.T) { + mixed := []tailcfg.DNSRecord{ + {Name: "Printer.fritz.box", Type: "A", Value: "192.168.1.2"}, + {Name: "NAS.FRITZ.BOX", Type: "A", Value: "192.168.1.3"}, + } + want := []tailcfg.DNSRecord{ + {Name: "printer.fritz.box", Type: "A", Value: "192.168.1.2"}, + {Name: "nas.fritz.box", Type: "A", Value: "192.168.1.3"}, + } + + tcfg := dnsToTailcfgDNS(DNSConfig{ + MagicDNS: true, + BaseDomain: "example.com", + ExtraRecords: mixed, + }) + if diff := cmp.Diff(want, tcfg.ExtraRecords); diff != "" { + t.Errorf("dnsToTailcfgDNS extra records mismatch (-want +got):\n%s", diff) + } + + cfg := &Config{TailcfgDNSConfig: &tailcfg.DNSConfig{}} + cfg.SetExtraRecords(mixed) + + if diff := cmp.Diff(want, cfg.TailcfgDNSConfig.ExtraRecords); diff != "" { + t.Errorf("SetExtraRecords mismatch (-want +got):\n%s", diff) + } +}