diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 7b3d11bb2..d1805ef34 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -536,6 +536,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) { // Since st.NetMap==nil means "netmap is unchanged", there is // no other way to represent this change. b.setNetMapLocked(nil) + b.e.SetNetworkMap(new(netmap.NetworkMap)) } prefs := b.prefs diff --git a/tstest/integration/integration_test.go b/tstest/integration/integration_test.go index e11d23cf0..3383d397d 100644 --- a/tstest/integration/integration_test.go +++ b/tstest/integration/integration_test.go @@ -409,6 +409,56 @@ func TestOneNodeUpWindowsStyle(t *testing.T) { d1.MustCleanShutdown(t) } +func TestLogoutRemovesAllPeers(t *testing.T) { + t.Parallel() + env := newTestEnv(t) + // Spin up some nodes. + nodes := make([]*testNode, 2) + for i := range nodes { + nodes[i] = newTestNode(t, env) + nodes[i].StartDaemon(t) + nodes[i].AwaitResponding(t) + nodes[i].MustUp() + nodes[i].AwaitIP(t) + nodes[i].AwaitRunning(t) + } + + // Make every node ping every other node. + // This makes sure magicsock is fully populated. + for i := range nodes { + for j := range nodes { + if i <= j { + continue + } + if err := tstest.WaitFor(20*time.Second, func() error { + return nodes[i].Ping(nodes[j]) + }); err != nil { + t.Fatalf("ping %v -> %v: %v", nodes[i].AwaitIP(t), nodes[j].AwaitIP(t), err) + } + } + } + + // wantNode0PeerCount waits until node[0] status includes exactly want peers. + wantNode0PeerCount := func(want int) { + if err := tstest.WaitFor(20*time.Second, func() error { + s := nodes[0].MustStatus(t) + if peers := s.Peers(); len(peers) != want { + return fmt.Errorf("want %d peer(s) in status, got %v", want, peers) + } + return nil + }); err != nil { + t.Fatal(err) + } + } + + wantNode0PeerCount(len(nodes) - 1) // all other nodes are peers + nodes[0].MustLogOut() + wantNode0PeerCount(0) // node[0] is logged out, so it should not have any peers + nodes[0].MustUp() + nodes[0].AwaitIP(t) + wantNode0PeerCount(len(nodes) - 1) // all other nodes are peers again +} + // testEnv contains the test environment (set of servers) used by one // or more nodes. type testEnv struct { @@ -703,6 +753,21 @@ func (n *testNode) MustDown() { } } +func (n *testNode) MustLogOut() { + t := n.env.t + t.Logf("Running logout ...") + if err := n.Tailscale("logout").Run(); err != nil { + t.Fatalf("logout: %v", err) + } +} + +func (n *testNode) Ping(otherNode *testNode) error { + t := n.env.t + ip := otherNode.AwaitIP(t).String() + t.Logf("Running ping %v (from %v)...", ip, n.AwaitIP(t)) + return n.Tailscale("ping", ip).Run() +} + // AwaitListening waits for the tailscaled to be serving local clients // over its localhost IPC mechanism. (Unix socket, etc) func (n *testNode) AwaitListening(t testing.TB) { diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index 4da5eb433..5e5a5aaa0 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -239,13 +239,15 @@ func (ns *Impl) updateIPs(nm *netmap.NetworkMap) { newIPs := make(map[tcpip.AddressWithPrefix]bool) isAddr := map[netaddr.IPPrefix]bool{} - for _, ipp := range nm.SelfNode.Addresses { - isAddr[ipp] = true - } - for _, ipp := range nm.SelfNode.AllowedIPs { - local := isAddr[ipp] - if local && ns.ProcessLocalIPs || !local && ns.ProcessSubnets { - newIPs[ipPrefixToAddressWithPrefix(ipp)] = true + if nm.SelfNode != nil { + for _, ipp := range nm.SelfNode.Addresses { + isAddr[ipp] = true + } + for _, ipp := range nm.SelfNode.AllowedIPs { + local := isAddr[ipp] + if local && ns.ProcessLocalIPs || !local && ns.ProcessSubnets { + newIPs[ipPrefixToAddressWithPrefix(ipp)] = true + } } }