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
This commit is contained in:
+1
-1
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user