Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit15438e6

Browse files
committed
feat(cli): add aws check to ping p2p diagnostics
1 parent8c15192 commit15438e6

File tree

5 files changed

+275
-3
lines changed

5 files changed

+275
-3
lines changed

‎cli/cliui/agent.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,8 @@ type ConnDiags struct {
357357
LocalNetInfo*tailcfg.NetInfo
358358
LocalInterfaces*healthsdk.InterfacesReport
359359
AgentNetcheck*healthsdk.AgentNetcheckReport
360+
ClientIPIsAWSbool
361+
AgentIPIsAWSbool
360362
// TODO: More diagnostics
361363
}
362364

@@ -375,9 +377,9 @@ func ConnDiagnostics(w io.Writer, d ConnDiags) {
375377

376378
ifd.PingP2P {
377379
_,_=fmt.Fprint(w,"✔ You are connected directly (p2p)\n")
378-
return
380+
}else {
381+
_,_=fmt.Fprint(w,"❗ You are connected via a DERP relay, not directly (p2p)\n")
379382
}
380-
_,_=fmt.Fprint(w,"❗ You are connected via a DERP relay, not directly (p2p)\n")
381383

382384
ifd.DisableDirect {
383385
_,_=fmt.Fprint(w,"❗ Direct connections are disabled locally, by `--disable-direct` or `CODER_DISABLE_DIRECT`\n")
@@ -400,4 +402,12 @@ func ConnDiagnostics(w io.Writer, d ConnDiags) {
400402
ifd.AgentNetcheck!=nil&&d.AgentNetcheck.NetInfo!=nil&&d.AgentNetcheck.NetInfo.MappingVariesByDestIP.EqualBool(true) {
401403
_,_=fmt.Fprint(w,"❗ Agent is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers\n")
402404
}
405+
406+
ifd.ClientIPIsAWS {
407+
_,_=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")
408+
}
409+
410+
ifd.AgentIPIsAWS {
411+
_,_=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")
412+
}
403413
}

‎cli/cliui/agent_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,28 @@ func TestConnDiagnostics(t *testing.T) {
782782
`✔ You are connected directly (p2p)`,
783783
},
784784
},
785+
{
786+
name:"ClientAWSIP",
787+
diags: cliui.ConnDiags{
788+
ClientIPIsAWS:true,
789+
AgentIPIsAWS:false,
790+
},
791+
want: []string{
792+
`❗ You are connected via a DERP relay, not directly (p2p)`,
793+
`❗ Client IP address is within an AWS range, which is known to cause problems with forming direct connections (AWS uses hard NAT)`,
794+
},
795+
},
796+
{
797+
name:"AgentAWSIP",
798+
diags: cliui.ConnDiags{
799+
ClientIPIsAWS:false,
800+
AgentIPIsAWS:true,
801+
},
802+
want: []string{
803+
`❗ You are connected via a DERP relay, not directly (p2p)`,
804+
`❗ Agent IP address is within an AWS range, which is known to cause problems with forming direct connections (AWS uses hard NAT)`,
805+
},
806+
},
785807
}
786808
for_,tc:=rangetestCases {
787809
tc:=tc

‎cli/cliutil/awscheck.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package cliutil
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"io"
7+
"net/http"
8+
"net/netip"
9+
"time"
10+
11+
"golang.org/x/xerrors"
12+
)
13+
14+
constAWSIPRangesURL="https://ip-ranges.amazonaws.com/ip-ranges.json"
15+
16+
typeawsIPv4Prefixstruct {
17+
Prefixstring`json:"ip_prefix"`
18+
Regionstring`json:"region"`
19+
Servicestring`json:"service"`
20+
NetworkBorderGroupstring`json:"network_border_group"`
21+
}
22+
23+
typeawsIPv6Prefixstruct {
24+
Prefixstring`json:"ipv6_prefix"`
25+
Regionstring`json:"region"`
26+
Servicestring`json:"service"`
27+
NetworkBorderGroupstring`json:"network_border_group"`
28+
}
29+
30+
typeAWSIPRangesstruct {
31+
V4 []netip.Prefix
32+
V6 []netip.Prefix
33+
}
34+
35+
typeawsIPRangesResponsestruct {
36+
SyncTokenstring`json:"syncToken"`
37+
CreateDatestring`json:"createDate"`
38+
IPV4Prefixes []awsIPv4Prefix`json:"prefixes"`
39+
IPV6Prefixes []awsIPv6Prefix`json:"ipv6_prefixes"`
40+
}
41+
42+
funcFetchAWSIPRanges(ctx context.Context,urlstring) (*AWSIPRanges,error) {
43+
client:=&http.Client{}
44+
reqCtx,reqCancel:=context.WithTimeout(ctx,5*time.Second)
45+
deferreqCancel()
46+
req,_:=http.NewRequestWithContext(reqCtx,http.MethodGet,url,nil)
47+
resp,err:=client.Do(req)
48+
iferr!=nil {
49+
returnnil,err
50+
}
51+
deferresp.Body.Close()
52+
53+
ifresp.StatusCode!=http.StatusOK {
54+
b,_:=io.ReadAll(resp.Body)
55+
returnnil,xerrors.Errorf("unexpected status code %d: %s",resp.StatusCode,b)
56+
}
57+
58+
varbodyawsIPRangesResponse
59+
err=json.NewDecoder(resp.Body).Decode(&body)
60+
iferr!=nil {
61+
returnnil,xerrors.Errorf("json decode: %w",err)
62+
}
63+
64+
out:=&AWSIPRanges{
65+
V4:make([]netip.Prefix,0,len(body.IPV4Prefixes)),
66+
V6:make([]netip.Prefix,0,len(body.IPV6Prefixes)),
67+
}
68+
69+
for_,p:=rangebody.IPV4Prefixes {
70+
prefix,err:=netip.ParsePrefix(p.Prefix)
71+
iferr!=nil {
72+
returnnil,xerrors.Errorf("parse ip prefix: %w",err)
73+
}
74+
ifprefix.Addr().Is6() {
75+
returnnil,xerrors.Errorf("ipv4 prefix contains ipv6 address: %s",p.Prefix)
76+
}
77+
out.V4=append(out.V4,prefix)
78+
}
79+
80+
for_,p:=rangebody.IPV6Prefixes {
81+
prefix,err:=netip.ParsePrefix(p.Prefix)
82+
iferr!=nil {
83+
returnnil,xerrors.Errorf("parse ip prefix: %w",err)
84+
}
85+
ifprefix.Addr().Is4() {
86+
returnnil,xerrors.Errorf("ipv6 prefix contains ipv4 address: %s",p.Prefix)
87+
}
88+
out.V6=append(out.V6,prefix)
89+
}
90+
91+
returnout,nil
92+
}
93+
94+
// CheckIP checks if the given IP address is an AWS IP.
95+
func (r*AWSIPRanges)CheckIP(ip netip.Addr)bool {
96+
ifip.IsLoopback()||ip.IsLinkLocalMulticast()||ip.IsLinkLocalUnicast()||ip.IsPrivate() {
97+
returnfalse
98+
}
99+
100+
ifip.Is4() {
101+
for_,p:=ranger.V4 {
102+
ifp.Contains(ip) {
103+
returntrue
104+
}
105+
}
106+
}else {
107+
for_,p:=ranger.V6 {
108+
ifp.Contains(ip) {
109+
returntrue
110+
}
111+
}
112+
}
113+
returnfalse
114+
}

