diff --git a/tailnet/conn.go b/tailnet/conn.go index 34e38da5e28f4..20a00d94d6804 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -631,6 +631,14 @@ func (c *Conn) sendNode() { return } node := c.selfNode() + // Conn.UpdateNodes will skip any nodes that don't have the PreferredDERP + // set to non-zero, since we cannot reach nodes without DERP for discovery. + // Therefore, there is no point in sending the node without this, and we can + // save ourselves from churn in the tailscale/wireguard layer. + if node.PreferredDERP == 0 { + c.logger.Debug(context.Background(), "skipped sending node; no PreferredDERP", slog.F("node", node)) + return + } nodeCallback := c.nodeCallback if nodeCallback == nil { return diff --git a/tailnet/conn_test.go b/tailnet/conn_test.go index 8205fb9d4ad4a..2e19379e6df03 100644 --- a/tailnet/conn_test.go +++ b/tailnet/conn_test.go @@ -165,3 +165,33 @@ func TestTailnet(t *testing.T) { w2.Close() }) } + +// TestConn_PreferredDERP tests that we only trigger the NodeCallback when we have a preferred DERP server. +func TestConn_PreferredDERP(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) + defer cancel() + logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) + derpMap := tailnettest.RunDERPAndSTUN(t) + conn, err := tailnet.NewConn(&tailnet.Options{ + Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)}, + Logger: logger.Named("w1"), + DERPMap: derpMap, + }) + require.NoError(t, err) + defer func() { + err := conn.Close() + require.NoError(t, err) + }() + // buffer channel so callback doesn't block + nodes := make(chan *tailnet.Node, 50) + conn.SetNodeCallback(func(node *tailnet.Node) { + nodes <- node + }) + select { + case node := <-nodes: + require.Equal(t, 1, node.PreferredDERP) + case <-ctx.Done(): + t.Fatal("timed out waiting for node") + } +}