Skip to content

Commit a2abede

Browse files
committed
discard pending input
1 parent cce42ad commit a2abede

9 files changed

Lines changed: 291 additions & 139 deletions

File tree

go.mod

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,21 @@ go 1.25
44

55
require (
66
github.com/UserExistsError/conpty v0.1.4
7-
github.com/alessio/shellescape v1.4.2
87
github.com/creack/pty v1.1.24
98
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
10-
github.com/quic-go/quic-go v0.57.0
9+
github.com/quic-go/quic-go v0.57.1
1110
github.com/stretchr/testify v1.11.1
12-
github.com/xtaci/kcp-go/v5 v5.6.40
13-
github.com/xtaci/smux v1.5.43
11+
github.com/trzsz/shellescape v1.6.0
12+
github.com/xtaci/kcp-go/v5 v5.6.45
13+
github.com/xtaci/smux v1.5.44
1414
golang.org/x/crypto v0.45.0
1515
golang.org/x/sys v0.38.0
1616
)
1717

1818
require (
1919
github.com/davecgh/go-spew v1.1.1 // indirect
2020
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
21-
github.com/klauspost/reedsolomon v1.12.5 // indirect
21+
github.com/klauspost/reedsolomon v1.12.6 // indirect
2222
github.com/kr/text v0.2.0 // indirect
2323
github.com/pkg/errors v0.9.1 // indirect
2424
github.com/pmezard/go-difflib v1.0.0 // indirect

go.sum

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
22
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
33
github.com/UserExistsError/conpty v0.1.4 h1:+3FhJhiqhyEJa+K5qaK3/w6w+sN3Nh9O9VbJyBS02to=
44
github.com/UserExistsError/conpty v0.1.4/go.mod h1:PDglKIkX3O/2xVk0MV9a6bCWxRmPVfxqZoTG/5sSd9I=
5-
github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0=
6-
github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
75
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
86
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
97
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
@@ -34,8 +32,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU
3432
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
3533
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
3634
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
37-
github.com/klauspost/reedsolomon v1.12.5 h1:4cJuyH926If33BeDgiZpI5OU0pE+wUHZvMSyNGqN73Y=
38-
github.com/klauspost/reedsolomon v1.12.5/go.mod h1:LkXRjLYGM8K/iQfujYnaPeDmhZLqkrGUyG9p7zs5L68=
35+
github.com/klauspost/reedsolomon v1.12.6 h1:8pqE9aECQG/ZFitiUD1xK/E83zwosBAZtE3UbuZM8TQ=
36+
github.com/klauspost/reedsolomon v1.12.6/go.mod h1:ggJT9lc71Vu+cSOPBlxGvBN6TfAS77qB4fp8vJ05NSA=
3937
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
4038
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
4139
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -45,20 +43,22 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
4543
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4644
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
4745
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
48-
github.com/quic-go/quic-go v0.57.0 h1:AsSSrrMs4qI/hLrKlTH/TGQeTMY0ib1pAOX7vA3AdqE=
49-
github.com/quic-go/quic-go v0.57.0/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
46+
github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10=
47+
github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
5048
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
5149
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
5250
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
5351
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
5452
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
5553
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
56-
github.com/xtaci/kcp-go/v5 v5.6.40 h1:0zaO+ws+dXV3VpUfL7f8z0s5J075RFtjXYu9hK6UVUQ=
57-
github.com/xtaci/kcp-go/v5 v5.6.40/go.mod h1:h7RBeE7Vm9xGLE0gK1JITz09q9rO2oFqDyciQhqlgfg=
54+
github.com/trzsz/shellescape v1.6.0 h1:Xtbjtc1X5xnQHQnzXvmJj+X0I3t7iPMOyeylKitzj+8=
55+
github.com/trzsz/shellescape v1.6.0/go.mod h1:HNPLv0Eucq99+zd8JndNxbg8e0vAWDTwMLBSss79rcw=
56+
github.com/xtaci/kcp-go/v5 v5.6.45 h1:Q+o8k2JB1T1FAgaktidpdwFrsGM8or3RDl2V9PYfffQ=
57+
github.com/xtaci/kcp-go/v5 v5.6.45/go.mod h1:h7RBeE7Vm9xGLE0gK1JITz09q9rO2oFqDyciQhqlgfg=
5858
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
5959
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
60-
github.com/xtaci/smux v1.5.43 h1:+H+7Ofg5GpK7T0u2Me1Ljhczc2SH+kMm4r5DVY4TfTk=
61-
github.com/xtaci/smux v1.5.43/go.mod h1:IGQ9QYrBphmb/4aTnLEcJby0TNr3NV+OslIOMrX825Q=
60+
github.com/xtaci/smux v1.5.44 h1:7T61zLfFX1jokXj6d+lPaxHnVwgYiJ7EN94DAudKqpg=
61+
github.com/xtaci/smux v1.5.44/go.mod h1:IGQ9QYrBphmb/4aTnLEcJby0TNr3NV+OslIOMrX825Q=
6262
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
6363
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
6464
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

tsshd/bus.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ func handleBusEvent(stream net.Conn) {
123123
_ = sendBusCommand("alive")
124124
case "alive2":
125125
err = handleAliveEvent(stream)
126+
case "setting":
127+
err = handleSettingEvent(stream)
126128
default:
127129
if err := handleUnknownEvent(stream, command); err != nil {
128130
warning("handle bus command [%s] failed: %v. You may need to upgrade tsshd.", command, err)
@@ -146,6 +148,17 @@ func handleAliveEvent(stream net.Conn) error {
146148
return sendBusMessage("alive2", msg)
147149
}
148150

151+
func handleSettingEvent(stream net.Conn) error {
152+
var msg settingsMessage
153+
if err := recvMessage(stream, &msg); err != nil {
154+
return fmt.Errorf("recv settings message failed: %v", err)
155+
}
156+
if msg.KeepPendingInput != nil {
157+
globalSetting.keepPendingInput.Store(*msg.KeepPendingInput)
158+
}
159+
return nil
160+
}
161+
149162
func handleUnknownEvent(stream net.Conn, command string) error {
150163
var msg struct{}
151164
if err := recvMessage(stream, &msg); err != nil {

tsshd/client.go

Lines changed: 75 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -61,27 +61,27 @@ type udpClient interface {
6161

6262
// SshUdpClient implements a UDP SSH client
6363
type SshUdpClient struct {
64-
client udpClient
65-
proxy *clientProxy
66-
connectTimeout time.Duration
67-
waitGroup sync.WaitGroup
68-
closed atomic.Bool
69-
busMutex sync.Mutex
70-
busStream net.Conn
71-
sessionMutex sync.Mutex
72-
sessionID atomic.Uint64
73-
sessionMap map[uint64]*SshUdpSession
74-
channelMutex sync.Mutex
75-
channelMap map[string]chan ssh.NewChannel
76-
aliveCallback func(int64)
77-
aliveNewVer bool
78-
serverName string
79-
quitCallback func(string)
64+
client udpClient
65+
proxy *clientProxy
66+
connectTimeout time.Duration
67+
waitGroup sync.WaitGroup
68+
closed atomic.Bool
69+
busMutex sync.Mutex
70+
busStream net.Conn
71+
sessionMutex sync.Mutex
72+
sessionID atomic.Uint64
73+
sessionMap map[uint64]*SshUdpSession
74+
channelMutex sync.Mutex
75+
channelMap map[string]chan ssh.NewChannel
76+
aliveCallback func(int64)
77+
aliveNewVer bool
78+
quitCallback func(string)
79+
discardCallback func([]byte, []byte)
8080
}
8181

8282
// NewSshUdpClient creates a SshUdpClient
8383
func NewSshUdpClient(addr string, info *ServerInfo, connectTimeout, aliveTimeout, intervalTime time.Duration,
84-
quitCallback func(string)) (*SshUdpClient, error) {
84+
quitCallback func(string), discardCallback func([]byte, []byte)) (*SshUdpClient, error) {
8585
var proxy *clientProxy
8686
if info.ProxyKey != "" {
8787
var err error
@@ -99,12 +99,13 @@ func NewSshUdpClient(addr string, info *ServerInfo, connectTimeout, aliveTimeout
9999
return nil, err
100100
}
101101
udpClient := &SshUdpClient{
102-
client: client,
103-
proxy: proxy,
104-
sessionMap: make(map[uint64]*SshUdpSession),
105-
channelMap: make(map[string]chan ssh.NewChannel),
106-
connectTimeout: connectTimeout,
107-
quitCallback: quitCallback,
102+
client: client,
103+
proxy: proxy,
104+
sessionMap: make(map[uint64]*SshUdpSession),
105+
channelMap: make(map[string]chan ssh.NewChannel),
106+
connectTimeout: connectTimeout,
107+
quitCallback: quitCallback,
108+
discardCallback: discardCallback,
108109
}
109110

110111
busStream, err := udpClient.newStream("bus")
@@ -140,27 +141,27 @@ func (c *SshUdpClient) Close() error {
140141

141142
_, _ = doWithTimeout(func() (int, error) {
142143
if err := c.sendBusCommand("close"); err != nil {
143-
debug("[client] [%s] send cmd [close] failed: %v", c.serverName, err)
144+
debug("[client] send cmd [close] failed: %v", err)
144145
} else {
145-
debug("[client] [%s] send cmd [close] completed", c.serverName)
146+
debug("[client] send cmd [close] completed")
146147
}
147148
// UDP connections do not support half-close (write-only close) for now,
148149
// so we add extra wait time to allow all incoming data to be received.
149150
time.Sleep(200 * time.Millisecond) // give udp some time
150151
if err := c.busStream.Close(); err != nil {
151-
debug("[client] [%s] close bus stream failed: %v", c.serverName, err)
152+
debug("[client] close bus stream failed: %v", err)
152153
} else {
153-
debug("[client] [%s] close bus stream completed", c.serverName)
154+
debug("[client] close bus stream completed")
154155
}
155156
return 0, nil
156157
}, 300*time.Millisecond)
157158

158159
_, err := doWithTimeout(func() (int, error) {
159160
err := c.client.closeClient()
160161
if err != nil {
161-
debug("[client] [%s] close client failed: %v", c.serverName, err)
162+
debug("[client] close client failed: %v", err)
162163
} else {
163-
debug("[client] [%s] close client completed", c.serverName)
164+
debug("[client] close client completed")
164165
}
165166
return 0, err
166167
}, 200*time.Millisecond)
@@ -170,16 +171,19 @@ func (c *SshUdpClient) Close() error {
170171

171172
// Reconnect creates a new UDP path to the server
172173
func (c *SshUdpClient) Reconnect(timeout time.Duration) error {
173-
if c.proxy != nil {
174-
if err := c.proxy.renewUdpPath(timeout); err != nil {
175-
return err
176-
}
177-
if err := c.sendBusCommand("alive"); err != nil { // ping the server
178-
return fmt.Errorf("ping server failed: %w", err)
179-
}
180-
return nil
174+
if c.proxy == nil {
175+
return fmt.Errorf("no proxy for connection migration")
181176
}
182-
return fmt.Errorf("no proxy for connection migration")
177+
178+
if err := c.proxy.renewUdpPath(timeout); err != nil {
179+
return err
180+
}
181+
182+
if err := c.sendBusCommand("alive"); err != nil { // ping the server
183+
return fmt.Errorf("ping server failed: %w", err)
184+
}
185+
186+
return nil
183187
}
184188

185189
func (c *SshUdpClient) newStream(cmd string) (net.Conn, error) {
@@ -305,14 +309,13 @@ func (c *SshUdpClient) IsClosed() bool {
305309
return c.closed.Load()
306310
}
307311

308-
// SetDebugFunc set the debugging function
309-
func (c *SshUdpClient) SetDebugFunc(svrName string, debugFunc func(string, ...any)) {
310-
c.serverName = svrName
312+
// SetDebugFunc sets the debugging function
313+
func (c *SshUdpClient) SetDebugFunc(debugFunc func(string, ...any)) {
311314
clientDebug = debugFunc
312315
enableDebugLogging = true
313316
}
314317

315-
// SetWarningFunc set the warning function
318+
// SetWarningFunc sets the warning function
316319
func (c *SshUdpClient) SetWarningFunc(warningFunc func(string, ...any)) {
317320
clientWarning = warningFunc
318321
enableWarningLogging = true
@@ -380,6 +383,11 @@ func (c *SshUdpClient) ForwardUDPv1(addr string, timeout time.Duration) (string,
380383
return localAddr, nil
381384
}
382385

386+
// SetKeepPendingInput sets whether to keep the pending input during reconnection.
387+
func (c *SshUdpClient) SetKeepPendingInput(keep bool) error {
388+
return c.sendBusMessage("setting", settingsMessage{KeepPendingInput: &keep})
389+
}
390+
383391
func (c *SshUdpClient) sendBusCommand(command string) error {
384392
c.busMutex.Lock()
385393
defer c.busMutex.Unlock()
@@ -417,10 +425,12 @@ func (c *SshUdpClient) handleBusEvent() {
417425
c.handleChannelEvent()
418426
case "alive":
419427
if c.aliveCallback != nil {
420-
c.aliveCallback(0)
428+
go c.aliveCallback(0)
421429
}
422430
case "alive2":
423431
c.handleAliveEvent()
432+
case "discard":
433+
c.handleDiscardEvent()
424434
default:
425435
if err := handleUnknownEvent(c.busStream, command); err != nil {
426436
warning("handle bus command [%s] failed: %v. You may need to upgrade tssh.", command, err)
@@ -435,11 +445,11 @@ func (c *SshUdpClient) handleQuitEvent() {
435445
warning("recv quit message failed: %v", err)
436446
return
437447
}
438-
debug("[client] [%s] quit due to %s", c.serverName, quitMsg.Msg)
448+
debug("[client] quit due to %s", quitMsg.Msg)
439449
if c.quitCallback != nil {
440-
go c.quitCallback(fmt.Sprintf("[%s] %s", c.serverName, quitMsg.Msg))
450+
go c.quitCallback(quitMsg.Msg)
441451
} else {
442-
warning("[udp] quit due to [%s] %s", c.serverName, quitMsg.Msg)
452+
warning("quit due to %s", quitMsg.Msg)
443453
}
444454
}
445455

@@ -449,7 +459,7 @@ func (c *SshUdpClient) handleExitEvent() {
449459
warning("recv exit message failed: %v", err)
450460
return
451461
}
452-
debug("[client] [%s] session [%d] exiting with code: %d", c.serverName, exitMsg.ID, exitMsg.ExitCode)
462+
debug("[client] session [%d] exiting with code: %d", exitMsg.ID, exitMsg.ExitCode)
453463

454464
c.sessionMutex.Lock()
455465
defer c.sessionMutex.Unlock()
@@ -471,7 +481,7 @@ func (c *SshUdpClient) handleDebugEvent() {
471481
warning("recv debug message failed: %v", err)
472482
return
473483
}
474-
debug("[server] [%s] %s", c.serverName, dbgMsg.Msg)
484+
debug("[server] %s", dbgMsg.Msg)
475485
}
476486

477487
func (c *SshUdpClient) handleErrorEvent() {
@@ -480,7 +490,7 @@ func (c *SshUdpClient) handleErrorEvent() {
480490
warning("recv error message failed: %v", err)
481491
return
482492
}
483-
warning("[udp] %s", errMsg.Msg)
493+
warning("%s", errMsg.Msg)
484494
}
485495

486496
func (c *SshUdpClient) handleChannelEvent() {
@@ -510,11 +520,22 @@ func (c *SshUdpClient) handleAliveEvent() {
510520
return
511521
}
512522
if c.aliveCallback != nil {
513-
c.aliveCallback(aliveMsg.Time)
523+
go c.aliveCallback(aliveMsg.Time)
514524
}
515525
c.aliveNewVer = true
516526
}
517527

528+
func (c *SshUdpClient) handleDiscardEvent() {
529+
var discardMsg discardMessage
530+
if err := recvMessage(c.busStream, &discardMsg); err != nil {
531+
warning("recv discard message failed: %v", err)
532+
return
533+
}
534+
if c.discardCallback != nil {
535+
go c.discardCallback(discardMsg.DiscardMarker, discardMsg.DiscardedInput)
536+
}
537+
}
538+
518539
// SshUdpSession represents a connection to a remote command or shell
519540
type SshUdpSession struct {
520541
id uint64
@@ -549,7 +570,7 @@ func (s *SshUdpSession) Close() error {
549570

550571
_, err := doWithTimeout(func() (int, error) {
551572
err := s.stream.Close()
552-
debug("[client] [%s] close session completed", s.client.serverName)
573+
debug("[client] close session completed")
553574
return 0, err
554575
}, 100*time.Millisecond)
555576
return err
@@ -625,14 +646,14 @@ func (s *SshUdpSession) startSession(msg *startMessage) error {
625646
go func() {
626647
_, _ = io.Copy(s.stream, s.stdin)
627648
_ = s.stream.Close()
628-
debug("[client] [%s] session [%d] stdin completed", s.client.serverName, s.id)
649+
debug("[client] session [%d] stdin completed", s.id)
629650
}()
630651
}
631652
if s.stdout != nil {
632653
s.wg.Go(func() {
633654
_, _ = io.Copy(s.stdout, s.stream)
634655
_ = s.stdout.Close()
635-
debug("[client] [%s] session [%d] stdout completed", s.client.serverName, s.id)
656+
debug("[client] session [%d] stdout completed", s.id)
636657
})
637658
}
638659
return nil
@@ -706,7 +727,7 @@ func (s *SshUdpSession) StderrPipe() (io.Reader, error) {
706727
s.wg.Go(func() {
707728
_, _ = io.Copy(s.stderr, stream)
708729
_ = s.stderr.Close()
709-
debug("[client] [%s] session [%d] stderr completed", s.client.serverName, s.id)
730+
debug("[client] session [%d] stderr completed", s.id)
710731
})
711732
return reader, nil
712733
}

0 commit comments

Comments
 (0)