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
This commit is contained in:
Saleh
2026-07-04 16:02:56 +03:00
committed by Kristoffer Dalby
parent 38722e5eeb
commit 95ba1f0566
3 changed files with 55 additions and 2 deletions
+8
View File
@@ -58,6 +58,14 @@ keys remain all-access.
- Fix tailsql not shutting down with headscale, leaving the process hanging on graceful shutdown [#3400](https://github.com/juanfont/headscale/pull/3400)
- Fix tvOS setup instructions: install the VPN configuration before setting the coordination server URL [#3431](https://github.com/juanfont/headscale/pull/3431)
## 0.29.4 (202x-xx-xx)
**Minimum supported Tailscale client version: v1.80.0**
### Changes
- Lowercase DNS extra record names so mixed-case records resolve [#3366](https://github.com/juanfont/headscale/pull/3366)
## 0.29.3 (2026-07-29)
**Minimum supported Tailscale client version: v1.80.0**
+18 -2
View File
@@ -993,7 +993,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 {
@@ -1475,6 +1475,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
}
+29
View File
@@ -741,3 +741,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)
}
}