diff --git a/lib/server.go b/lib/server.go index 3e9a8ac..dce2347 100644 --- a/lib/server.go +++ b/lib/server.go @@ -13,6 +13,7 @@ import ( "net/http" "net/netip" "sync" + "time" "golang.org/x/crypto/ssh" "golang.org/x/sync/errgroup" @@ -734,7 +735,18 @@ func (c *Client) checkVersion(newversion uint8) error { if c.log != nil { c.log.Debugf("%v: has bad version (received: v%v, current: v%v) error", c.String(), newversion, c.version) } + // Send the Error Report through the normal channel, then give + // sendLoop time to flush it before we disconnect. The error + // report must use the server's highest supported version so the + // client knows what to downgrade to (RFC 8210 ยง7). + if !c.versionset { + c.SetVersion(PROTOCOL_VERSION_1) + } c.SendWrongVersionError() + // Brief yield to let sendLoop drain the error report. Not + // guaranteed, but materially reduces the race window vs the + // previous immediate-disconnect path. + time.Sleep(50 * time.Millisecond) c.Disconnect() return fmt.Errorf("%v: has bad version (received: v%v, current: v%v)", c.String(), newversion, c.version) } @@ -805,7 +817,11 @@ func (c *Client) readLoop(ctx context.Context) error { if c.log != nil { c.log.Debugf("Bad version error") } + // c.version already holds the server's enforced version + // (set at connection accept time), so SendWrongVersionError + // stamps the correct version for the client to downgrade to. c.SendWrongVersionError() + time.Sleep(50 * time.Millisecond) c.Disconnect() return fmt.Errorf("%s: bad version error", c.String()) }