Skip to content

Commit da189ae

Browse files
committed
feat(cli): add p2p diagnostics to ping
1 parent c8eacc6 commit da189ae

File tree

7 files changed

+124
-1
lines changed

7 files changed

+124
-1
lines changed

agent/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func (a *agent) apiHandler() http.Handler {
3737
}
3838
promHandler := PrometheusMetricsHandler(a.prometheusRegistry, a.logger)
3939
r.Get("/api/v0/listening-ports", lp.handler)
40+
r.Get("/api/v0/netcheck", a.HandleNetCheck)
4041
r.Get("/debug/logs", a.HandleHTTPDebugLogs)
4142
r.Get("/debug/magicsock", a.HandleHTTPDebugMagicsock)
4243
r.Get("/debug/magicsock/debug-logging/{state}", a.HandleHTTPMagicsockDebugLoggingState)

agent/health.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package agent
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/coder/coder/v2/coderd/healthcheck/health"
7+
"github.com/coder/coder/v2/coderd/httpapi"
8+
"github.com/coder/coder/v2/codersdk/healthsdk"
9+
)
10+
11+
func (a *agent) HandleNetCheck(rw http.ResponseWriter, r *http.Request) {
12+
ni := a.TailnetConn().GetNetInfo()
13+
14+
httpapi.Write(r.Context(), rw, http.StatusOK, healthsdk.AgentNetcheckReport{
15+
BaseReport: healthsdk.BaseReport{
16+
Severity: health.SeverityOK,
17+
},
18+
NetInfo: ni,
19+
})
20+
}

cli/cliui/agent.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ import (
1010

1111
"github.com/google/uuid"
1212
"golang.org/x/xerrors"
13+
"tailscale.com/tailcfg"
1314

1415
"github.com/coder/coder/v2/codersdk"
16+
"github.com/coder/coder/v2/codersdk/healthsdk"
17+
"github.com/coder/coder/v2/codersdk/workspacesdk"
1518
"github.com/coder/coder/v2/tailnet"
1619
)
1720

@@ -346,3 +349,50 @@ func PeerDiagnostics(w io.Writer, d tailnet.PeerDiagnostics) {
346349
_, _ = fmt.Fprint(w, "✘ Wireguard is not connected\n")
347350
}
348351
}
352+
353+
type ConnDiags struct {
354+
Info *workspacesdk.AgentConnectionInfo
355+
PingP2P bool
356+
LocalNetInfo *tailcfg.NetInfo
357+
FetchAgentNetInfo func() (healthsdk.AgentNetcheckReport, error)
358+
// TODO: More diagnostics
359+
}
360+
361+
func ConnDiagnostics(w io.Writer, d ConnDiags) {
362+
if d.PingP2P {
363+
_, _ = fmt.Fprintln(w, "✔ You are connected directly, peer-to-peer (p2p).")
364+
return
365+
}
366+
_, _ = fmt.Fprintln(w, "❗ You are connected via a DERP relay, not directly, peer-to-peer (p2p).")
367+
368+
if d.Info == nil {
369+
return
370+
}
371+
372+
if d.Info.DisableDirectConnections {
373+
_, _ = fmt.Fprintln(w, "❗ Your Coder administrator has blocked direct connections.")
374+
return
375+
}
376+
377+
if !d.Info.DERPMap.HasSTUN() {
378+
_, _ = fmt.Fprintln(w, "✘ The workspace agent appears to be unable to reach any STUN servers.\nhttps://coder.com/docs/networking/stun")
379+
}
380+
381+
if d.LocalNetInfo == nil {
382+
return
383+
}
384+
385+
if d.LocalNetInfo.MappingVariesByDestIP.EqualBool(true) {
386+
_, _ = fmt.Fprintln(w, "❗ Client is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers.")
387+
}
388+
389+
agentNetcheck, err := d.FetchAgentNetInfo()
390+
if err != nil {
391+
_, _ = fmt.Fprintf(w, "Failed to retrieve netcheck report from agent: %v\n", err)
392+
return
393+
}
394+
395+
if agentNetcheck.NetInfo.MappingVariesByDestIP.EqualBool(true) {
396+
_, _ = fmt.Fprintln(w, "❗ Agent is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers.")
397+
}
398+
}

