Skip to content

Commit 31d10dc

Browse files
committed
Filter out common connection errors
1 parent 9e6b02f commit 31d10dc

File tree

1 file changed

+65
-7
lines changed

1 file changed

+65
-7
lines changed

internal/sshproxy/server.go

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import (
77
"io"
88
"net"
99
"strconv"
10+
"strings"
1011
"sync"
1112
"sync/atomic"
13+
"syscall"
1214
"time"
1315

1416
"golang.org/x/crypto/ssh"
@@ -278,20 +280,62 @@ func handleConnectionError(ctx context.Context, err error) {
278280
return
279281
}
280282

281-
authErr, isAuthErr := errors.AsType[*ssh.ServerAuthError](err)
282-
if !isAuthErr {
283-
logger.WithError(err).Error("failed to handshake client")
283+
if errors.Is(err, syscall.ECONNRESET) {
284+
// For example, OpenSSH client may send RST during key exchange if the host keys have changed
285+
logger.WithError(err).Debug("connection reset by client")
284286
return
285287
}
286288

287-
for _, err := range authErr.Errors {
288-
if errors.Is(err, ErrUpstreamNotFound) {
289-
logger.Debug("client requested unknown upstream")
289+
if errors.Is(err, syscall.ETIMEDOUT) {
290+
logger.WithError(err).Debug("client connection timed out")
291+
return
292+
}
293+
294+
if authErr, ok := errors.AsType[*ssh.ServerAuthError](err); ok {
295+
for _, err := range authErr.Errors {
296+
if errors.Is(err, ErrUpstreamNotFound) {
297+
logger.Debug("client requested unknown upstream")
298+
return
299+
}
300+
}
301+
302+
logger.WithError(err).Debug("client auth failed")
303+
return
304+
}
305+
306+
if algoErr, ok := errors.AsType[*ssh.AlgorithmNegotiationError](err); ok {
307+
logger.WithField("offered", algoErr.RequestedAlgorithms).Debugf("no common algorithm for %s", algoErr.What)
308+
return
309+
}
310+
311+
if sshErr := getSSHError(err); sshErr != nil {
312+
errMsg := sshErr.Error()
313+
314+
for _, msg := range [...]string{
315+
// https://github.com/golang/crypto/blob/982eaa62dfb7273603b97fc1835561450096f3bd/ssh/transport.go#L369
316+
"overflow reading version string",
317+
// https://github.com/golang/crypto/blob/982eaa62dfb7273603b97fc1835561450096f3bd/ssh/messages.go#L385
318+
// e.g., "unmarshal error for field Language of type disconnectMsg"
319+
"unmarshal error",
320+
// https://github.com/golang/crypto/blob/982eaa62dfb7273603b97fc1835561450096f3bd/ssh/common.go#L382
321+
"unexpected message type",
322+
} {
323+
if strings.Contains(errMsg, msg) {
324+
logger.WithError(err).Debug("suspicious client")
325+
return
326+
}
327+
}
328+
329+
// https://github.com/golang/crypto/blob/982eaa62dfb7273603b97fc1835561450096f3bd/ssh/messages.go#L47
330+
// e.g., "ssh: disconnect, reason 11: disconnected by user"
331+
// Most probably this is also a suspicious client, but may be a legitimate use of SSH_MSG_DISCONNECT
332+
if strings.Contains(errMsg, "disconnect, reason") {
333+
logger.WithError(err).Debug("client disconnected")
290334
return
291335
}
292336
}
293337

294-
logger.WithError(err).Debug("client auth failed")
338+
logger.WithError(err).Error("failed to handshake client")
295339
}
296340

297341
func connectToUpstream(
@@ -458,3 +502,17 @@ func bridgeChannelRequests(ctx context.Context, dir direction, inReqs <-chan *ss
458502
func isClosedError(err error) bool {
459503
return errors.Is(err, net.ErrClosed) || errors.Is(err, io.EOF)
460504
}
505+
506+
func getSSHError(err error) error {
507+
for {
508+
if strings.HasPrefix(err.Error(), "ssh: ") {
509+
return err
510+
}
511+
err = errors.Unwrap(err)
512+
if err == nil {
513+
break
514+
}
515+
}
516+
517+
return nil
518+
}

0 commit comments

Comments
 (0)