You've already forked step-ca-cli
mirror of
https://github.com/smallstep/cli.git
synced 2026-10-24 11:19:19 +03:00
proxyDirect waited for both copy goroutines via wg.Wait(). When the server closes the connection mid-session, the stdin->conn goroutine stays blocked reading a stdin that never reaches EOF (the ssh client holds the proxycommand's stdin open until it exits), deadlocking until an OS-level timeout reaps the process. Return as soon as either direction finishes instead. Extracted a testable proxyDirectWithIO and added a regression test. Fixes #1641 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
56 lines
1.5 KiB
Go
56 lines
1.5 KiB
Go
package ssh
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// Test_proxyDirectWithIO_serverClosesBeforeStdin reproduces smallstep/cli#1641:
|
|
// when the server closes the connection before the client has closed stdin, the
|
|
// proxycommand must still return promptly. Previously it would block in
|
|
// wg.Wait() forever because the stdin->conn goroutine stayed blocked reading a
|
|
// stdin that never reaches EOF (the ssh client keeps it open until the
|
|
// proxycommand exits).
|
|
func Test_proxyDirectWithIO_serverClosesBeforeStdin(t *testing.T) {
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
defer ln.Close()
|
|
|
|
// Server sends some data and immediately closes the connection.
|
|
go func() {
|
|
conn, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
conn.Write([]byte("hello"))
|
|
conn.Close()
|
|
}()
|
|
|
|
host, port, err := net.SplitHostPort(ln.Addr().String())
|
|
require.NoError(t, err)
|
|
|
|
// stdin that never reaches EOF, simulating the ssh client keeping the
|
|
// proxycommand's stdin open for the lifetime of the session.
|
|
stdinR, stdinW := io.Pipe()
|
|
defer stdinW.Close() // write end intentionally left open during the call
|
|
|
|
var stdout bytes.Buffer
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- proxyDirectWithIO(host, port, stdinR, &stdout)
|
|
}()
|
|
|
|
select {
|
|
case err := <-done:
|
|
require.NoError(t, err)
|
|
require.Equal(t, "hello", stdout.String())
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("proxyDirectWithIO did not return after the server closed the connection")
|
|
}
|
|
}
|