Ver código fonte

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
Dave Cheney 13 anos atrás
pai
commit
b333fd1d05
5 arquivos alterados com 270 adições e 26 exclusões
  1. 1 1
      ssh/channel.go
  2. 78 8
      ssh/client.go
  3. 29 0
      ssh/example_test.go
  4. 1 1
      ssh/messages.go
  5. 161 16
      ssh/tcpip.go

+ 1 - 1
ssh/channel.go

@@ -122,7 +122,7 @@ func (c *channel) Reject(reason RejectionReason, message string) error {
 
 	reject := channelOpenFailureMsg{
 		PeersId:  c.theirId,
-		Reason:   uint32(reason),
+		Reason:   reason,
 		Message:  message,
 		Language: "en",
 	}

+ 78 - 8
ssh/client.go

@@ -21,8 +21,9 @@ var clientVersion = []byte("SSH-2.0-Go\r\n")
 // ClientConn represents the client side of an SSH connection.
 type ClientConn struct {
 	*transport
-	config *ClientConfig
-	chanlist
+	config      *ClientConfig
+	chanlist    // channels associated with this connection
+	forwardList // forwared tcpip connections from the remote side
 }
 
 // Client returns a new SSH client connection using c as the underlying transport.
@@ -239,7 +240,7 @@ func (c *ClientConn) mainLoop() {
 		default:
 			switch msg := decode(packet).(type) {
 			case *channelOpenMsg:
-				c.getChan(msg.PeersId).msg <- msg
+				c.handleChanOpen(msg)
 			case *channelOpenConfirmMsg:
 				c.getChan(msg.PeersId).msg <- msg
 			case *channelOpenFailureMsg:
@@ -281,6 +282,71 @@ func (c *ClientConn) mainLoop() {
 	}
 }
 
+// Handle channel open messages from the remote side.
+func (c *ClientConn) handleChanOpen(msg *channelOpenMsg) {
+	switch msg.ChanType {
+	case "forwarded-tcpip":
+		addr, err := parseAddr(msg.TypeSpecificData)
+		if err != nil {
+			// invalid request
+			m := channelOpenFailureMsg{
+				PeersId:  msg.PeersId,
+				Reason:   ConnectionFailed,
+				Message:  fmt.Sprintf("invalid request: %v", err),
+				Language: "en_US.UTF-8",
+			}
+			c.writePacket(marshal(msgChannelOpenFailure, m))
+			return
+		}
+		l, ok := c.forwardList.Lookup(addr)
+		if !ok {
+			// Section 7.2, implementations MUST reject suprious incoming
+			// connections.
+			return
+		}
+		ch := c.newChan(c.transport)
+		ch.peersId = msg.PeersId
+		ch.stdin.win.add(msg.PeersWindow)
+
+		m := channelOpenConfirmMsg{
+			PeersId:       ch.peersId,
+			MyId:          ch.id,
+			MyWindow:      1 << 14,
+			MaxPacketSize: 1 << 15, // RFC 4253 6.1
+		}
+		c.writePacket(marshal(msgChannelOpenConfirm, m))
+		l <- forward{ch, addr}
+	default:
+		// unknown channel type
+		m := channelOpenFailureMsg{
+			PeersId:  msg.PeersId,
+			Reason:   UnknownChannelType,
+			Message:  fmt.Sprintf("unknown channel type: %v", msg.ChanType),
+			Language: "en_US.UTF-8",
+		}
+		c.writePacket(marshal(msgChannelOpenFailure, m))
+	}
+}
+
+// parseAddr parses the originating address from the remote into a *net.TCPAddr. 
+// RFC 4254 section 7.2 is mute on what to do if parsing fails but the forwardlist
+// requires a valid *net.TCPAddr to operate, so we enforce that restriction here.
+func parseAddr(b []byte) (*net.TCPAddr, error) {
+	addr, b, ok := parseString(b)
+	if !ok {
+		return nil, ParseError{msgChannelOpen}
+	}
+	port, _, ok := parseUint32(b)
+	if !ok {
+		return nil, ParseError{msgChannelOpen}
+	}
+	ip := net.ParseIP(string(addr))
+	if ip == nil {
+		return nil, ParseError{msgChannelOpen}
+	}
+	return &net.TCPAddr{ip, int(port)}, nil
+}
+
 // Dial connects to the given network address using net.Dial and
 // then initiates a SSH handshake, returning the resulting client connection.
 func Dial(network, addr string, config *ClientConfig) (*ClientConn, error) {
@@ -383,6 +449,14 @@ func (c *clientChan) sendClose() error {
 	}))
 }
 
