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