diff --git a/appc/conn25.go b/appc/conn25.go index 13a41e93f..e4c999a7a 100644 --- a/appc/conn25.go +++ b/appc/conn25.go @@ -80,7 +80,7 @@ func AppDNSRoutes(hasCap func(c tailcfg.NodeCapability) bool, self tailcfg.NodeV } m := make(map[string][]*dnstype.Resolver, len(appNamesByDomain)) for domain, appName := range appNamesByDomain { - m[domain] = []*dnstype.Resolver{{Addr: fmt.Sprintf("%s:%s", DNSAddrScheme, appName)}} + m[domain] = []*dnstype.Resolver{{Addr: fmt.Sprintf("%s:%s", DNSAddrScheme, appName), UseWithExitNode: true}} } return m } diff --git a/appc/conn25_test.go b/appc/conn25_test.go index d9b4201a9..7194a67fe 100644 --- a/appc/conn25_test.go +++ b/appc/conn25_test.go @@ -37,7 +37,7 @@ func TestAppDNSRoutes(t *testing.T) { appSixBytes := getBytesForAttr("app6", []string{"*.Example.com", "EXAMPLE.com", "EXAMPLE.COM"}, []string{"tag:one"}) resolver := func(appName string) []*dnstype.Resolver { - return []*dnstype.Resolver{{Addr: fmt.Sprintf("%s:%s", DNSAddrScheme, appName)}} + return []*dnstype.Resolver{{Addr: fmt.Sprintf("%s:%s", DNSAddrScheme, appName), UseWithExitNode: true}} } for _, tt := range []struct { diff --git a/ipn/ipnlocal/dnsconfig_test.go b/ipn/ipnlocal/dnsconfig_test.go index f4f4f1b38..3198222f1 100644 --- a/ipn/ipnlocal/dnsconfig_test.go +++ b/ipn/ipnlocal/dnsconfig_test.go @@ -429,12 +429,62 @@ func TestDNSConfigForNetmap(t *testing.T) { Hosts: map[dnsname.FQDN][]netip.Addr{}, Routes: map[dnsname.FQDN][]*dnstype.Resolver{ dnsname.FQDN("example.com."): { - {Addr: "tailscale-app:app1"}, + {Addr: "tailscale-app:app1", UseWithExitNode: true}, }, }, MagicDNSHostsUnrouted: true, }, }, + { + name: "conn25-split-dns-with-exit-node", + nm: &netmap.NetworkMap{ + SelfNode: (&tailcfg.Node{ + Name: "a", + Addresses: ipps("100.101.101.101"), + CapMap: tailcfg.NodeCapMap{ + tailcfg.NodeCapability(appc.AppConnectorsExperimentalAttrName): []tailcfg.RawMessage{ + tailcfg.RawMessage(`{"name":"app1","connectors":["tag:woo"],"domains":["example.com"]}`), + }, + }, + }).View(), + AllCaps: set.Of(tailcfg.NodeCapability(appc.AppConnectorsExperimentalAttrName)), + }, + peers: nodeViews([]*tailcfg.Node{ + { + ID: 1, + StableID: "exit", + Name: "p1", + Cap: 26, // can proxy DNS over DoH + Addresses: ipps("100.102.0.1"), + Tags: []string{"tag:woo"}, + Hostinfo: (&tailcfg.Hostinfo{ + Services: []tailcfg.Service{ + { + Proto: tailcfg.PeerAPI4, + Port: 1234, + }, + }, + }).View(), + }, + }), + prefs: &ipn.Prefs{ + CorpDNS: true, + ExitNodeID: "exit", + }, + want: &dns.Config{ + AcceptDNS: true, + Hosts: map[dnsname.FQDN][]netip.Addr{}, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{ + dnsname.FQDN("example.com."): { + {Addr: "tailscale-app:app1", UseWithExitNode: true}, + }, + }, + DefaultResolvers: []*dnstype.Resolver{ + {Addr: "http://100.102.0.1:1234/dns-query"}, + }, + MagicDNSHostsUnrouted: true, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/ipn/ipnlocal/node_backend.go b/ipn/ipnlocal/node_backend.go index a0b539eb1..874ac04c6 100644 --- a/ipn/ipnlocal/node_backend.go +++ b/ipn/ipnlocal/node_backend.go @@ -1536,6 +1536,15 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg. } } + // conn25 split DNS routes are calculated from the domains in the SelfNode.CapMap + // section of the netmap, so need to be assembled separately. + // TODO(tailscale/corp#37125): make this a hook the extension can add + // to reduce dependency from ipnlocal to appc. + var conn25AppRoutes map[string][]*dnstype.Resolver + if (envknob.UseWIPCode() || testenv.InTest()) && buildfeatures.HasConn25 && !prefs.AppConnector().Advertise { + conn25AppRoutes = appc.AppDNSRoutes(nm.HasCap, nm.SelfNode) + } + // If we're using an exit node and that exit node is new enough (1.19.x+) // to run a DoH DNS proxy, then send all our DNS traffic through it, // unless we find resolvers with UseWithExitNode set, in which case we use that. @@ -1551,6 +1560,7 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg. } addSplitDNSRoutes(useWithExitNodeRoutes(nm.DNS.Routes)) + addSplitDNSRoutes(useWithExitNodeRoutes(conn25AppRoutes)) return dcfg } } @@ -1568,13 +1578,7 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg. // Add split DNS routes, with no regard to exit node configuration. addSplitDNSRoutes(nm.DNS.Routes) - - if (envknob.UseWIPCode() || testenv.InTest()) && buildfeatures.HasConn25 && !prefs.AppConnector().Advertise { - // Add split DNS routes for conn25 - if appRoutes := appc.AppDNSRoutes(nm.HasCap, nm.SelfNode); appRoutes != nil { - addSplitDNSRoutes(appRoutes) - } - } + addSplitDNSRoutes(conn25AppRoutes) // Set FallbackResolvers as the default resolvers in the // scenarios that can't handle a purely split-DNS config. See