Skip to content

Commit 2d61d53

Browse files
authored
fix: detect JetBrains running on local ipv6 (coder#11653)
1 parent be43d62 commit 2d61d53

File tree

3 files changed

+65
-31
lines changed

3 files changed

+65
-31
lines changed

agent/agent_test.go

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -214,46 +214,59 @@ func TestAgent_Stats_Magic(t *testing.T) {
214214
_, b, _, ok := runtime.Caller(0)
215215
require.True(t, ok)
216216
dir := filepath.Join(filepath.Dir(b), "../scripts/echoserver/main.go")
217-
echoServerCmd := exec.Command("go", "run", dir,
218-
"-D", agentssh.MagicProcessCmdlineJetBrains)
219-
stdout, err := echoServerCmd.StdoutPipe()
220-
require.NoError(t, err)
221-
err = echoServerCmd.Start()
222-
require.NoError(t, err)
223-
defer echoServerCmd.Process.Kill()
224217

225-
// The echo server prints its port as the first line.
226-
sc := bufio.NewScanner(stdout)
227-
sc.Scan()
228-
remotePort := sc.Text()
218+
spawnServer := func(network string) (string, *exec.Cmd) {
219+
echoServerCmd := exec.Command("go", "run", dir,
220+
network, "-D", agentssh.MagicProcessCmdlineJetBrains)
221+
stdout, err := echoServerCmd.StdoutPipe()
222+
require.NoError(t, err)
223+
err = echoServerCmd.Start()
224+
require.NoError(t, err)
225+
t.Cleanup(func() {
226+
echoServerCmd.Process.Kill()
227+
})
228+
229+
// The echo server prints its port as the first line.
230+
sc := bufio.NewScanner(stdout)
231+
sc.Scan()
232+
return sc.Text(), echoServerCmd
233+
}
234+
235+
port4, cmd4 := spawnServer("tcp4")
236+
port6, cmd6 := spawnServer("tcp6")
229237

230238
//nolint:dogsled
231239
conn, _, stats, _, _ := setupAgent(t, agentsdk.Manifest{}, 0)
240+
defer conn.Close()
241+
232242
sshClient, err := conn.SSHClient(ctx)
233243
require.NoError(t, err)
234244

235-
tunneledConn, err := sshClient.Dial("tcp", fmt.Sprintf("127.0.0.1:%s", remotePort))
245+
tunnel4, err := sshClient.Dial("tcp4", fmt.Sprintf("127.0.0.1:%s", port4))
236246
require.NoError(t, err)
237-
t.Cleanup(func() {
238-
// always close on failure of test
239-
_ = conn.Close()
240-
_ = tunneledConn.Close()
241-
})
247+
defer tunnel4.Close()
248+
249+
tunnel6, err := sshClient.Dial("tcp6", fmt.Sprintf("[::]:%s", port6))
250+
require.NoError(t, err)
251+
defer tunnel6.Close()
242252

243253
require.Eventuallyf(t, func() bool {
244254
s, ok := <-stats
245255
t.Logf("got stats with conn open: ok=%t, ConnectionCount=%d, SessionCountJetBrains=%d",
246256
ok, s.ConnectionCount, s.SessionCountJetBrains)
247257
return ok && s.ConnectionCount > 0 &&
248-
s.SessionCountJetBrains == 1
258+
s.SessionCountJetBrains == 2
249259
}, testutil.WaitLong, testutil.IntervalFast,
250260
"never saw stats with conn open",
251261
)
252262

253263
// Kill the server and connection after checking for the echo.
254-
requireEcho(t, tunneledConn)
255-
_ = echoServerCmd.Process.Kill()
256-
_ = tunneledConn.Close()
264+
requireEcho(t, tunnel4)
265+
requireEcho(t, tunnel6)
266+
_ = cmd4.Process.Kill()
267+
_ = cmd6.Process.Kill()
268+
_ = tunnel4.Close()
269+
_ = tunnel6.Close()
257270

258271
require.Eventuallyf(t, func() bool {
259272
s, ok := <-stats

agent/agentssh/portinspection_supported.go

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package agentssh
44

55
import (
6+
"errors"
67
"fmt"
78
"os"
89

@@ -11,24 +12,33 @@ import (
1112
)
1213

1314
func getListeningPortProcessCmdline(port uint32) (string, error) {
14-
tabs, err := netstat.TCPSocks(func(s *netstat.SockTabEntry) bool {
15+
acceptFn := func(s *netstat.SockTabEntry) bool {
1516
return s.LocalAddr != nil && uint32(s.LocalAddr.Port) == port
16-
})
17-
if err != nil {
18-
return "", xerrors.Errorf("inspect port %d: %w", port, err)
1917
}
20-
if len(tabs) == 0 {
21-
return "", nil
18+
tabs, err := netstat.TCPSocks(acceptFn)
19+
tabs6, err6 := netstat.TCP6Socks(acceptFn)
20+
21+
// Only return the error if the other method found nothing.
22+
if (err != nil && len(tabs6) == 0) || (err6 != nil && len(tabs) == 0) {
23+
return "", xerrors.Errorf("inspect port %d: %w", port, errors.Join(err, err6))
2224
}
2325

24-
// Defensive check.
25-
if tabs[0].Process == nil {
26+
var proc *netstat.Process
27+
if len(tabs) > 0 {
28+
proc = tabs[0].Process
29+
} else if len(tabs6) > 0 {
30+
proc = tabs6[0].Process
31+
}
32+
if proc == nil {
33+
// Either nothing is listening on this port or we were unable to read the
34+
// process details (permission issues reading /proc/$pid/* potentially).
35+
// Or, perhaps /proc/net/tcp{,6} is not listing the port for some reason.
2636
return "", nil
2737
}
2838

2939
// The process name provided by go-netstat does not include the full command
3040
// line so grab that instead.
31-
pid := tabs[0].Process.Pid
41+
pid := proc.Pid
3242
data, err := os.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
3343
if err != nil {
3444
return "", xerrors.Errorf("read /proc/%d/cmdline: %w", pid, err)

scripts/echoserver/main.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,21 @@ import (
99
"io"
1010
"log"
1111
"net"
12+
"os"
1213
)
1314

1415
func main() {
15-
l, err := net.Listen("tcp", "127.0.0.1:0")
16+
network := os.Args[1]
17+
var address string
18+
switch network {
19+
case "tcp4":
20+
address = "127.0.0.1"
21+
case "tcp6":
22+
address = "[::]"
23+
default:
24+
log.Fatalf("invalid network: %s", network)
25+
}
26+
l, err := net.Listen(network, address+":0")
1627
if err != nil {
1728
log.Fatalf("listen error: err=%s", err)
1829
}

0 commit comments

Comments
 (0)