go.crypto/ssh: add support for remote tcpip forwarding

Add support for server (remote) forwarded tcpip channels.
See RFC4254 Section 7.1

R=gustav.paul, jeff, agl, lieqiewang
CC=golang-dev
https://golang.org/cl/6038047
diff --git a/ssh/tcpip.go b/ssh/tcpip.go
index e0c47bc..55cd7cc 100644
--- a/ssh/tcpip.go
+++ b/ssh/tcpip.go
@@ -9,9 +9,154 @@
 	"fmt"
 	"io"
 	"net"
+	"sync"
 	"time"
 )
 
+var (
+	// TODO(dfc) relax this restriction
+	errNoPort = errors.New("A port number must be supplied")
+)
+
+// Listen requests the remote peer open a listening socket 
+// on addr. Incoming connections will be available by calling
+// Accept on the returned net.Listener.
+func (c *ClientConn) Listen(n, addr string) (net.Listener, error) {
+	raddr, err := net.ResolveTCPAddr(n, addr)
+	if err != nil {
+		return nil, err
+	}
+	return c.ListenTCP(raddr)
+}
+
+// ListenTCP requests the remote peer open a listening socket 
+// on raddr. Incoming connections will be available by calling
+// Accept on the returned net.Listener.
+func (c *ClientConn) ListenTCP(raddr *net.TCPAddr) (net.Listener, error) {
+	if raddr.Port == 0 {
+		return nil, errNoPort
+	}
+	return c.listen(raddr)
+}
+
+// RFC 4254 7.1
+type channelForwardMsg struct {
+	Message   string
+	WantReply bool
+	raddr     string
+	rport     uint32
+}
+
+func (c *ClientConn) listen(addr *net.TCPAddr) (net.Listener, error) {
+	m := channelForwardMsg{
+		"tcpip-forward",
+		false, // can't handle reply message from remote yet
+		addr.IP.String(),
+		uint32(addr.Port),
+	}
+	// register this forward
+	ch := c.forwardList.Add(addr)
+	// send message
+	if err := c.writePacket(marshal(msgGlobalRequest, m)); err != nil {
+		c.forwardList.Remove(addr)
+		return nil, err
+	}
+	return &tcpListener{addr, c, ch}, nil
+}
+
+// forwardList stores a mapping between remote 
+// forward requests and the tcpListeners.
+type forwardList struct {
+	sync.Mutex
+	entries []forwardEntry
+}
+
+// forwardEntry represents an established mapping of a laddr on a 
+// remote ssh server to a channel connected to a tcpListener.
+type forwardEntry struct {
+	laddr *net.TCPAddr
+	c     chan forward
+}
+
+// forward represents an incoming forwarded tcpip connection
+type forward struct {
+	c     *clientChan  // the ssh client channel underlying this forward
+	raddr *net.TCPAddr // the raddr of the incoming connection
+}
+
+func (l *forwardList) Add(addr *net.TCPAddr) chan forward {
+	l.Lock()
+	defer l.Unlock()
+	f := forwardEntry{
+		addr,
+		make(chan forward, 1),
+	}
+	l.entries = append(l.entries, f)
+	return f.c
+}
+
+func (l *forwardList) Remove(addr *net.TCPAddr) {
+	l.Lock()
+	defer l.Unlock()
+	for i, f := range l.entries {
+		if addr.IP.Equal(f.laddr.IP) && addr.Port == f.laddr.Port {
+			l.entries = append(l.entries[:i], l.entries[i+1:]...)
+			return
+		}
+	}
+}
+
+func (l *forwardList) Lookup(addr *net.TCPAddr) (chan forward, bool) {
+	l.Lock()
+	defer l.Unlock()
+	for _, f := range l.entries {
+		if addr.IP.Equal(f.laddr.IP) && addr.Port == f.laddr.Port {
+			return f.c, true
+		}
+	}
+	return nil, false
+}
+
+type tcpListener struct {
+	laddr *net.TCPAddr
+	conn  *ClientConn
+	in    <-chan forward
+}
+
+// Accept waits for and returns the next connection to the listener.
+func (l *tcpListener) Accept() (net.Conn, error) {
+	s, ok := <-l.in
+	if !ok {
+		return nil, io.EOF
+	}
+	return &tcpChanConn{
+		tcpChan: &tcpChan{
+			clientChan: s.c,
+			Reader:     s.c.stdout,
+			Writer:     s.c.stdin,
+		},
+		laddr: l.laddr,
+		raddr: s.raddr,
+	}, nil
+}
+
+// Close closes the listener.
+func (l *tcpListener) Close() error {
+	m := channelForwardMsg{
+		"cancel-tcpip-forward",
+		false, // TODO(dfc) process reply
+		l.laddr.IP.String(),
+		uint32(l.laddr.Port),
+	}
+	l.conn.forwardList.Remove(l.laddr)
+	return l.conn.writePacket(marshal(msgGlobalRequest, m))
+}
+
+// Addr returns the listener's network address.
+func (l *tcpListener) Addr() net.Addr {
+	return l.laddr
+}
+
 // Dial initiates a connection to the addr from the remote host.
 // addr is resolved using net.ResolveTCPAddr before connection. 
 // This could allow an observer to observe the DNS name of the 