‎cli/cliutil/awscheck_internal_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package cliutil
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"net/http/httptest"
7+
"net/netip"
8+
"testing"
9+
10+
"github.com/stretchr/testify/require"
11+
12+
"github.com/coder/coder/v2/coderd/httpapi"
13+
"github.com/coder/coder/v2/testutil"
14+
)
15+
16+
funcTestIPV4Check(t*testing.T) {
17+
t.Parallel()
18+
srv:=httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter,r*http.Request) {
19+
httpapi.Write(context.Background(),w,http.StatusOK,awsIPRangesResponse{
20+
IPV4Prefixes: []awsIPv4Prefix{
21+
{
22+
Prefix:"3.24.0.0/14",
23+
},
24+
{
25+
Prefix:"15.230.15.29/32",
26+
},
27+
{
28+
Prefix:"47.128.82.100/31",
29+
},
30+
},
31+
IPV6Prefixes: []awsIPv6Prefix{
32+
{
33+
Prefix:"2600:9000:5206::/48",
34+
},
35+
{
36+
Prefix:"2406:da70:8800::/40",
37+
},
38+
{
39+
Prefix:"2600:1f68:5000::/40",
40+
},
41+
},
42+
})
43+
}))
44+
ctx:=testutil.Context(t,testutil.WaitShort)
45+
ranges,err:=FetchAWSIPRanges(ctx,srv.URL)
46+
require.NoError(t,err)
47+
48+
t.Run("Private/IPV4",func(t*testing.T) {
49+
t.Parallel()
50+
ip,err:=netip.ParseAddr("192.168.0.1")
51+
require.NoError(t,err)
52+
isAws:=ranges.CheckIP(ip)
53+
require.False(t,isAws)
54+
})
55+
56+
t.Run("AWS/IPV4",func(t*testing.T) {
57+
t.Parallel()
58+
ip,err:=netip.ParseAddr("3.25.61.113")
59+
require.NoError(t,err)
60+
isAws:=ranges.CheckIP(ip)
61+
require.True(t,isAws)
62+
})
63+
64+
t.Run("NonAWS/IPV4",func(t*testing.T) {
65+
t.Parallel()
66+
ip,err:=netip.ParseAddr("159.196.123.40")
67+
require.NoError(t,err)
68+
isAws:=ranges.CheckIP(ip)
69+
require.False(t,isAws)
70+
})
71+
72+
t.Run("Private/IPV6",func(t*testing.T) {
73+
t.Parallel()
74+
ip,err:=netip.ParseAddr("::1")
75+
require.NoError(t,err)
76+
isAws:=ranges.CheckIP(ip)
77+
require.False(t,isAws)
78+
})
79+
80+
t.Run("AWS/IPV6",func(t*testing.T) {
81+
t.Parallel()
82+
ip,err:=netip.ParseAddr("2600:9000:5206:0001:0000:0000:0000:0001")
83+
require.NoError(t,err)
84+
isAws:=ranges.CheckIP(ip)
85+
require.True(t,isAws)
86+
})
87+
88+
t.Run("NonAWS/IPV6",func(t*testing.T) {
89+
t.Parallel()
90+
ip,err:=netip.ParseAddr("2403:5807:885f:0:a544:49d4:58f8:aedf")
91+
require.NoError(t,err)
92+
isAws:=ranges.CheckIP(ip)
93+
require.False(t,isAws)
94+
})
95+
}

