diff --git a/ipn/ipnlocal/dnsconfig_test.go b/ipn/ipnlocal/dnsconfig_test.go index 02db18ffe..19d8e8b86 100644 --- a/ipn/ipnlocal/dnsconfig_test.go +++ b/ipn/ipnlocal/dnsconfig_test.go @@ -50,6 +50,7 @@ func TestDNSConfigForNetmap(t *testing.T) { tests := []struct { name string nm *netmap.NetworkMap + expired bool peers []tailcfg.NodeView os string // version.OS value; empty means linux cloud cloudenv.Cloud @@ -327,12 +328,31 @@ func TestDNSConfigForNetmap(t *testing.T) { Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, }, }, + { + name: "self_expired", + nm: &netmap.NetworkMap{ + Name: "myname.net", + SelfNode: (&tailcfg.Node{ + Addresses: ipps("100.101.101.101"), + }).View(), + }, + expired: true, + peers: nodeViews([]*tailcfg.Node{ + { + ID: 1, + Name: "peera.net", + Addresses: ipps("100.102.0.1", "100.102.0.2", "fe75::1001", "fe75::1002"), + }, + }), + prefs: &ipn.Prefs{}, + want: &dns.Config{}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { verOS := cmp.Or(tt.os, "linux") var log tstest.MemLogger - got := dnsConfigForNetmap(tt.nm, peersMap(tt.peers), tt.prefs.View(), log.Logf, verOS) + got := dnsConfigForNetmap(tt.nm, peersMap(tt.peers), tt.prefs.View(), tt.expired, log.Logf, verOS) if !reflect.DeepEqual(got, tt.want) { gotj, _ := json.MarshalIndent(got, "", "\t") wantj, _ := json.MarshalIndent(tt.want, "", "\t") diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 9a57776a0..fbe35b87b 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -4026,7 +4026,7 @@ func (b *LocalBackend) authReconfig() { disableSubnetsIfPAC := nm.HasCap(tailcfg.NodeAttrDisableSubnetsIfPAC) userDialUseRoutes := nm.HasCap(tailcfg.NodeAttrUserDialUseRoutes) dohURL, dohURLOK := exitNodeCanProxyDNS(nm, b.peers, prefs.ExitNodeID()) - dcfg := dnsConfigForNetmap(nm, b.peers, prefs, b.logf, version.OS()) + dcfg := dnsConfigForNetmap(nm, b.peers, prefs, b.keyExpired, b.logf, version.OS()) // If the current node is an app connector, ensure the app connector machine is started b.reconfigAppConnectorLocked(nm, prefs) b.mu.Unlock() @@ -4126,10 +4126,23 @@ func shouldUseOneCGNATRoute(logf logger.Logf, controlKnobs *controlknobs.Knobs, // // The versionOS is a Tailscale-style version ("iOS", "macOS") and not // a runtime.GOOS. -func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg.NodeView, prefs ipn.PrefsView, logf logger.Logf, versionOS string) *dns.Config { +func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg.NodeView, prefs ipn.PrefsView, selfExpired bool, logf logger.Logf, versionOS string) *dns.Config { if nm == nil { return nil } + + // If the current node's key is expired, then we don't program any DNS + // configuration into the operating system. This ensures that if the + // DNS configuration specifies a DNS server that is only reachable over + // Tailscale, we don't break connectivity for the user. + // + // TODO(andrew-d): this also stops returning anything from quad-100; we + // could do the same thing as having "CorpDNS: false" and keep that but + // not program the OS? + if selfExpired { + return &dns.Config{} + } + dcfg := &dns.Config{ Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, Hosts: map[dnsname.FQDN][]netip.Addr{}, diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go index bf4a28ff9..e4091ef02 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -1283,7 +1283,7 @@ func TestDNSConfigForNetmapForExitNodeConfigs(t *testing.T) { } prefs := &ipn.Prefs{ExitNodeID: tc.exitNode, CorpDNS: true} - got := dnsConfigForNetmap(nm, peersMap(tc.peers), prefs.View(), t.Logf, "") + got := dnsConfigForNetmap(nm, peersMap(tc.peers), prefs.View(), false, t.Logf, "") if !resolversEqual(t, got.DefaultResolvers, tc.wantDefaultResolvers) { t.Errorf("DefaultResolvers: got %#v, want %#v", got.DefaultResolvers, tc.wantDefaultResolvers) }