From a3d2a9e27ecfff4c3a6b7e2122d54d3f0288d84d Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Tue, 25 Apr 2023 18:52:14 +0000 Subject: [PATCH 1/3] feat(agent): add http debug routes for magicsock --- agent/agent.go | 32 +++++++++++++++++++++++++++++--- cli/agent.go | 36 ++++++++++++++++++++++++++---------- tailnet/conn.go | 4 ++++ 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 9b70506b49936..f312ca9549bc3 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -60,7 +60,7 @@ type Options struct { ReconnectingPTYTimeout time.Duration EnvironmentVariables map[string]string Logger slog.Logger - AgentPorts map[int]string + IgnorePorts map[int]string SSHMaxTimeout time.Duration TailnetListenPort uint16 } @@ -76,7 +76,12 @@ type Client interface { PatchStartupLogs(ctx context.Context, req agentsdk.PatchStartupLogs) error } -func New(options Options) io.Closer { +type Agent interface { + HTTPDebug() http.Handler + io.Closer +} + +func New(options Options) Agent { if options.ReconnectingPTYTimeout == 0 { options.ReconnectingPTYTimeout = 5 * time.Minute } @@ -112,7 +117,7 @@ func New(options Options) io.Closer { tempDir: options.TempDir, lifecycleUpdate: make(chan struct{}, 1), lifecycleReported: make(chan codersdk.WorkspaceAgentLifecycle, 1), - ignorePorts: options.AgentPorts, + ignorePorts: options.IgnorePorts, connStatsChan: make(chan *agentsdk.Stats, 1), sshMaxTimeout: options.SSHMaxTimeout, } @@ -1267,6 +1272,27 @@ func (a *agent) isClosed() bool { } } +func (a *agent) HTTPDebug() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + a.closeMutex.Lock() + network := a.network + a.closeMutex.Unlock() + + if network == nil { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("network is not ready yet")) + return + } + + if r.URL.Path == "/debug/magicsock" { + network.MagicsockServeHTTPDebug(w, r) + } else { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte("404 not found")) + } + }) +} + func (a *agent) Close() error { a.closeMutex.Lock() defer a.closeMutex.Unlock() diff --git a/cli/agent.go b/cli/agent.go index bcc18b5bb331d..00be766ba9303 100644 --- a/cli/agent.go +++ b/cli/agent.go @@ -37,7 +37,8 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd { noReap bool sshMaxTimeout time.Duration tailnetListenPort int64 - prometheusAddress string + promAddress string + debugAddress string ) cmd := &clibase.Cmd{ Use: "agent", @@ -48,7 +49,7 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd { ctx, cancel := context.WithCancel(inv.Context()) defer cancel() - agentPorts := map[int]string{} + ignorePorts := map[int]string{} isLinux := runtime.GOOS == "linux" @@ -125,14 +126,14 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd { defer pprofSrvClose() // Do a best effort here. If this fails, it's not a big deal. if port, err := urlPort(pprofAddress); err == nil { - agentPorts[port] = "pprof" + ignorePorts[port] = "pprof" } - prometheusSrvClose := ServeHandler(ctx, logger, prometheusMetricsHandler(), prometheusAddress, "prometheus") + prometheusSrvClose := ServeHandler(ctx, logger, prometheusMetricsHandler(), promAddress, "prometheus") defer prometheusSrvClose() // Do a best effort here. If this fails, it's not a big deal. - if port, err := urlPort(prometheusAddress); err == nil { - agentPorts[port] = "prometheus" + if port, err := urlPort(promAddress); err == nil { + ignorePorts[port] = "prometheus" } // exchangeToken returns a session token. @@ -196,7 +197,7 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd { return xerrors.Errorf("add executable to $PATH: %w", err) } - closer := agent.New(agent.Options{ + agnt := agent.New(agent.Options{ Client: client, Logger: logger, LogDir: logDir, @@ -215,11 +216,19 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd { EnvironmentVariables: map[string]string{ "GIT_ASKPASS": executablePath, }, - AgentPorts: agentPorts, + IgnorePorts: ignorePorts, SSHMaxTimeout: sshMaxTimeout, }) + + debugSrvClose := ServeHandler(ctx, logger, agnt.HTTPDebug(), debugAddress, "debug") + defer debugSrvClose() + // Do a best effort here. If this fails, it's not a big deal. + if port, err := urlPort(debugAddress); err == nil { + ignorePorts[port] = "debug" + } + <-ctx.Done() - return closer.Close() + return agnt.Close() }, } @@ -270,9 +279,16 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd { Flag: "prometheus-address", Default: "127.0.0.1:2112", Env: "CODER_AGENT_PROMETHEUS_ADDRESS", - Value: clibase.StringOf(&prometheusAddress), + Value: clibase.StringOf(&promAddress), Description: "The bind address to serve Prometheus metrics.", }, + { + Flag: "debug-address", + Default: "127.0.0.1:2113", + Env: "CODER_AGENT_DEBUG_ADDRESS", + Value: clibase.StringOf(&debugAddress), + Description: "The bind address to serve a debug HTTP server.", + }, } return cmd diff --git a/tailnet/conn.go b/tailnet/conn.go index e5f422cb973c8..34e38da5e28f4 100644 --- a/tailnet/conn.go +++ b/tailnet/conn.go @@ -828,6 +828,10 @@ func (c *Conn) SetConnStatsCallback(maxPeriod time.Duration, maxConns int, dump c.tunDevice.SetStatistics(connStats) } +func (c *Conn) MagicsockServeHTTPDebug(w http.ResponseWriter, r *http.Request) { + c.magicConn.ServeHTTPDebug(w, r) +} + type listenKey struct { network string host string From 7ab75215ab6a95b77beeccbb6b9eb4e6850d3b37 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Tue, 25 Apr 2023 23:42:43 +0000 Subject: [PATCH 2/3] fixup! feat(agent): add http debug routes for magicsock --- cli/agent.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/agent.go b/cli/agent.go index 00be766ba9303..da39f1caa22b9 100644 --- a/cli/agent.go +++ b/cli/agent.go @@ -37,7 +37,7 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd { noReap bool sshMaxTimeout time.Duration tailnetListenPort int64 - promAddress string + prometheusAddress string debugAddress string ) cmd := &clibase.Cmd{ @@ -129,10 +129,10 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd { ignorePorts[port] = "pprof" } - prometheusSrvClose := ServeHandler(ctx, logger, prometheusMetricsHandler(), promAddress, "prometheus") + prometheusSrvClose := ServeHandler(ctx, logger, prometheusMetricsHandler(), prometheusAddress, "prometheus") defer prometheusSrvClose() // Do a best effort here. If this fails, it's not a big deal. - if port, err := urlPort(promAddress); err == nil { + if port, err := urlPort(prometheusAddress); err == nil { ignorePorts[port] = "prometheus" } @@ -279,7 +279,7 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd { Flag: "prometheus-address", Default: "127.0.0.1:2112", Env: "CODER_AGENT_PROMETHEUS_ADDRESS", - Value: clibase.StringOf(&promAddress), + Value: clibase.StringOf(&prometheusAddress), Description: "The bind address to serve Prometheus metrics.", }, { From 658ba721727795d798e34cbc0a820c0d1347c878 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Wed, 26 Apr 2023 17:18:39 +0000 Subject: [PATCH 3/3] fixup! feat(agent): add http debug routes for magicsock --- cli/testdata/coder_agent_--help.golden | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/testdata/coder_agent_--help.golden b/cli/testdata/coder_agent_--help.golden index 7b6d05ecb9602..5b9e6f394076e 100644 --- a/cli/testdata/coder_agent_--help.golden +++ b/cli/testdata/coder_agent_--help.golden @@ -6,6 +6,9 @@ Starts the Coder workspace agent. --auth string, $CODER_AGENT_AUTH (default: token) Specify the authentication type to use for the agent. + --debug-address string, $CODER_AGENT_DEBUG_ADDRESS (default: 127.0.0.1:2113) + The bind address to serve a debug HTTP server. + --log-dir string, $CODER_AGENT_LOG_DIR (default: /tmp) Specify the location for the agent log files.