cli/ping.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/coder/coder/v2/cli/cliui"
1616
"github.com/coder/coder/v2/codersdk"
17+
"github.com/coder/coder/v2/codersdk/healthsdk"
1718
"github.com/coder/coder/v2/codersdk/workspacesdk"
1819
"github.com/coder/serpent"
1920
)
@@ -140,6 +141,18 @@ func (r *RootCmd) ping() *serpent.Command {
140141
if n == int(pingNum) {
141142
diags := conn.GetPeerDiagnostics()
142143
cliui.PeerDiagnostics(inv.Stdout, diags)
144+
145+
connDiags := cliui.ConnDiags{
146+
PingP2P: didP2p,
147+
FetchAgentNetInfo: func() (healthsdk.AgentNetcheckReport, error) {
148+
return conn.NetCheck(ctx)
149+
},
150+
}
151+
connInfo, err := workspacesdk.New(client).AgentConnectionInfoGeneric(ctx)
152+
if err == nil {
153+
connDiags.Info = &connInfo
154+
}
155+
cliui.ConnDiagnostics(inv.Stdout, connDiags)
143156
return nil
144157
}
145158
}

codersdk/healthsdk/healthsdk.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,9 @@ type ClientNetcheckReport struct {
273273
DERP DERPHealthReport `json:"derp"`
274274
Interfaces InterfacesReport `json:"interfaces"`
275275
}
276+
277+
// @typescript-ignore AgentNetcheckReport
278+
type AgentNetcheckReport struct {
279+
BaseReport
280+
NetInfo *tailcfg.NetInfo `json:"net_info"`
281+
}

codersdk/workspacesdk/agentconn.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
"github.com/coder/coder/v2/coderd/tracing"
2424
"github.com/coder/coder/v2/codersdk"
25+
"github.com/coder/coder/v2/codersdk/healthsdk"
2526
"github.com/coder/coder/v2/tailnet"
2627
)
2728

@@ -241,6 +242,23 @@ func (c *AgentConn) ListeningPorts(ctx context.Context) (codersdk.WorkspaceAgent
241242
return resp, json.NewDecoder(res.Body).Decode(&resp)
242243
}
243244

245+
// NetCheck returns a network check report from the workspace agent.
246+
func (c *AgentConn) NetCheck(ctx context.Context) (healthsdk.AgentNetcheckReport, error) {
247+
ctx, span := tracing.StartSpan(ctx)
248+
defer span.End()
249+
res, err := c.apiRequest(ctx, http.MethodGet, "/api/v0/netcheck", nil)
250+
if err != nil {
251+
return healthsdk.AgentNetcheckReport{}, xerrors.Errorf("do request: %w", err)
252+
}
253+
defer res.Body.Close()
254+
if res.StatusCode != http.StatusOK {
255+
return healthsdk.AgentNetcheckReport{}, codersdk.ReadBodyAsError(res)
256+
}
257+
258+
var resp healthsdk.AgentNetcheckReport
259+
return resp, json.NewDecoder(res.Body).Decode(&resp)
260+
}
261+
244262
// DebugMagicsock makes a request to the workspace agent's magicsock debug endpoint.
245263
func (c *AgentConn) DebugMagicsock(ctx context.Context) ([]byte, error) {
246264
ctx, span := tracing.StartSpan(ctx)

tailnet/conn.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,9 @@ func NewConn(options *Options) (conn *Conn, err error) {
294294
}()
295295
if server.telemetryStore != nil {
296296
server.wireguardEngine.SetNetInfoCallback(func(ni *tailcfg.NetInfo) {
297+
server.mutex.Lock()
298+
defer server.mutex.Unlock()
299+
server.lastNetInfo = ni.Clone()
297300
server.telemetryStore.setNetInfo(ni)
298301
nodeUp.setNetInfo(ni)
299302
server.telemetryStore.pingPeer(server)
@@ -304,7 +307,12 @@ func NewConn(options *Options) (conn *Conn, err error) {
304307
})
305308
go server.watchConnChange()
306309
} else {
307-
server.wireguardEngine.SetNetInfoCallback(nodeUp.setNetInfo)
310+
server.wireguardEngine.SetNetInfoCallback(func(ni *tailcfg.NetInfo) {
311+
server.mutex.Lock()
312+
defer server.mutex.Unlock()
313+
server.lastNetInfo = ni.Clone()
314+
nodeUp.setNetInfo(ni)
315+
})
308316
}
309317
server.wireguardEngine.SetStatusCallback(nodeUp.setStatus)
310318
server.magicConn.SetDERPForcedWebsocketCallback(nodeUp.setDERPForcedWebsocket)
@@ -373,6 +381,13 @@ type Conn struct {
373381
watchCancel func()
374382

375383
trafficStats *connstats.Statistics
384+
lastNetInfo *tailcfg.NetInfo
385+
}
386+
387+
func (c *Conn) GetNetInfo() *tailcfg.NetInfo {
388+
c.mutex.Lock()
389+
defer c.mutex.Unlock()
390+
return c.lastNetInfo.Clone()
376391
}
377392

378393
func (c *Conn) SetTunnelDestination(id uuid.UUID) {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy