Skip to content

feat(cli): add aws check to ping p2p diagnostics #14450

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
review
  • Loading branch information
ethanndickson committed Aug 29, 2024
commit f16e4e72ab3a2dfef9a278437ef795271d59a82b
83 changes: 61 additions & 22 deletions cli/cliui/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import (
"strconv"
"strings"
"time"
"unicode"

"github.com/google/uuid"
"golang.org/x/xerrors"
"tailscale.com/tailcfg"

"github.com/coder/coder/v2/coderd/healthcheck/health"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/healthsdk"
"github.com/coder/coder/v2/codersdk/workspacesdk"
Expand Down Expand Up @@ -351,72 +353,109 @@ func PeerDiagnostics(w io.Writer, d tailnet.PeerDiagnostics) {
}

type ConnDiags struct {
ConnInfo *workspacesdk.AgentConnectionInfo
ConnInfo workspacesdk.AgentConnectionInfo
PingP2P bool
DisableDirect bool
LocalNetInfo *tailcfg.NetInfo
LocalInterfaces *healthsdk.InterfacesReport
AgentNetcheck *healthsdk.AgentNetcheckReport
ClientIPIsAWS bool
AgentIPIsAWS bool
Verbose bool
// TODO: More diagnostics
}

func ConnDiagnostics(w io.Writer, d ConnDiags) {
func (d ConnDiags) Write(w io.Writer) {
_, _ = fmt.Fprintln(w, "")
general, client, agent := d.splitDiagnostics()
for _, msg := range general {
_, _ = fmt.Fprintln(w, msg)
}
if len(client) > 0 {
_, _ = fmt.Fprint(w, "Possible client-side issues with direct connection:\n\n")
for _, msg := range client {
_, _ = fmt.Fprintf(w, " - %s\n\n", msg)
}
}
if len(agent) > 0 {
_, _ = fmt.Fprint(w, "Possible agent-side issues with direct connections:\n\n")
for _, msg := range agent {
_, _ = fmt.Fprintf(w, " - %s\n\n", msg)
}
}
}

func (d ConnDiags) splitDiagnostics() (general, client, agent []string) {
if d.PingP2P {
_, _ = fmt.Fprint(w, "✔ You are connected directly (p2p)\n")
general = append(general, "✔ You are connected directly (p2p)")
} else {
_, _ = fmt.Fprint(w, "❗ You are connected via a DERP relay, not directly (p2p)\n")
general = append(general, "❗ You are connected via a DERP relay, not directly (p2p)")
}

if d.AgentNetcheck != nil {
for _, msg := range d.AgentNetcheck.Interfaces.Warnings {
_, _ = fmt.Fprintf(w, "❗ Agent: %s\n", msg.Message)
agent = append(agent, formatHealthMessage(msg))
}
}

if d.LocalInterfaces != nil {
for _, msg := range d.LocalInterfaces.Warnings {
_, _ = fmt.Fprintf(w, "❗ Client: %s\n", msg.Message)
client = append(client, formatHealthMessage(msg))
}
}

if d.DisableDirect {
_, _ = fmt.Fprint(w, "❗ Direct connections are disabled locally, by `--disable-direct` or `CODER_DISABLE_DIRECT`\n")
return
if d.PingP2P && !d.Verbose {
return general, client, agent
}

if d.ConnInfo != nil && d.ConnInfo.DisableDirectConnections {
_, _ = fmt.Fprint(w, "❗ Your Coder administrator has blocked direct connections\n")
return
if d.DisableDirect {
general = append(general, "❗ Direct connections are disabled locally, by `--disable-direct` or `CODER_DISABLE_DIRECT`")
if !d.Verbose {
return general, client, agent
}
}

if d.ConnInfo != nil && d.ConnInfo.DERPMap != nil {
if !d.ConnInfo.DERPMap.HasSTUN() {
_, _ = fmt.Fprint(w, "✘ The DERP map is not configured to use STUN, which will prevent direct connections from starting outside of local networks\n")
} else if d.LocalNetInfo != nil && !d.LocalNetInfo.UDP {
_, _ = fmt.Fprint(w, "❗ Client could not connect to STUN over UDP, which may be preventing a direct connection\n")
if d.ConnInfo.DisableDirectConnections {
general = append(general, "❗ Your Coder administrator has blocked direct connections")
if !d.Verbose {
return general, client, agent
}
}

if !d.ConnInfo.DERPMap.HasSTUN() {
general = append(general, "✘ The DERP map is not configured to use STUN")
} else if d.LocalNetInfo != nil && !d.LocalNetInfo.UDP {
client = append(client, "✘ Client could not connect to STUN over UDP")
}

if d.LocalNetInfo != nil && d.LocalNetInfo.MappingVariesByDestIP.EqualBool(true) {
_, _ = fmt.Fprint(w, "❗ Client is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers\n")
client = append(client, "❗ Client is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers")
}

if d.AgentNetcheck != nil && d.AgentNetcheck.NetInfo != nil {
if d.AgentNetcheck.NetInfo.MappingVariesByDestIP.EqualBool(true) {
_, _ = fmt.Fprint(w, "❗ Agent is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers\n")
agent = append(agent, "❗ Agent is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers")
}
if !d.AgentNetcheck.NetInfo.UDP {
_, _ = fmt.Fprint(w, " Agent could not connect to STUN over UDP, which may be preventing a direct connection\n")
agent = append(agent, " Agent could not connect to STUN over UDP")
}
}

if d.ClientIPIsAWS {
_, _ = fmt.Fprint(w, "❗ Client IP address is within an AWS range, which is known to cause problems with forming direct connections (AWS uses hard NAT)\n")
client = append(client, "❗ Client IP address is within an AWS range (AWS uses hard NAT)")
}

if d.AgentIPIsAWS {
_, _ = fmt.Fprint(w, "❗ Agent IP address is within an AWS range, which is known to cause problems with forming direct connections (AWS uses hard NAT)\n")
agent = append(agent, "❗ Agent IP address is within an AWS range (AWS uses hard NAT)")
}
return general, client, agent
}

func formatHealthMessage(msg health.Message) string {
if msg.Code != health.CodeInterfaceSmallMTU {
return msg.Message
}
r := []rune(strings.Replace(msg.Message, ", which may cause problems with direct connections", "", -1))
out := string(append([]rune{unicode.ToUpper(r[0])}, r[1:]...))
return fmt.Sprintf("❗ %s, which may degrade the quality of direct connections", out)
}
48 changes: 34 additions & 14 deletions cli/cliui/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,9 @@ func TestConnDiagnostics(t *testing.T) {
{
name: "Direct",
diags: cliui.ConnDiags{
ConnInfo: &workspacesdk.AgentConnectionInfo{},
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
},
PingP2P: true,
LocalNetInfo: &tailcfg.NetInfo{},
},
Expand All @@ -697,7 +699,8 @@ func TestConnDiagnostics(t *testing.T) {
{
name: "DirectBlocked",
diags: cliui.ConnDiags{
ConnInfo: &workspacesdk.AgentConnectionInfo{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
DisableDirectConnections: true,
},
},
Expand All @@ -709,20 +712,20 @@ func TestConnDiagnostics(t *testing.T) {
{
name: "NoStun",
diags: cliui.ConnDiags{
ConnInfo: &workspacesdk.AgentConnectionInfo{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
},
LocalNetInfo: &tailcfg.NetInfo{},
},
want: []string{
`❗ You are connected via a DERP relay, not directly (p2p)`,
`✘ The DERP map is not configured to use STUN, which will prevent direct connections from starting outside of local networks`,
`✘ The DERP map is not configured to use STUN`,
},
},
{
name: "ClientHasStunNoUDP",
diags: cliui.ConnDiags{
ConnInfo: &workspacesdk.AgentConnectionInfo{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
999: {
Expand All @@ -741,13 +744,13 @@ func TestConnDiagnostics(t *testing.T) {
},
want: []string{
`❗ You are connected via a DERP relay, not directly (p2p)`,
` Client could not connect to STUN over UDP, which may be preventing a direct connection`,
` Client could not connect to STUN over UDP`,
},
},
{
name: "AgentHasStunNoUDP",
diags: cliui.ConnDiags{
ConnInfo: &workspacesdk.AgentConnectionInfo{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
999: {
Expand All @@ -768,12 +771,15 @@ func TestConnDiagnostics(t *testing.T) {
},
want: []string{
`❗ You are connected via a DERP relay, not directly (p2p)`,
` Agent could not connect to STUN over UDP, which may be preventing a direct connection`,
` Agent could not connect to STUN over UDP`,
},
},
{
name: "ClientHardNat",
diags: cliui.ConnDiags{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
},
LocalNetInfo: &tailcfg.NetInfo{
MappingVariesByDestIP: "true",
},
Expand All @@ -786,7 +792,9 @@ func TestConnDiagnostics(t *testing.T) {
{
name: "AgentHardNat",
diags: cliui.ConnDiags{
ConnInfo: &workspacesdk.AgentConnectionInfo{},
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
},
PingP2P: false,
LocalNetInfo: &tailcfg.NetInfo{},
AgentNetcheck: &healthsdk.AgentNetcheckReport{
Expand All @@ -801,6 +809,9 @@ func TestConnDiagnostics(t *testing.T) {
{
name: "AgentInterfaceWarnings",
diags: cliui.ConnDiags{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
},
PingP2P: true,
AgentNetcheck: &healthsdk.AgentNetcheckReport{
Interfaces: healthsdk.InterfacesReport{
Expand All @@ -813,13 +824,16 @@ func TestConnDiagnostics(t *testing.T) {
},
},
want: []string{
`❗ Agent: network interface eth0 has MTU 1280, (less than 1378), which may cause problems with direct connections`,
`❗ Network interface eth0 has MTU 1280, (less than 1378), which may degrade the quality of direct connections`,
`✔ You are connected directly (p2p)`,
},
},
{
name: "LocalInterfaceWarnings",
diags: cliui.ConnDiags{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
},
PingP2P: true,
LocalInterfaces: &healthsdk.InterfacesReport{
BaseReport: healthsdk.BaseReport{
Expand All @@ -830,30 +844,36 @@ func TestConnDiagnostics(t *testing.T) {
},
},
want: []string{
`❗ Client: network interface eth1 has MTU 1310, (less than 1378), which may cause problems with direct connections`,
`❗ Network interface eth1 has MTU 1310, (less than 1378), which may degrade the quality of direct connections`,
`✔ You are connected directly (p2p)`,
},
},
{
name: "ClientAWSIP",
diags: cliui.ConnDiags{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
},
ClientIPIsAWS: true,
AgentIPIsAWS: false,
},
want: []string{
`❗ You are connected via a DERP relay, not directly (p2p)`,
`❗ Client IP address is within an AWS range, which is known to cause problems with forming direct connections (AWS uses hard NAT)`,
`❗ Client IP address is within an AWS range (AWS uses hard NAT)`,
},
},
{
name: "AgentAWSIP",
diags: cliui.ConnDiags{
ConnInfo: workspacesdk.AgentConnectionInfo{
DERPMap: &tailcfg.DERPMap{},
},
ClientIPIsAWS: false,
AgentIPIsAWS: true,
},
want: []string{
`❗ You are connected via a DERP relay, not directly (p2p)`,
`❗ Agent IP address is within an AWS range, which is known to cause problems with forming direct connections (AWS uses hard NAT)`,
`❗ Agent IP address is within an AWS range (AWS uses hard NAT)`,
},
},
}
Expand All @@ -864,7 +884,7 @@ func TestConnDiagnostics(t *testing.T) {
r, w := io.Pipe()
go func() {
defer w.Close()
cliui.ConnDiagnostics(w, tc.diags)
tc.diags.Write(w)
}()
bytes, err := io.ReadAll(r)
require.NoError(t, err)
Expand Down
15 changes: 9 additions & 6 deletions cli/ping.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,21 +158,21 @@ func (r *RootCmd) ping() *serpent.Command {
PingP2P: didP2p,
DisableDirect: r.disableDirect,
LocalNetInfo: ni,
Verbose: r.verbose,
}

awsRanges, err := cliutil.FetchAWSIPRanges(ctx, cliutil.AWSIPRangesURL)
if err != nil {
_, _ = fmt.Fprintf(inv.Stdout, "Failed to retrieve AWS IP ranges: %v\n", err)
opts.Logger.Debug(inv.Context(), "failed to retrieve AWS IP ranges", slog.Error(err))
}

connDiags.ClientIPIsAWS = isAWSIP(awsRanges, ni)

connInfo, err := client.AgentConnectionInfoGeneric(ctx)
if err == nil {
connDiags.ConnInfo = &connInfo
} else {
_, _ = fmt.Fprintf(inv.Stdout, "Failed to retrieve connection info from server: %v\n", err)
if err != nil || connInfo.DERPMap == nil {
return xerrors.Errorf("Failed to retrieve connection info from server: %v\n", err)
}
connDiags.ConnInfo = connInfo
ifReport, err := healthsdk.RunInterfacesReport()
if err == nil {
connDiags.LocalInterfaces = &ifReport
Expand All @@ -193,7 +193,7 @@ func (r *RootCmd) ping() *serpent.Command {
}
}

cliui.ConnDiagnostics(inv.Stdout, connDiags)
connDiags.Write(inv.Stdout)
return nil
},
}
Expand Down Expand Up @@ -224,6 +224,9 @@ func (r *RootCmd) ping() *serpent.Command {
}

func isAWSIP(awsRanges *cliutil.AWSIPRanges, ni *tailcfg.NetInfo) bool {
if awsRanges == nil {
return false
}
if ni.GlobalV4 != "" {
ip, err := netip.ParseAddr(ni.GlobalV4)
if err == nil && awsRanges.CheckIP(ip) {
Expand Down
Loading