Skip to content

Commit 8ba6601

Browse files
acmoreclaude
andcommitted
feat: wire three-layer health monitoring into ssh-proxy command
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b4a56b3 commit 8ba6601

1 file changed

Lines changed: 41 additions & 11 deletions

File tree

internal/cli/ssh.go

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -541,22 +541,48 @@ func newSSHProxyCmd(opts *Options) *cobra.Command {
541541
if err != nil {
542542
return err
543543
}
544-
pfKeepAliveCtx, pfKeepAliveCancel := context.WithCancel(context.Background())
545-
startPortForwardKeepalive(pfKeepAliveCtx, usedLocalPort, 10*time.Second, nil)
546-
defer pfKeepAliveCancel()
547544
if cancelPF != nil {
548545
defer cancelPF()
549546
}
547+
550548
slog.Debug("ssh-proxy dialing local port-forward", "port", usedLocalPort)
551549
conn, err := waitDialLocal(usedLocalPort, 10*time.Second)
552550
if err != nil {
553551
slog.Debug("ssh-proxy dial failed", "error", err)
554552
return err
555553
}
556554
defer conn.Close()
555+
startTime := time.Now()
556+
557+
// Layer 1: TCP keepalive tuning (5s period, KEEPCNT=2 where supported)
558+
setTCPKeepAliveProxyTuning(conn)
559+
560+
// Layer 3: Port-forward keepalive probe with onDegraded wired up
561+
pfKeepAliveCtx, pfKeepAliveCancel := context.WithCancel(cmd.Context())
562+
defer pfKeepAliveCancel()
563+
startPortForwardKeepalive(pfKeepAliveCtx, usedLocalPort, 10*time.Second, func() {
564+
slog.Info("ssh-proxy port-forward degraded, closing connection",
565+
"port", usedLocalPort,
566+
"uptime", time.Since(startTime).Round(time.Second),
567+
)
568+
_ = conn.Close()
569+
})
570+
557571
slog.Debug("ssh-proxy connection established", "localAddr", conn.LocalAddr(), "remoteAddr", conn.RemoteAddr())
558572

559-
var wg sync.WaitGroup
573+
// Close conn on context cancellation (SIGINT/SIGTERM) to unblock io.Copy goroutines
574+
go func() {
575+
<-cmd.Context().Done()
576+
_ = conn.Close()
577+
}()
578+
579+
// Layer 2: Data flow monitor
580+
var lastData atomic.Int64
581+
lastData.Store(time.Now().UnixNano())
582+
watchdogCtx, watchdogCancel := context.WithCancel(cmd.Context())
583+
defer watchdogCancel()
584+
go proxyDataFlowWatchdog(watchdogCtx, conn, &lastData, 5*time.Second, 15*time.Second)
585+
560586
var copyErr error
561587
var once sync.Once
562588
done := make(chan struct{})
@@ -567,11 +593,9 @@ func newSSHProxyCmd(opts *Options) *cobra.Command {
567593
slog.Debug("ssh-proxy copy error", "error", err)
568594
once.Do(func() { copyErr = err })
569595
}
570-
wg.Add(2)
571596
go func() {
572-
defer wg.Done()
573597
slog.Debug("ssh-proxy starting copy: stdin -> conn")
574-
_, err := io.Copy(conn, os.Stdin)
598+
_, err := monitoredCopy(conn, os.Stdin, &lastData)
575599
setErr(err)
576600
slog.Debug("ssh-proxy finished copy: stdin -> conn")
577601
select {
@@ -581,17 +605,23 @@ func newSSHProxyCmd(opts *Options) *cobra.Command {
581605
}
582606
}()
583607
go func() {
584-
defer wg.Done()
585608
slog.Debug("ssh-proxy starting copy: conn -> stdout")
586-
_, err := io.Copy(os.Stdout, conn)
609+
_, err := monitoredCopy(os.Stdout, conn, &lastData)
587610
setErr(err)
588611
slog.Debug("ssh-proxy finished copy: conn -> stdout")
589612
close(done)
590613
_ = conn.Close()
591614
}()
592615
<-done
593-
slog.Debug("ssh-proxy session finished", "error", copyErr)
594-
return copyErr
616+
watchdogCancel()
617+
slog.Info("ssh-proxy session finished",
618+
"error", copyErr,
619+
"uptime", time.Since(startTime).Round(time.Second),
620+
)
621+
if copyErr != nil {
622+
return fmt.Errorf("%w: %v", ErrProxyHealthDisconnect, copyErr)
623+
}
624+
return nil
595625
})
596626
},
597627
}

0 commit comments

Comments
 (0)