‎cli/ping.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@ import (
55
"errors"
66
"fmt"
77
"net/http"
8+
"net/netip"
89
"time"
910

1011
"golang.org/x/xerrors"
12+
"tailscale.com/tailcfg"
1113

1214
"cdr.dev/slog"
1315
"cdr.dev/slog/sloggers/sloghuman"
1416

1517
"github.com/coder/pretty"
1618

1719
"github.com/coder/coder/v2/cli/cliui"
20+
"github.com/coder/coder/v2/cli/cliutil"
1821
"github.com/coder/coder/v2/codersdk"
1922
"github.com/coder/coder/v2/codersdk/healthsdk"
2023
"github.com/coder/coder/v2/codersdk/workspacesdk"
@@ -150,11 +153,20 @@ func (r *RootCmd) ping() *serpent.Command {
150153
diags:=conn.GetPeerDiagnostics()
151154
cliui.PeerDiagnostics(inv.Stdout,diags)
152155

156+
ni:=conn.GetNetInfo()
153157
connDiags:= cliui.ConnDiags{
154158
PingP2P:didP2p,
155159
DisableDirect:r.disableDirect,
156-
LocalNetInfo:conn.GetNetInfo(),
160+
LocalNetInfo:ni,
157161
}
162+
163+
awsRanges,err:=cliutil.FetchAWSIPRanges(ctx,cliutil.AWSIPRangesURL)
164+
iferr!=nil {
165+
_,_=fmt.Fprintf(inv.Stdout,"Failed to retrieve AWS IP ranges: %v\n",err)
166+
}
167+
168+
connDiags.ClientIPIsAWS=isAWSIP(awsRanges,ni)
169+
158170
connInfo,err:=client.AgentConnectionInfoGeneric(ctx)
159171
iferr==nil {
160172
connDiags.ConnInfo=&connInfo
@@ -167,9 +179,11 @@ func (r *RootCmd) ping() *serpent.Command {
167179
}else {
168180
_,_=fmt.Fprintf(inv.Stdout,"Failed to retrieve local interfaces report: %v\n",err)
169181
}
182+
170183
agentNetcheck,err:=conn.Netcheck(ctx)
171184
iferr==nil {
172185
connDiags.AgentNetcheck=&agentNetcheck
186+
connDiags.AgentIPIsAWS=isAWSIP(awsRanges,agentNetcheck.NetInfo)
173187
}else {
174188
varsdkErr*codersdk.Error
175189
iferrors.As(err,&sdkErr)&&sdkErr.StatusCode()==http.StatusNotFound {
@@ -178,6 +192,7 @@ func (r *RootCmd) ping() *serpent.Command {
178192
_,_=fmt.Fprintf(inv.Stdout,"Failed to retrieve connection report from agent: %v\n",err)
179193
}
180194
}
195+
181196
cliui.ConnDiagnostics(inv.Stdout,connDiags)
182197
returnnil
183198
},
@@ -207,3 +222,19 @@ func (r *RootCmd) ping() *serpent.Command {
207222
}
208223
returncmd
209224
}
225+
226+
funcisAWSIP(awsRanges*cliutil.AWSIPRanges,ni*tailcfg.NetInfo)bool {
227+
ifni.GlobalV4!="" {
228+
ip,err:=netip.ParseAddr(ni.GlobalV4)
229+
iferr==nil&&awsRanges.CheckIP(ip) {
230+
returntrue
231+
}
232+
}
233+
ifni.GlobalV6!="" {
234+
ip,err:=netip.ParseAddr(ni.GlobalV6)
235+
iferr==nil&&awsRanges.CheckIP(ip) {
236+
returntrue
237+
}
238+
}
239+
returnfalse
240+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp