diff options
Diffstat (limited to 'ipn/ipnlocal/node_backend_test.go')
| -rw-r--r-- | ipn/ipnlocal/node_backend_test.go | 185 |
1 files changed, 168 insertions, 17 deletions
diff --git a/ipn/ipnlocal/node_backend_test.go b/ipn/ipnlocal/node_backend_test.go index f6698bd4b..bd41cd70a 100644 --- a/ipn/ipnlocal/node_backend_test.go +++ b/ipn/ipnlocal/node_backend_test.go @@ -6,9 +6,12 @@ package ipnlocal import ( "context" "errors" + "fmt" + "net/netip" "testing" "time" + "tailscale.com/ipn/ipnstate" "tailscale.com/tailcfg" "tailscale.com/tstest" "tailscale.com/types/netmap" @@ -17,7 +20,7 @@ import ( ) func TestNodeBackendReadiness(t *testing.T) { - nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New()) + nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New(), nil) // The node backend is not ready until [nodeBackend.ready] is called, // and [nodeBackend.Wait] should fail with [context.DeadlineExceeded]. @@ -48,7 +51,7 @@ func TestNodeBackendReadiness(t *testing.T) { } func TestNodeBackendShutdown(t *testing.T) { - nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New()) + nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New(), nil) shutdownCause := errors.New("test shutdown") @@ -86,7 +89,7 @@ func TestNodeBackendShutdown(t *testing.T) { } func TestNodeBackendReadyAfterShutdown(t *testing.T) { - nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New()) + nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New(), nil) shutdownCause := errors.New("test shutdown") nb.shutdown(shutdownCause) @@ -98,7 +101,7 @@ func TestNodeBackendReadyAfterShutdown(t *testing.T) { func TestNodeBackendParentContextCancellation(t *testing.T) { ctx, cancelCtx := context.WithCancel(context.Background()) - nb := newNodeBackend(ctx, tstest.WhileTestRunningLogger(t), eventbus.New()) + nb := newNodeBackend(ctx, tstest.WhileTestRunningLogger(t), eventbus.New(), nil) cancelCtx() @@ -115,7 +118,7 @@ func TestNodeBackendParentContextCancellation(t *testing.T) { } func TestNodeBackendConcurrentReadyAndShutdown(t *testing.T) { - nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New()) + nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New(), nil) // Calling [nodeBackend.ready] and [nodeBackend.shutdown] concurrently // should not cause issues, and [nodeBackend.Wait] should unblock, @@ -127,6 +130,17 @@ func TestNodeBackendConcurrentReadyAndShutdown(t *testing.T) { } func TestNodeBackendReachability(t *testing.T) { + addrs := []netip.Prefix{netip.MustParsePrefix("100.64.0.1/32")} + defaults := func(n tailcfg.Node) tailcfg.Node { + if n.ID == 0 { + n.ID = 1234 + } + if n.Name == "" { + n.Name = "exit-node.example.ts.net" + } + return n + } + for _, tc := range []struct { name string @@ -139,54 +153,191 @@ func TestNodeBackendReachability(t *testing.T) { // peer node. cap bool + // Peer defines the peer node. peer tailcfg.Node + + // Ping sets how the peer node responds to pings: + // pingTimedOut: peer is unreachable + // pingSuccess: peer responds to pings + // pingLocalhost: peer is the same as the self node + ping mockPinger + want bool }{ { + name: "disabled/nil", + cap: false, + peer: defaults(tailcfg.Node{ + Online: nil, + }), + want: false, + }, + { name: "disabled/offline", cap: false, - peer: tailcfg.Node{ + peer: defaults(tailcfg.Node{ Online: ptr.To(false), - }, + }), want: false, }, { name: "disabled/online", cap: false, - peer: tailcfg.Node{ + peer: defaults(tailcfg.Node{ Online: ptr.To(true), - }, + }), want: true, }, { + name: "enabled/no_ip", + cap: true, + ping: pingTimedOut, + peer: defaults(tailcfg.Node{ + Online: ptr.To(false), + Addresses: nil, + }), + want: false, + }, + { name: "enabled/offline", cap: true, - peer: tailcfg.Node{ - Online: ptr.To(false), - }, + peer: defaults(tailcfg.Node{ + Online: ptr.To(false), + Addresses: addrs, + }), + ping: pingTimedOut, + want: false, + }, + { + name: "enabled/offline_but_pingable", + cap: true, + peer: defaults(tailcfg.Node{ + Online: ptr.To(false), + Addresses: addrs, + }), + ping: pingSuccess, want: true, }, { name: "enabled/online", cap: true, - peer: tailcfg.Node{ - Online: ptr.To(true), - }, + peer: defaults(tailcfg.Node{ + Online: ptr.To(true), + Addresses: addrs, + }), + ping: pingSuccess, want: true, }, + { + name: "enabled/online_but_unpingable", + cap: true, + peer: defaults(tailcfg.Node{ + Online: ptr.To(true), + Addresses: addrs, + }), + ping: pingTimedOut, + want: false, + }, + { + name: "enabled/offline_localhost", + cap: true, + peer: defaults(tailcfg.Node{ + Online: ptr.To(false), + Addresses: addrs, + }), + ping: pingLocalhost, + want: true, + }, + { + name: "enabled/online_localhost", + cap: true, + peer: defaults(tailcfg.Node{ + Online: ptr.To(true), + Addresses: addrs, + }), + ping: pingLocalhost, + want: true, + }, + { + name: "enabled/offline_but_cancelled", + cap: true, + peer: defaults(tailcfg.Node{ + Online: ptr.To(false), + Addresses: addrs, + }), + ping: pingCancelled, + want: false, + }, + { + name: "enabled/online_but_cancelled", + cap: true, + peer: defaults(tailcfg.Node{ + Online: ptr.To(true), + Addresses: addrs, + }), + ping: pingCancelled, + want: false, + }, } { + t.Run(tc.name, func(t *testing.T) { - nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New()) + ctx := t.Context() + + nb := newNodeBackend(ctx, tstest.WhileTestRunningLogger(t), eventbus.New(), mockPinger(tc.ping)) nb.netMap = &netmap.NetworkMap{} if tc.cap { nb.netMap.AllCaps.Make() nb.netMap.AllCaps.Add(tailcfg.NodeAttrClientSideReachability) } - got := nb.PeerIsReachable(t.Context(), tc.peer.View()) + if tc.ping == pingCancelled { + c, cancel := context.WithCancelCause(ctx) + ctx = c + cancel(fmt.Errorf("subtest: %q", tc.name)) + } + + got := nb.PeerIsReachable(ctx, tc.peer.View()) if got != tc.want { t.Errorf("got %v, want %v", got, tc.want) } }) } } + +type mockPinger int + +const ( + pingTimedOut mockPinger = iota + pingSuccess + pingLocalhost + pingCancelled +) + +func (p mockPinger) Ping(ctx context.Context, ip netip.Addr, pingType tailcfg.PingType, size int) (*ipnstate.PingResult, error) { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + res := &ipnstate.PingResult{ + IP: ip.String(), + NodeIP: ip.String(), + } + switch p { + case pingTimedOut: + ctx, cancel := context.WithTimeout(ctx, 0) + defer cancel() + <-ctx.Done() + res.Err = ctx.Err().Error() + return res, nil + case pingLocalhost: + res.Err = fmt.Sprintf("%v is local Tailscale IP", ip) + res.IsLocalIP = true + case pingSuccess: + res.LatencySeconds = 1 + default: + panic(fmt.Sprintf("unknown mockPinger %v", p)) + } + return res, nil +} |
