Skip to content

Commit bd68635

Browse files
committed
go.crypto/ssh: replace window channel with an atomic variable and condition
Fixes golang/go#3479. Using a channel to model window size was a mistake. Unlike stdin and stdout, which are streams of data, window size is an variable and should be modeled as such. R=golang-dev, agl, gustav.paul, kardianos, dvyukov CC=golang-dev https://golang.org/cl/5986053
1 parent 15577f9 commit bd68635

File tree

1 file changed

+54
-16
lines changed

1 file changed

+54
-16
lines changed

ssh/client.go

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ func (c *ClientConn) mainLoop() {
192192
break
193193
}
194194
// TODO(dfc) A note on blocking channel use.
195-
// The msg, win, data and dataExt channels of a clientChan can
195+
// The msg, data and dataExt channels of a clientChan can
196196
// cause this loop to block indefinately if the consumer does
197197
// not service them.
198198
switch packet[0] {
@@ -233,7 +233,6 @@ func (c *ClientConn) mainLoop() {
233233
case *channelCloseMsg:
234234
ch := c.getChan(msg.PeersId)
235235
ch.theyClosed = true
236-
close(ch.stdin.win)
237236
ch.stdout.eof()
238237
ch.stderr.eof()
239238
close(ch.msg)
@@ -255,7 +254,10 @@ func (c *ClientConn) mainLoop() {
255254
case *channelRequestMsg:
256255
c.getChan(msg.PeersId).msg <- msg
257256
case *windowAdjustMsg:
258-
c.getChan(msg.PeersId).stdin.win <- int(msg.AdditionalBytes)
257+
if !c.getChan(msg.PeersId).stdin.win.add(msg.AdditionalBytes) {
258+
// invalid window update
259+
break
260+
}
259261
case *disconnectMsg:
260262
break
261263
default:
@@ -324,7 +326,7 @@ func newClientChan(t *transport, id uint32) *clientChan {
324326
msg: make(chan interface{}, 16),
325327
}
326328
c.stdin = &chanWriter{
327-
win: make(chan int, 16),
329+
win: &window{Cond: sync.NewCond(new(sync.Mutex))},
328330
clientChan: c,
329331
}
330332
c.stdout = &chanReader{
@@ -345,7 +347,7 @@ func (c *clientChan) waitForChannelOpenResponse() error {
345347
case *channelOpenConfirmMsg:
346348
// fixup peersId field
347349
c.peersId = msg.MyId
348-
c.stdin.win <- int(msg.MyWindow)
350+
c.stdin.win.add(msg.MyWindow)
349351
return nil
350352
case *channelOpenFailureMsg:
351353
return errors.New(safeString(msg.Message))
@@ -417,22 +419,16 @@ func (c *chanlist) remove(id uint32) {
417419

418420
// A chanWriter represents the stdin of a remote process.
419421
type chanWriter struct {
420-
win chan int // receives window adjustments
421-
rwin int // current rwin size
422+
win *window
422423
clientChan *clientChan // the channel backing this writer
423424
}
424425

425426
// Write writes data to the remote process's standard input.
426427
func (w *chanWriter) Write(data []byte) (written int, err error) {
427428
for len(data) > 0 {
428-
for w.rwin < 1 {
429-
win, ok := <-w.win
430-
if !ok {
431-
return 0, io.EOF
432-
}
433-
w.rwin += win
434-
}
435-
n := min(len(data), w.rwin)
429+
// n cannot be larger than 2^31 as len(data) cannot
430+
// be larger than 2^31
431+
n := int(w.win.reserve(uint32(len(data))))
436432
peersId := w.clientChan.peersId
437433
packet := []byte{
438434
msgChannelData,
@@ -443,7 +439,6 @@ func (w *chanWriter) Write(data []byte) (written int, err error) {
443439
break
444440
}
445441
data = data[n:]
446-
w.rwin -= n
447442
written += n
448443
}
449444
return
@@ -507,3 +502,46 @@ func (r *chanReader) Read(data []byte) (int, error) {
507502
}
508503
panic("unreachable")
509504
}
505+
506+
// window represents the buffer available to clients
507+
// wishing to write to a channel.
508+
type window struct {
509+
*sync.Cond
510+
win uint32 // RFC 4254 5.2 says the window size can grow to 2^32-1
511+
}
512+
513+
// add adds win to the amount of window available
514+
// for consumers.
515+
func (w *window) add(win uint32) bool {
516+
if win == 0 {
517+
return false
518+
}
519+
w.L.Lock()
520+
if w.win+win < win {
521+
w.L.Unlock()
522+
return false
523+
}
524+
w.win += win
525+
// It is unusual that multiple goroutines would be attempting to reserve
526+
// window space, but not guaranteed. Use broadcast to notify all waiters
527+
// that additional window is available.
528+
w.Broadcast()
529+
w.L.Unlock()
530+
return true
531+
}
532+
533+
// reserve reserves win from the available window capacity.
534+
// If no capacity remains, reserve will block. reserve may
535+
// return less than requested.
536+
func (w *window) reserve(win uint32) uint32 {
537+
w.L.Lock()
538+
for w.win == 0 {
539+
w.Wait()
540+
}
541+
if w.win < win {
542+
win = w.win
543+
}
544+
w.win -= win
545+
w.L.Unlock()
546+
return win
547+
}

0 commit comments

Comments
 (0)