Skip to content

Commit 2380e4d

Browse files
committed
Export public struct for reporting explicit disconnects
1 parent ebe9262 commit 2380e4d

File tree

6 files changed

+50
-13
lines changed

6 files changed

+50
-13
lines changed

ssh/client_auth_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@ func TestClientAuthMaxAuthTries(t *testing.T) {
613613
}
614614
serverConfig.AddHostKey(testSigners["rsa"])
615615

616-
expectedErr := fmt.Errorf("ssh: handshake failed: %v", &disconnectMsg{
616+
expectedErr := fmt.Errorf("ssh: handshake failed: %v", &DisconnectError{
617617
Reason: 2,
618618
Message: "too many authentication failures",
619619
})
@@ -676,7 +676,7 @@ func TestClientAuthMaxAuthTriesPublicKey(t *testing.T) {
676676
t.Fatalf("unable to dial remote side: %s", err)
677677
}
678678

679-
expectedErr := fmt.Errorf("ssh: handshake failed: %v", &disconnectMsg{
679+
expectedErr := fmt.Errorf("ssh: handshake failed: %v", &DisconnectError{
680680
Reason: 2,
681681
Message: "too many authentication failures",
682682
})

ssh/connection.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,39 @@ func (e *OpenChannelError) Error() string {
2020
return fmt.Sprintf("ssh: rejected: %s (%s)", e.Reason, e.Message)
2121
}
2222

23+
// DisconnectReason is an enumeration used when closing connections to describe
24+
// why a disconnect was sent. See RFC 4253, section 11.1.
25+
type DisconnectReason uint32
26+
27+
const (
28+
HostNotAllowedToConnect DisconnectReason = 1
29+
ProtocolError = 2
30+
KeyExchangeFailed = 3
31+
// 4 is reserved for future use.
32+
MacError = 5
33+
CompressionError = 6
34+
ServiceNotAvailable = 7
35+
ProtocolVersionNotSupported = 8
36+
HostKeyNotVerifiable = 9
37+
ConnectionLost = 10
38+
ByApplication = 11
39+
TooManyConnections = 12
40+
AuthCancelledByUser = 13
41+
NoMoreAuthMethodsAvailable = 14
42+
IllegalUserName = 15
43+
)
44+
45+
// DisconnectError is returned by Conn.Wait if the other end of the connection
46+
// explicitly closes the connection by sending a disconnect message.
47+
type DisconnectError struct {
48+
Reason DisconnectReason
49+
Message string
50+
}
51+
52+
func (d *DisconnectError) Error() string {
53+
return fmt.Sprintf("ssh: disconnect, reason %d: %s", d.Reason, d.Message)
54+
}
55+
2356
// ConnMetadata holds metadata for the connection.
2457
type ConnMetadata interface {
2558
// User returns the user ID for this connection.
@@ -66,7 +99,9 @@ type Conn interface {
6699
Close() error
67100

68101
// Wait blocks until the connection has shut down, and returns the
69-
// error causing the shutdown.
102+
// error causing the shutdown. If the connection has been closed by an
103+
// explicit disconnect message from the other end, then Wait will return a
104+
// DisconnectError.
70105
Wait() error
71106

72107
// TODO(hanwen): consider exposing:

ssh/handshake_test.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -505,11 +505,15 @@ func TestDisconnect(t *testing.T) {
505505
defer trS.Close()
506506

507507
trC.writePacket([]byte{msgRequestSuccess, 0, 0})
508-
errMsg := &disconnectMsg{
508+
errPacket := &disconnectMsg{
509509
Reason: 42,
510510
Message: "such is life",
511511
}
512-
trC.writePacket(Marshal(errMsg))
512+
errResponse := &DisconnectError{
513+
Reason: DisconnectReason(errPacket.Reason),
514+
Message: errPacket.Message,
515+
}
516+
trC.writePacket(Marshal(errPacket))
513517
trC.writePacket([]byte{msgRequestSuccess, 0, 0})
514518

515519
packet, err := trS.readPacket()
@@ -523,8 +527,8 @@ func TestDisconnect(t *testing.T) {
523527
_, err = trS.readPacket()
524528
if err == nil {
525529
t.Errorf("readPacket 2 succeeded")
526-
} else if !reflect.DeepEqual(err, errMsg) {
527-
t.Errorf("got error %#v, want %#v", err, errMsg)
530+
} else if !reflect.DeepEqual(err, errResponse) {
531+
t.Errorf("got error %#v, want %#v", err, errResponse)
528532
}
529533

530534
_, err = trS.readPacket()

ssh/messages.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,6 @@ type disconnectMsg struct {
4343
Language string
4444
}
4545

46-
func (d *disconnectMsg) Error() string {
47-
return fmt.Sprintf("ssh: disconnect, reason %d: %s", d.Reason, d.Message)
48-
}
49-
5046
// See RFC 4253, section 7.1.
5147
const msgKexInit = 20
5248

ssh/server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,8 @@ userAuthLoop:
416416
return nil, err
417417
}
418418

419-
return nil, discMsg
419+
err := &DisconnectError{Reason: DisconnectReason(discMsg.Reason), Message: discMsg.Message}
420+
return nil, err
420421
}
421422

422423
var userAuthReq userAuthRequestMsg

ssh/transport.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) {
154154
if err := Unmarshal(packet, &msg); err != nil {
155155
return nil, err
156156
}
157-
return nil, &msg
157+
err := &DisconnectError{Reason: DisconnectReason(msg.Reason), Message: msg.Message}
158+
return nil, err
158159
}
159160
}
160161

0 commit comments

Comments
 (0)