- 为 LoginInput 新增 DialTimeout,明确区分【TCP/proxy/ssh-agent 拨号超时】和【SSH 握手/认证超时】 - 将 Timeout 收口为握手/认证阶段超时,0 表示不限制,不再在登录入口自动回填默认值 - 新增 effectiveLoginTimeout/effectiveDialTimeout,统一超时决策逻辑 - 调整 login 流程,仅对 login context、ssh.ClientConfig 和握手阶段连接 deadline 使用认证超时 - 调整 transport 拨号链路,默认 TCP dial、proxy dial 与 ssh-agent 建连统一改用 DialTimeout - 修正 agent forwarding 初始化仍错误复用 LoginInfo.Timeout 的问题 - 保持 LoginSimple 的直观行为:传入 timeout 时同时映射到 Timeout 和 DialTimeout - 新增 login_timeout_test,覆盖零值不回填、DialTimeout 优先级,以及 ssh-agent 认证路径使用拨号超时的回归测试
208 lines
5.6 KiB
Go
208 lines
5.6 KiB
Go
package starssh
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"io"
|
|
"net"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/pkg/sftp"
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
const (
|
|
defaultSSHPort = 22
|
|
defaultLoginTimeout = 5 * time.Second
|
|
defaultKeepAliveTimeout = 3 * time.Second
|
|
defaultShellPollInterval = 120 * time.Millisecond
|
|
defaultShellSetupDelay = 200 * time.Millisecond
|
|
defaultShellSetupTimeout = 3 * time.Second
|
|
defaultShellWaitTimeout = 30 * time.Second
|
|
defaultShellPromptToken = "__STARSSH_PROMPT__>"
|
|
defaultPTYTerm = "xterm"
|
|
defaultPTYRows = 500
|
|
defaultPTYColumns = 250
|
|
|
|
defaultTransferBufferSize = 1024 * 1024
|
|
|
|
defaultExecStreamMaxPendingChunks = 256
|
|
defaultExecStreamMaxPendingBytes = 4 * 1024 * 1024
|
|
)
|
|
|
|
type DialContextFunc func(ctx context.Context, network, address string) (net.Conn, error)
|
|
|
|
type ProxyType string
|
|
|
|
const (
|
|
ProxyTypeSOCKS5 ProxyType = "socks5"
|
|
ProxyTypeHTTPConnect ProxyType = "http_connect"
|
|
)
|
|
|
|
type ProxyConfig struct {
|
|
Type ProxyType
|
|
Addr string
|
|
Username string
|
|
Password string
|
|
Timeout time.Duration
|
|
}
|
|
|
|
type AuthMethodKind string
|
|
|
|
const (
|
|
AuthMethodPrivateKey AuthMethodKind = "private_key"
|
|
AuthMethodPassword AuthMethodKind = "password"
|
|
AuthMethodKeyboardInteractive AuthMethodKind = "keyboard_interactive"
|
|
AuthMethodSSHAgent AuthMethodKind = "ssh_agent"
|
|
)
|
|
|
|
type StarSSH struct {
|
|
stateMu sync.RWMutex
|
|
Client *ssh.Client
|
|
PublicKey ssh.PublicKey
|
|
PubkeyBase64 string
|
|
Hostname string
|
|
RemoteAddr net.Addr
|
|
Banner string
|
|
LoginInfo LoginInput
|
|
online bool
|
|
upstream *StarSSH
|
|
sftpClient *sftp.Client
|
|
sftpMu sync.Mutex
|
|
agentForwardMu sync.Mutex
|
|
agentForwarder io.Closer
|
|
keepaliveMu sync.Mutex
|
|
keepaliveStop chan struct{}
|
|
keepaliveDone chan struct{}
|
|
closing atomic.Bool
|
|
}
|
|
|
|
type LoginInput struct {
|
|
KeyExchanges []string
|
|
Ciphers []string
|
|
MACs []string
|
|
User string
|
|
Password string
|
|
PasswordCallback func() (string, error)
|
|
KeyboardInteractiveCallback ssh.KeyboardInteractiveChallenge
|
|
Prikey string
|
|
PrikeyPwd string
|
|
DisableSSHAgent bool
|
|
ForwardSSHAgent bool
|
|
AuthOrder []AuthMethodKind
|
|
Addr string
|
|
Port int
|
|
// Timeout limits the SSH handshake/authentication phase after a TCP connection has
|
|
// already been established. Zero means no authentication timeout.
|
|
Timeout time.Duration
|
|
// DialTimeout limits outbound dial steps such as TCP connect, proxy connect, and
|
|
// local ssh-agent socket connect. Zero falls back to Timeout when set, otherwise
|
|
// uses the package default dial timeout. Negative disables the default dial timeout.
|
|
DialTimeout time.Duration
|
|
DialContext DialContextFunc
|
|
Proxy *ProxyConfig
|
|
Jump *LoginInput
|
|
KeepAliveInterval time.Duration
|
|
KeepAliveTimeout time.Duration
|
|
HostKeyCallback func(string, net.Addr, ssh.PublicKey) error
|
|
BannerCallback func(string) error
|
|
}
|
|
|
|
// StarShell keeps the legacy prompt-driven helper for POSIX-style scripted shell interactions.
|
|
// It is not a generic cross-shell abstraction; for product-grade interactive terminals, prefer TerminalSession.
|
|
type StarShell struct {
|
|
Keyword string
|
|
UseWaitDefault bool
|
|
WaitTimeout time.Duration
|
|
Session *ssh.Session
|
|
in io.Writer
|
|
out *bufio.Reader
|
|
er *bufio.Reader
|
|
outbyte []byte
|
|
errbyte []byte
|
|
lastout int64
|
|
errors error
|
|
isprint bool
|
|
isfuncs bool
|
|
iscolor bool
|
|
isecho bool
|
|
rw sync.RWMutex
|
|
funcs func(string)
|
|
writeMu sync.Mutex
|
|
commandMu sync.Mutex
|
|
closeOnce sync.Once
|
|
promptToken string
|
|
}
|
|
|
|
type TerminalConfig struct {
|
|
Term string
|
|
Rows int
|
|
Columns int
|
|
Modes ssh.TerminalModes
|
|
}
|
|
|
|
type TerminalControl byte
|
|
|
|
const (
|
|
TerminalControlInterrupt TerminalControl = 0x03
|
|
TerminalControlEOF TerminalControl = 0x04
|
|
TerminalControlBell TerminalControl = 0x07
|
|
TerminalControlBackspace TerminalControl = 0x08
|
|
TerminalControlLineKill TerminalControl = 0x15
|
|
TerminalControlQuit TerminalControl = 0x1c
|
|
TerminalControlSuspend TerminalControl = 0x1a
|
|
TerminalControlPauseOutput TerminalControl = 0x13
|
|
TerminalControlResumeOutput TerminalControl = 0x11
|
|
)
|
|
|
|
type TerminalCloseReason string
|
|
|
|
const (
|
|
TerminalCloseReasonUnknown TerminalCloseReason = ""
|
|
TerminalCloseReasonExit TerminalCloseReason = "exit"
|
|
TerminalCloseReasonSignal TerminalCloseReason = "signal"
|
|
TerminalCloseReasonClosed TerminalCloseReason = "closed"
|
|
TerminalCloseReasonContextCanceled TerminalCloseReason = "context_canceled"
|
|
TerminalCloseReasonDeadlineExceeded TerminalCloseReason = "deadline_exceeded"
|
|
TerminalCloseReasonTransportError TerminalCloseReason = "transport_error"
|
|
)
|
|
|
|
type TerminalExitInfo struct {
|
|
ExitCode int
|
|
ExitSignal string
|
|
ExitMessage string
|
|
Reason TerminalCloseReason
|
|
}
|
|
|
|
type TerminalSession struct {
|
|
Session *ssh.Session
|
|
ID string
|
|
Label string
|
|
Metadata map[string]string
|
|
stdin io.WriteCloser
|
|
stdout io.Reader
|
|
stderr io.Reader
|
|
|
|
attachMu sync.RWMutex
|
|
in io.Reader
|
|
out io.Writer
|
|
errOut io.Writer
|
|
|
|
runOnce sync.Once
|
|
runDone chan struct{}
|
|
runErr error
|
|
|
|
waitOnce sync.Once
|
|
waitDone chan struct{}
|
|
|
|
stateMu sync.RWMutex
|
|
waitErr error
|
|
exitInfo TerminalExitInfo
|
|
closeReason TerminalCloseReason
|
|
closeErr error
|
|
|
|
closeOnce sync.Once
|
|
}
|