go.crypto/ssh: add workaround for broken port forwarding in
OpenSSH 5.
Tested with OpenSSH_5.9
R=agl, dave
CC=golang-dev
https://golang.org/cl/11921043
diff --git a/ssh/tcpip.go b/ssh/tcpip.go
index ad92b43..5abd690 100644
--- a/ssh/tcpip.go
+++ b/ssh/tcpip.go
@@ -8,7 +8,10 @@
"errors"
"fmt"
"io"
+ "math/rand"
"net"
+ "strconv"
+ "strings"
"sync"
"time"
)
@@ -32,10 +35,60 @@
rport uint32
}
+// Automatic port allocation is broken with OpenSSH before 6.0. See
+// also https://bugzilla.mindrot.org/show_bug.cgi?id=2017. In
+// particular, OpenSSH 5.9 sends a channelOpenMsg with port number 0,
+// rather than the actual port number. This means you can never open
+// two different listeners with auto allocated ports. We work around
+// this by trying explicit ports until we succeed.
+
+const openSSHPrefix = "OpenSSH_"
+
+// isBrokenOpenSSHVersion returns true if the given version string
+// specifies a version of OpenSSH that is known to have a bug in port
+// forwarding.
+func isBrokenOpenSSHVersion(versionStr string) bool {
+ i := strings.Index(versionStr, openSSHPrefix)
+ if i < 0 {
+ return false
+ }
+ i += len(openSSHPrefix)
+ j := i
+ for ; j < len(versionStr); j++ {
+ if versionStr[j] < '0' || versionStr[j] > '9' {
+ break
+ }
+ }
+ version, _ := strconv.Atoi(versionStr[i:j])
+ return version < 6
+}
+
+// autoPortListenWorkaround simulates automatic port allocation by
+// trying random ports repeatedly.
+func (c *ClientConn) autoPortListenWorkaround(laddr *net.TCPAddr) (net.Listener, error) {
+ var sshListener net.Listener
+ var err error
+ const tries = 10
+ for i := 0; i < tries; i++ {
+ addr := *laddr
+ addr.Port = 1024 + rand.Intn(60000)
+ sshListener, err = c.ListenTCP(&addr)
+ if err == nil {
+ laddr.Port = addr.Port
+ return sshListener, err
+ }
+ }
+ return nil, fmt.Errorf("ssh: listen on random port failed after %d tries: %v", tries, err)
+}
+
// ListenTCP requests the remote peer open a listening socket
// on laddr. Incoming connections will be available by calling
// Accept on the returned net.Listener.
func (c *ClientConn) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {
+ if laddr.Port == 0 && isBrokenOpenSSHVersion(c.serverVersion) {
+ return c.autoPortListenWorkaround(laddr)
+ }
+
m := channelForwardMsg{
"tcpip-forward",
true, // sendGlobalRequest waits for a reply
@@ -59,10 +112,6 @@
}
// Register this forward, using the port number we obtained.
- //
- // This does not work on OpenSSH < 6.0, which will send a
- // channelOpenMsg with port number 0, rather than the actual
- // port number.
ch := c.forwardList.add(*laddr)
return &tcpListener{laddr, c, ch}, nil