ipn/ipnlocal,net/dns/resolver: serve MagicDNS names from live indexes
Every netmap change, including an incremental delta of a single peer, rebuilt the full MagicDNS state twice: dnsConfigForNetmap walked all peers to build the dns.Config.Hosts map, and resolver.SetConfig then walked that map again to build its reverse (PTR) index. On a tailnet with 10k peers that is a lot of garbage per delta. Instead, add a resolver.MagicDNSHosts hook, installed once by LocalBackend, that the quad-100 resolver consults on demand at query time. It is backed by nodeBackend's nodeByName, nodeByAddr, and peers indexes, which are already maintained incrementally as netmap deltas arrive. The subdomain-resolve capability check also moves to the hook (checking the node's CapMap at query time), so dns.Config's SubdomainHosts is no longer populated. dns.Config.Hosts remains for control's DNS.ExtraRecords, which are few and which feed the split-DNS decisions in dns.Manager's compileConfig, and on Windows it still carries every node's records because the hosts-file fallback path (compileHostEntries) needs the complete enumerable set. Those compileConfig decisions also consulted the per-node Hosts entries (hasHostsWithoutSplitDNSRoutes), so a new Config.MagicDNSHostsUnrouted bit preserves that signal now that node records are not listed: with MagicDNS names present but MagicDNS domain routing off, quad-100 stays in the OS resolver path. One small behavior change: reverse (PTR) lookups now also answer for node addresses whose forward records are filtered out by the IPv6-suppression rule (issue #1152), since nodeByAddr indexes all node addresses. Previously such addresses were absent from the pushed Hosts map and thus from the reverse index. Updates #12542 Updates tailscale/corp#43949 Change-Id: I63b99199c2b3b124c08cb8bbaea1f63165095294 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
6a635c4e55
commit
7e609b2581
@@ -18,6 +18,7 @@ import (
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/util/eventbus"
|
||||
"tailscale.com/util/mak"
|
||||
"tailscale.com/util/set"
|
||||
@@ -559,3 +560,82 @@ func TestNodeBackendRouteManagerExtras(t *testing.T) {
|
||||
t.Errorf("Outbound still routes %v after extras cleared", transit.Addr())
|
||||
}
|
||||
}
|
||||
|
||||
// Tests the live MagicDNS lookup methods backing
|
||||
// [resolver.MagicDNSHosts]: forward, reverse, and subdomain-cap
|
||||
// lookups must serve from the node indexes and stay correct across
|
||||
// netmap deltas without any full Hosts map rebuild.
|
||||
func TestNodeBackendMagicDNSHosts(t *testing.T) {
|
||||
nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New())
|
||||
|
||||
self := &tailcfg.Node{
|
||||
ID: 1,
|
||||
Name: "self.example.ts.net.",
|
||||
Addresses: []netip.Prefix{netip.MustParsePrefix("100.64.0.1/32")},
|
||||
}
|
||||
p1 := &tailcfg.Node{
|
||||
ID: 2,
|
||||
Key: key.NewNode().Public(),
|
||||
Name: "p1.example.ts.net.",
|
||||
Addresses: []netip.Prefix{
|
||||
netip.MustParsePrefix("100.64.0.2/32"),
|
||||
netip.MustParsePrefix("fd7a:115c:a1e0::2/128"),
|
||||
},
|
||||
CapMap: tailcfg.NodeCapMap{tailcfg.NodeAttrDNSSubdomainResolve: nil},
|
||||
}
|
||||
nb.SetNetMap(&netmap.NetworkMap{
|
||||
SelfNode: self.View(),
|
||||
Peers: []tailcfg.NodeView{p1.View()},
|
||||
})
|
||||
|
||||
wantHost := func(fqdn dnsname.FQDN, want ...netip.Addr) {
|
||||
t.Helper()
|
||||
ips, ok := nb.magicDNSHostAddrs(fqdn)
|
||||
if len(want) == 0 {
|
||||
if ok {
|
||||
t.Errorf("magicDNSHostAddrs(%q) = %v; want no match", fqdn, ips)
|
||||
}
|
||||
return
|
||||
}
|
||||
if !ok || !slices.Equal(ips, want) {
|
||||
t.Errorf("magicDNSHostAddrs(%q) = %v, %v; want %v", fqdn, ips, ok, want)
|
||||
}
|
||||
}
|
||||
|
||||
// The self node has IPv4, so the peer's IPv6 address is
|
||||
// filtered out (issue 1152).
|
||||
wantHost("p1.example.ts.net.", netip.MustParseAddr("100.64.0.2"))
|
||||
wantHost("self.example.ts.net.", netip.MustParseAddr("100.64.0.1"))
|
||||
wantHost("unknown.example.ts.net.")
|
||||
|
||||
if fqdn, ok := nb.magicDNSPTR(netip.MustParseAddr("100.64.0.2")); !ok || fqdn != "p1.example.ts.net." {
|
||||
t.Errorf("magicDNSPTR(100.64.0.2) = %q, %v; want p1's name", fqdn, ok)
|
||||
}
|
||||
if got, want := nb.magicDNSSubdomainHost("p1.example.ts.net."), true; got != want {
|
||||
t.Errorf("magicDNSSubdomainHost(p1) = %v; want %v", got, want)
|
||||
}
|
||||
if got, want := nb.magicDNSSubdomainHost("self.example.ts.net."), false; got != want {
|
||||
t.Errorf("magicDNSSubdomainHost(self) = %v; want %v", got, want)
|
||||
}
|
||||
|
||||
// Removing the peer via a delta drops its records.
|
||||
if _, handled := nb.UpdateNetmapDelta([]netmap.NodeMutation{netmap.MakeNodeMutationRemove(2)}); !handled {
|
||||
t.Fatal("UpdateNetmapDelta not handled")
|
||||
}
|
||||
wantHost("p1.example.ts.net.")
|
||||
if fqdn, ok := nb.magicDNSPTR(netip.MustParseAddr("100.64.0.2")); ok {
|
||||
t.Errorf("magicDNSPTR(100.64.0.2) after removal = %q; want no match", fqdn)
|
||||
}
|
||||
|
||||
// Adding a peer via a delta serves it immediately.
|
||||
p3 := &tailcfg.Node{
|
||||
ID: 3,
|
||||
Key: key.NewNode().Public(),
|
||||
Name: "p3.example.ts.net.",
|
||||
Addresses: []netip.Prefix{netip.MustParsePrefix("100.64.0.3/32")},
|
||||
}
|
||||
if _, handled := nb.UpdateNetmapDelta([]netmap.NodeMutation{netmap.NodeMutationUpsert{Node: p3.View()}}); !handled {
|
||||
t.Fatal("UpdateNetmapDelta not handled")
|
||||
}
|
||||
wantHost("p3.example.ts.net.", netip.MustParseAddr("100.64.0.3"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user