diff --git a/ssh/client.go b/ssh/client.go index a4daa24..e2a143e 100644 --- a/ssh/client.go +++ b/ssh/client.go @@ -309,6 +309,12 @@ func (c *ClientConn) mainLoop() { // invalid window update return } + case *globalRequestMsg: + // This handles keepalive messages and matches + // the behaviour of OpenSSH. + if msg.WantReply { + c.writePacket(marshal(msgRequestFailure, globalRequestFailureMsg{})) + } case *globalRequestSuccessMsg, *globalRequestFailureMsg: c.globalRequest.response <- msg case *disconnectMsg: diff --git a/ssh/session.go b/ssh/session.go index ac96d52..640d322 100644 --- a/ssh/session.go +++ b/ssh/session.go @@ -404,7 +404,13 @@ func (s *Session) wait() error { } wm.lang = safeString(string(lang)) default: - return fmt.Errorf("wait: unexpected channel request: %v", msg) + // This handles keepalives and matches + // OpenSSH's behaviour. + if msg.WantReply { + s.writePacket(marshal(msgChannelFailure, channelRequestFailureMsg{ + PeersId: s.remoteId, + })) + } } default: return fmt.Errorf("wait: unexpected packet %T received: %v", msg, msg) diff --git a/ssh/session_test.go b/ssh/session_test.go index 0c64ed2..218d002 100644 --- a/ssh/session_test.go +++ b/ssh/session_test.go @@ -498,6 +498,24 @@ func TestServerWindow(t *testing.T) { } } +// Verify the client can handle a keepalive packet from the server. +func TestClientHandlesKeepalives(t *testing.T) { + conn := dial(channelKeepaliveSender, t) + defer conn.Close() + session, err := conn.NewSession() + if err != nil { + t.Fatal(err) + } + defer session.Close() + if err := session.Shell(); err != nil { + t.Fatalf("Unable to execute command: %v", err) + } + err = session.Wait() + if err != nil { + t.Fatalf("expected nil but got: %v", err) + } +} + type exitStatusMsg struct { PeersId uint32 Request string @@ -687,3 +705,18 @@ func copyNRandomly(title string, dst io.Writer, src io.Reader, n int) (int, erro } return written, nil } + +func channelKeepaliveSender(ch *serverChan, t *testing.T) { + defer ch.Close() + shell := newServerShell(ch, "> ") + readLine(shell, t) + msg := channelRequestMsg{ + PeersId: ch.remoteId, + Request: "keepalive@openssh.com", + WantReply: true, + } + if err := ch.writePacket(marshal(msgChannelRequest, msg)); err != nil { + t.Errorf("unable to send channel keepalive request: %v", err) + } + sendStatus(0, ch, t) +}