package healthcheck_test import ( "context" "encoding/json" "io" "net/http" "net/http/httptest" "net/url" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "tailscale.com/derp" "tailscale.com/derp/derphttp" "tailscale.com/ipn" "tailscale.com/tailcfg" "tailscale.com/types/key" "github.com/coder/coder/v2/coderd/healthcheck" "github.com/coder/coder/v2/tailnet" "github.com/coder/coder/v2/testutil" ) //nolint:tparallel func TestDERP(t *testing.T) { if testing.Short() { t.Skip("skipping healthcheck test in short mode, they reach out over the network.") } t.Run("OK", func(t *testing.T) { t.Parallel() derpSrv := derp.NewServer(key.NewNode(), func(format string, args ...any) { t.Logf(format, args...) }) defer derpSrv.Close() srv := httptest.NewServer(derphttp.Handler(derpSrv)) defer srv.Close() var ( ctx = context.Background() report = healthcheck.DERPReport{} derpURL, _ = url.Parse(srv.URL) opts = &healthcheck.DERPReportOptions{ DERPMap: &tailcfg.DERPMap{Regions: map[int]*tailcfg.DERPRegion{ 1: { EmbeddedRelay: true, RegionID: 999, Nodes: []*tailcfg.DERPNode{{ Name: "1a", RegionID: 999, HostName: derpURL.Host, IPv4: derpURL.Host, STUNPort: -1, InsecureForTests: true, ForceHTTP: true, }}, }, }}, } ) report.Run(ctx, opts) assert.True(t, report.Healthy) for _, region := range report.Regions { assert.True(t, region.Healthy) for _, node := range region.NodeReports { assert.True(t, node.Healthy) assert.True(t, node.CanExchangeMessages) assert.NotEmpty(t, node.RoundTripPing) assert.Len(t, node.ClientLogs, 2) assert.Len(t, node.ClientLogs[0], 1) assert.Len(t, node.ClientErrs[0], 0) assert.Len(t, node.ClientLogs[1], 1) assert.Len(t, node.ClientErrs[1], 0) assert.False(t, node.STUN.Enabled) assert.False(t, node.STUN.CanSTUN) assert.Nil(t, node.STUN.Error) } } }) t.Run("Tailscale/Dallas/OK", func(t *testing.T) { t.Parallel() if testutil.InCI() { t.Skip("This test depends on reaching out over the network to Tailscale servers, which is inherently flaky.") } derpSrv := derp.NewServer(key.NewNode(), func(format string, args ...any) { t.Logf(format, args...) }) defer derpSrv.Close() srv := httptest.NewServer(derphttp.Handler(derpSrv)) defer srv.Close() var ( ctx = context.Background() report = healthcheck.DERPReport{} opts = &healthcheck.DERPReportOptions{ DERPMap: tsDERPMap(ctx, t), } ) // Only include the Dallas region opts.DERPMap.Regions = map[int]*tailcfg.DERPRegion{9: opts.DERPMap.Regions[9]} report.Run(ctx, opts) assert.True(t, report.Healthy) for _, region := range report.Regions { assert.True(t, region.Healthy) for _, node := range region.NodeReports { assert.True(t, node.Healthy) assert.True(t, node.CanExchangeMessages) assert.NotEmpty(t, node.RoundTripPing) assert.Len(t, node.ClientLogs, 2) assert.Len(t, node.ClientLogs[0], 1) assert.Len(t, node.ClientErrs[0], 0) assert.Len(t, node.ClientLogs[1], 1) assert.Len(t, node.ClientErrs[1], 0) assert.True(t, node.STUN.Enabled) assert.True(t, node.STUN.CanSTUN) assert.Nil(t, node.STUN.Error) } } }) t.Run("ForceWebsockets", func(t *testing.T) { t.Parallel() derpSrv := derp.NewServer(key.NewNode(), func(format string, args ...any) { t.Logf(format, args...) }) defer derpSrv.Close() handler, closeHandler := tailnet.WithWebsocketSupport(derpSrv, derphttp.Handler(derpSrv)) defer closeHandler() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Upgrade") == "DERP" { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("bad request")) return } handler.ServeHTTP(w, r) })) var ( ctx = context.Background() report = healthcheck.DERPReport{} derpURL, _ = url.Parse(srv.URL) opts = &healthcheck.DERPReportOptions{ DERPMap: &tailcfg.DERPMap{Regions: map[int]*tailcfg.DERPRegion{ 1: { EmbeddedRelay: true, RegionID: 999, Nodes: []*tailcfg.DERPNode{{ Name: "1a", RegionID: 999, HostName: derpURL.Host, IPv4: derpURL.Host, STUNPort: -1, InsecureForTests: true, ForceHTTP: true, }}, }, }}, } ) report.Run(ctx, opts) assert.False(t, report.Healthy) for _, region := range report.Regions { assert.False(t, region.Healthy) for _, node := range region.NodeReports { assert.False(t, node.Healthy) assert.True(t, node.CanExchangeMessages) assert.NotEmpty(t, node.RoundTripPing) assert.Len(t, node.ClientLogs, 2) assert.Len(t, node.ClientLogs[0], 3) assert.Len(t, node.ClientLogs[1], 3) assert.Len(t, node.ClientErrs, 2) assert.Len(t, node.ClientErrs[0], 1) // this assert.Len(t, node.ClientErrs[1], 1) assert.True(t, node.UsesWebsocket) assert.False(t, node.STUN.Enabled) assert.False(t, node.STUN.CanSTUN) assert.Nil(t, node.STUN.Error) } } }) t.Run("STUNOnly/OK", func(t *testing.T) { t.Parallel() var ( ctx = context.Background() report = healthcheck.DERPReport{} opts = &healthcheck.DERPReportOptions{ DERPMap: &tailcfg.DERPMap{Regions: map[int]*tailcfg.DERPRegion{ 1: { EmbeddedRelay: true, RegionID: 999, Nodes: []*tailcfg.DERPNode{{ Name: "999stun0", RegionID: 999, HostName: "stun.l.google.com", STUNPort: 19302, STUNOnly: true, InsecureForTests: true, ForceHTTP: true, }}, }, }}, } ) report.Run(ctx, opts) assert.True(t, report.Healthy) for _, region := range report.Regions { assert.True(t, region.Healthy) for _, node := range region.NodeReports { assert.True(t, node.Healthy) assert.False(t, node.CanExchangeMessages) assert.Len(t, node.ClientLogs, 0) assert.True(t, node.STUN.Enabled) assert.True(t, node.STUN.CanSTUN) assert.Nil(t, node.STUN.Error) } } }) } func tsDERPMap(ctx context.Context, t testing.TB) *tailcfg.DERPMap { req, err := http.NewRequestWithContext(ctx, "GET", ipn.DefaultControlURL+"/derpmap/default", nil) require.NoError(t, err) res, err := http.DefaultClient.Do(req) require.NoError(t, err) defer res.Body.Close() require.Equal(t, http.StatusOK, res.StatusCode) var derpMap tailcfg.DERPMap err = json.NewDecoder(io.LimitReader(res.Body, 1<<20)).Decode(&derpMap) require.NoError(t, err) return &derpMap }