@@ -38,8 +183,8 @@
 	if err != nil {
 		return nil, err
 	}
-	return &tcpchanconn{
-		tcpchan: ch,
+	return &tcpChanConn{
+		tcpChan: ch,
 		laddr:   laddr,
 		raddr:   raddr,
 	}, nil
@@ -59,7 +204,7 @@
 
 // dial opens a direct-tcpip connection to the remote server. laddr and raddr are passed as
 // strings and are expected to be resolveable at the remote end.
-func (c *ClientConn) dial(laddr string, lport int, raddr string, rport int) (*tcpchan, error) {
+func (c *ClientConn) dial(laddr string, lport int, raddr string, rport int) (*tcpChan, error) {
 	ch := c.newChan(c.transport)
 	if err := c.writePacket(marshal(msgChannelOpen, channelOpenDirectMsg{
 		ChanType:      "direct-tcpip",
@@ -78,39 +223,39 @@
 		c.chanlist.remove(ch.id)
 		return nil, fmt.Errorf("ssh: unable to open direct tcpip connection: %v", err)
 	}
-	return &tcpchan{
+	return &tcpChan{
 		clientChan: ch,
 		Reader:     ch.stdout,
 		Writer:     ch.stdin,
 	}, nil
 }
 
-type tcpchan struct {
+type tcpChan struct {
 	*clientChan // the backing channel
 	io.Reader
 	io.Writer
 }
 
-// tcpchanconn fulfills the net.Conn interface without 
-// the tcpchan having to hold laddr or raddr directly.
-type tcpchanconn struct {
-	*tcpchan
+// tcpChanConn fulfills the net.Conn interface without 
+// the tcpChan having to hold laddr or raddr directly.
+type tcpChanConn struct {
+	*tcpChan
 	laddr, raddr net.Addr
 }
 
 // LocalAddr returns the local network address.
-func (t *tcpchanconn) LocalAddr() net.Addr {
+func (t *tcpChanConn) LocalAddr() net.Addr {
 	return t.laddr
 }
 
 // RemoteAddr returns the remote network address.
-func (t *tcpchanconn) RemoteAddr() net.Addr {
+func (t *tcpChanConn) RemoteAddr() net.Addr {
 	return t.raddr
 }
 
 // SetDeadline sets the read and write deadlines associated
 // with the connection.
-func (t *tcpchanconn) SetDeadline(deadline time.Time) error {
+func (t *tcpChanConn) SetDeadline(deadline time.Time) error {
 	if err := t.SetReadDeadline(deadline); err != nil {
 		return err
 	}
@@ -121,12 +266,12 @@
 // A zero value for t means Read will not time out.
 // After the deadline, the error from Read will implement net.Error
 // with Timeout() == true.
-func (t *tcpchanconn) SetReadDeadline(deadline time.Time) error {
-	return errors.New("ssh: tcpchan: deadline not supported")
+func (t *tcpChanConn) SetReadDeadline(deadline time.Time) error {
+	return errors.New("ssh: tcpChan: deadline not supported")
 }
 
 // SetWriteDeadline exists to satisfy the net.Conn interface
 // but is not implemented by this type.  It always returns an error.
-func (t *tcpchanconn) SetWriteDeadline(deadline time.Time) error {
-	return errors.New("ssh: tcpchan: deadline not supported")
+func (t *tcpChanConn) SetWriteDeadline(deadline time.Time) error {
+	return errors.New("ssh: tcpChan: deadline not supported")
 }