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")
}