From 59bc6867a4c7946974f03a8457530d643938f9cd Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Tue, 8 Aug 2023 17:31:26 +0000 Subject: [PATCH 1/2] chore: add debug endpoint on agent to enable magicsock debug logging --- agent/agent.go | 48 +++++++++++++++++++---- agent/agent_test.go | 92 ++++++++++++++++++++++++++++++++++++++++++++- tailnet/conn.go | 20 ++++++++++ 3 files changed, 152 insertions(+), 8 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 52c423787fb44..e435b795d5fc9 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -22,6 +22,7 @@ import ( "time" "github.com/armon/circbuf" + "github.com/go-chi/chi/v5" "github.com/google/uuid" "github.com/prometheus/client_golang/prometheus" "github.com/spf13/afero" @@ -1408,24 +1409,57 @@ func (a *agent) isClosed() bool { } func (a *agent) HTTPDebug() http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + r := chi.NewRouter() + + requireNetwork := func(w http.ResponseWriter) (*tailnet.Conn, bool) { a.closeMutex.Lock() network := a.network a.closeMutex.Unlock() if network == nil { - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNotFound) _, _ = w.Write([]byte("network is not ready yet")) + return nil, false + } + + return network, true + } + + r.Get("/debug/magicsock", func(w http.ResponseWriter, r *http.Request) { + network, ok := requireNetwork(w) + if !ok { return } + network.MagicsockServeHTTPDebug(w, r) + }) - if r.URL.Path == "/debug/magicsock" { - network.MagicsockServeHTTPDebug(w, r) - } else { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte("404 not found")) + r.Get("/debug/magicsock/debug-logging/{state}", func(w http.ResponseWriter, r *http.Request) { + state := chi.URLParam(r, "state") + stateBool, err := strconv.ParseBool(state) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + _, _ = fmt.Fprintf(w, "invalid state %q, must be a boolean", state) + return + } + + network, ok := requireNetwork(w) + if !ok { + return } + + network.MagicsockSetDebugLoggingEnabled(stateBool) + a.logger.Info(r.Context(), "updated magicsock debug logging due to debug request", slog.F("new_state", stateBool)) + + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, "updated magicsock debug logging to %v", stateBool) }) + + r.NotFound(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte("404 not found")) + }) + + return r } func (a *agent) Close() error { diff --git a/agent/agent_test.go b/agent/agent_test.go index 34637992536b7..92be40764f209 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -1932,6 +1932,96 @@ func TestAgent_WriteVSCodeConfigs(t *testing.T) { }, testutil.WaitShort, testutil.IntervalFast) } +func TestAgent_DebugServer(t *testing.T) { + t.Parallel() + + derpMap, _ := tailnettest.RunDERPAndSTUN(t) + //nolint:dogsled + conn, _, _, _, agnt := setupAgent(t, agentsdk.Manifest{ + DERPMap: derpMap, + }, 0) + + awaitReachableCtx := testutil.Context(t, testutil.WaitLong) + ok := conn.AwaitReachable(awaitReachableCtx) + require.True(t, ok) + _ = conn.Close() + + srv := httptest.NewServer(agnt.HTTPDebug()) + t.Cleanup(srv.Close) + + t.Run("MagicsockDebug", func(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitLong) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL+"/debug/magicsock", nil) + require.NoError(t, err) + + res, err := srv.Client().Do(req) + require.NoError(t, err) + defer res.Body.Close() + require.Equal(t, http.StatusOK, res.StatusCode) + + resBody, err := io.ReadAll(res.Body) + require.NoError(t, err) + require.Contains(t, string(resBody), "

magicsock

") + }) + + t.Run("MagicsockDebugLogging", func(t *testing.T) { + t.Parallel() + + t.Run("Enable", func(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitLong) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL+"/debug/magicsock/debug-logging/t", nil) + require.NoError(t, err) + + res, err := srv.Client().Do(req) + require.NoError(t, err) + defer res.Body.Close() + require.Equal(t, http.StatusOK, res.StatusCode) + + resBody, err := io.ReadAll(res.Body) + require.NoError(t, err) + require.Contains(t, string(resBody), "updated magicsock debug logging to true") + }) + + t.Run("Disable", func(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitLong) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL+"/debug/magicsock/debug-logging/0", nil) + require.NoError(t, err) + + res, err := srv.Client().Do(req) + require.NoError(t, err) + defer res.Body.Close() + require.Equal(t, http.StatusOK, res.StatusCode) + + resBody, err := io.ReadAll(res.Body) + require.NoError(t, err) + require.Contains(t, string(resBody), "updated magicsock debug logging to false") + }) + + t.Run("Invalid", func(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitLong) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL+"/debug/magicsock/debug-logging/blah", nil) + require.NoError(t, err) + + res, err := srv.Client().Do(req) + require.NoError(t, err) + defer res.Body.Close() + require.Equal(t, http.StatusBadRequest, res.StatusCode) + + resBody, err := io.ReadAll(res.Body) + require.NoError(t, err) + require.Contains(t, string(resBody), `invalid state "blah", must be a boolean`) + }) + }) +} + func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) (*ptytest.PTYCmd, pty.Process) { //nolint:dogsled agentConn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0) @@ -2013,7 +2103,7 @@ func setupAgent(t *testing.T, metadata agentsdk.Manifest, ptyTimeout time.Durati *agenttest.Client, <-chan *agentsdk.Stats, afero.Fs, - io.Closer, + agent.Agent, ) { logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) if metadata.DERPMap == nil { diff --git a/tailnet/conn.go b/tailnet/conn.go index 089a83ff79ff9..9315e3ba4abf7 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -8,6 +8,7 @@ import ( "net" "net/http" "net/netip" + "os" "reflect" "strconv" "sync" @@ -51,6 +52,14 @@ const ( WorkspaceAgentSpeedtestPort = 3 ) +// EnvMagicsockDebugLogging enables super-verbose logging for the magicsock +// internals. A logger must be supplied to the connection with the debug level +// enabled. +// +// With this disabled, you still get a lot of output if you have a valid logger +// with the debug level enabled. +const EnvMagicsockDebugLogging = "CODER_MAGICSOCK_DEBUG_LOGGING" + func init() { // Globally disable network namespacing. All networking happens in // userspace. @@ -175,6 +184,13 @@ func NewConn(options *Options) (conn *Conn, err error) { magicConn.SetDERPHeader(options.DERPHeader.Clone()) } + if v, ok := os.LookupEnv(EnvMagicsockDebugLogging); ok { + vBool, err := strconv.ParseBool(v) + if err == nil { + magicConn.SetDebugLoggingEnabled(vBool) + } + } + // Update the keys for the magic connection! err = magicConn.SetPrivateKey(nodePrivateKey) if err != nil { @@ -361,6 +377,10 @@ type Conn struct { trafficStats *connstats.Statistics } +func (c *Conn) MagicsockSetDebugLoggingEnabled(enabled bool) { + c.magicConn.SetDebugLoggingEnabled(enabled) +} + func (c *Conn) SetAddresses(ips []netip.Prefix) error { c.mutex.Lock() defer c.mutex.Unlock() From c21e19db0e72a689fc6bd4af33141efe12c908ff Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Tue, 8 Aug 2023 17:35:35 +0000 Subject: [PATCH 2/2] fixup! chore: add debug endpoint on agent to enable magicsock debug logging --- tailnet/conn.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tailnet/conn.go b/tailnet/conn.go index 9315e3ba4abf7..ebe57d2606b1c 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -186,9 +186,14 @@ func NewConn(options *Options) (conn *Conn, err error) { if v, ok := os.LookupEnv(EnvMagicsockDebugLogging); ok { vBool, err := strconv.ParseBool(v) - if err == nil { + if err != nil { + options.Logger.Debug(context.Background(), fmt.Sprintf("magicsock debug logging disabled due to invalid value %s=%q, use true or false", EnvMagicsockDebugLogging, v)) + } else { magicConn.SetDebugLoggingEnabled(vBool) + options.Logger.Debug(context.Background(), fmt.Sprintf("magicsock debug logging set by %s=%t", EnvMagicsockDebugLogging, vBool)) } + } else { + options.Logger.Debug(context.Background(), fmt.Sprintf("magicsock debug logging disabled, use %s=true to enable", EnvMagicsockDebugLogging)) } // Update the keys for the magic connection!