+func (c *clientChan) sendWindowAdj(n int) error {
+	msg := windowAdjustMsg{
+		PeersId:         c.peersId,
+		AdditionalBytes: uint32(n),
+	}
+	return c.writePacket(marshal(msgChannelWindowAdjust, msg))
+}
+
 // Close closes the channel. This does not close the underlying connection.
 func (c *clientChan) Close() error {
 	if !c.weClosed {
@@ -522,11 +596,7 @@ func (r *chanReader) Read(data []byte) (int, error) {
 		if len(r.buf) > 0 {
 			n := copy(data, r.buf)
 			r.buf = r.buf[n:]
-			msg := windowAdjustMsg{
-				PeersId:         r.clientChan.peersId,
-				AdditionalBytes: uint32(n),
-			}
-			return n, r.clientChan.writePacket(marshal(msgChannelWindowAdjust, msg))
+			return n, r.clientChan.sendWindowAdj(n)
 		}
 		r.buf, ok = <-r.data
 		if !ok {

+ 29 - 0
ssh/example_test.go

@@ -8,6 +8,8 @@ import (
 	"bytes"
 	"fmt"
 	"io/ioutil"
+	"log"
+	"net/http"
 
 	"code.google.com/p/go.crypto/ssh/terminal"
 )
@@ -120,3 +122,30 @@ func ExampleDial() {
 	}
 	fmt.Println(b.String())
 }
+
+func ExampleClientConn_Listen() {
+	config := &ClientConfig{
+		User: "username",
+		Auth: []ClientAuth{
+			ClientAuthPassword(password("password")),
+		},
+	}
+	// Dial your ssh server.
+	conn, err := Dial("tcp", "localhost:22", config)
+	if err != nil {
+		log.Fatalf("unable to connect: %s", err)
+	}
+	defer conn.Close()
+
+	// Request the remote side to open port 8080 on all interfaces.
+	l, err := conn.Listen("tcp", "0.0.0.0:8080")
+	if err != nil {
+		log.Fatalf("unable to register tcp forward: %v", err)
+	}
+	defer l.Close()
+
+	// Serve HTTP with your SSH server acting as a reverse proxy.
+	http.Serve(l, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+		fmt.Fprintf(resp, "Hello world!\n")
+	}))
+}

+ 1 - 1
ssh/messages.go

@@ -139,7 +139,7 @@ type channelOpenConfirmMsg struct {
 // See RFC 4254, section 5.1.
 type channelOpenFailureMsg struct {
 	PeersId  uint32
-	Reason   uint32
+	Reason   RejectionReason
 	Message  string
 	Language string
 }

+ 161 - 16
ssh/tcpip.go

@@ -9,9 +9,154 @@ import (
 	"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 @@ func (c *ClientConn) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, err
 	if err != nil {
 		return nil, err
 	}
-	return &tcpchanconn{
-		tcpchan: ch,
+	return &tcpChanConn{
+		tcpChan: ch,
 		laddr:   laddr,
 		raddr:   raddr,
 	}, nil
@@ -59,7 +204,7 @@ type channelOpenDirectMsg struct {
 
 // 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 @@ func (c *ClientConn) dial(laddr string, lport int, raddr string, rport int) (*tc
 		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 @@ func (t *tcpchanconn) SetDeadline(deadline time.Time) error {
 // 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")
 }