This CL is in preparation for 6128059. * rename channel -> serverChan * rename chanlist -> chanList * normalise theirId/MyId/id/peersId -> localId/remoteId R=agl CC=golang-dev https://golang.org/cl/6174046
279 lines
6.9 KiB
Go
279 lines
6.9 KiB
Go
// Copyright 2011 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package ssh
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// 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) {
|
|
laddr, err := net.ResolveTCPAddr(n, addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return c.ListenTCP(laddr)
|
|
}
|
|
|
|
// RFC 4254 7.1
|
|
type channelForwardMsg struct {
|
|
Message string
|
|
WantReply bool
|
|
raddr string
|
|
rport uint32
|
|
}
|
|
|
|
// 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) {
|
|
m := channelForwardMsg{
|
|
"tcpip-forward",
|
|
true, // sendGlobalRequest waits for a reply
|
|
laddr.IP.String(),
|
|
uint32(laddr.Port),
|
|
}
|
|
// send message
|
|
resp, err := c.sendGlobalRequest(m)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// fixup laddr. If the original port was 0, then the remote side will
|
|
// supply one in the resp.
|
|
if laddr.Port == 0 {
|
|
port, _, ok := parseUint32(resp.Data)
|
|
if !ok {
|
|
return nil, errors.New("unable to parse response")
|
|
}
|
|
laddr.Port = int(port)
|
|
}
|
|
|
|
// register this forward
|
|
ch := c.forwardList.add(laddr)
|
|
return &tcpListener{laddr, 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",
|
|
true,
|
|
l.laddr.IP.String(),
|
|
uint32(l.laddr.Port),
|
|
}
|
|
l.conn.forwardList.remove(l.laddr)
|
|
if _, err := l.conn.sendGlobalRequest(m); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// 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
|
|
// remote host. Consider using ssh.DialTCP to avoid this.
|
|
func (c *ClientConn) Dial(n, addr string) (net.Conn, error) {
|
|
raddr, err := net.ResolveTCPAddr(n, addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return c.DialTCP(n, nil, raddr)
|
|
}
|
|
|
|
// DialTCP connects to the remote address raddr on the network net,
|
|
// which must be "tcp", "tcp4", or "tcp6". If laddr is not nil, it is used
|
|
// as the local address for the connection.
|
|
func (c *ClientConn) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error) {
|
|
if laddr == nil {
|
|
laddr = &net.TCPAddr{
|
|
IP: net.IPv4zero,
|
|
Port: 0,
|
|
}
|
|
}
|
|
ch, err := c.dial(laddr.IP.String(), laddr.Port, raddr.IP.String(), raddr.Port)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &tcpChanConn{
|
|
tcpChan: ch,
|
|
laddr: laddr,
|
|
raddr: raddr,
|
|
}, nil
|
|
}
|
|
|
|
// RFC 4254 7.2
|
|
type channelOpenDirectMsg struct {
|
|
ChanType string
|
|
PeersId uint32
|
|
PeersWindow uint32
|
|
MaxPacketSize uint32
|
|
raddr string
|
|
rport uint32
|
|
laddr string
|
|
lport uint32
|
|
}
|
|
|
|
// 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) {
|
|
ch := c.newChan(c.transport)
|
|
if err := c.writePacket(marshal(msgChannelOpen, channelOpenDirectMsg{
|
|
ChanType: "direct-tcpip",
|
|
PeersId: ch.localId,
|
|
PeersWindow: 1 << 14,
|
|
MaxPacketSize: 1 << 15, // RFC 4253 6.1
|
|
raddr: raddr,
|
|
rport: uint32(rport),
|
|
laddr: laddr,
|
|
lport: uint32(lport),
|
|
})); err != nil {
|
|
c.chanList.remove(ch.localId)
|
|
return nil, err
|
|
}
|
|
if err := ch.waitForChannelOpenResponse(); err != nil {
|
|
c.chanList.remove(ch.localId)
|
|
return nil, fmt.Errorf("ssh: unable to open direct tcpip connection: %v", err)
|
|
}
|
|
return &tcpChan{
|
|
clientChan: ch,
|
|
Reader: ch.stdout,
|
|
Writer: ch.stdin,
|
|
}, nil
|
|
}
|
|
|
|
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
|
|
laddr, raddr net.Addr
|
|
}
|
|
|
|
// LocalAddr returns the local network address.
|
|
func (t *tcpChanConn) LocalAddr() net.Addr {
|
|
return t.laddr
|
|
}
|
|
|
|
// RemoteAddr returns the remote network address.
|
|
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 {
|
|
if err := t.SetReadDeadline(deadline); err != nil {
|
|
return err
|
|
}
|
|
return t.SetWriteDeadline(deadline)
|
|
}
|
|
|
|
// SetReadDeadline sets the read deadline.
|
|
// 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")
|
|
}
|
|
|
|
// 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")
|
|
}
|