// 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 sshimport ()// Listen requests the remote peer open a listening socket on// addr. Incoming connections will be available by calling Accept on// the returned net.Listener. The listener must be serviced, or the// SSH connection may hang.// N must be "tcp", "tcp4", "tcp6", or "unix".func ( *Client) (, string) (net.Listener, error) {switch {case"tcp", "tcp4", "tcp6": , := net.ResolveTCPAddr(, )if != nil {returnnil, }return .ListenTCP()case"unix":return .ListenUnix()default:returnnil, fmt.Errorf("ssh: unsupported protocol: %s", ) }}// Automatic port allocation is broken with OpenSSH before 6.0. See// also https://bugzilla.mindrot.org/show_bug.cgi?id=2017. In// particular, OpenSSH 5.9 sends a channelOpenMsg with port number 0,// rather than the actual port number. This means you can never open// two different listeners with auto allocated ports. We work around// this by trying explicit ports until we succeed.const openSSHPrefix = "OpenSSH_"var portRandomizer = rand.New(rand.NewSource(time.Now().UnixNano()))// isBrokenOpenSSHVersion returns true if the given version string// specifies a version of OpenSSH that is known to have a bug in port// forwarding.func isBrokenOpenSSHVersion( string) bool { := strings.Index(, openSSHPrefix)if < 0 {returnfalse } += len(openSSHPrefix) := for ; < len(); ++ {if [] < '0' || [] > '9' {break } } , := strconv.Atoi([:])return < 6}// autoPortListenWorkaround simulates automatic port allocation by// trying random ports repeatedly.func ( *Client) ( *net.TCPAddr) (net.Listener, error) {varnet.Listenervarerrorconst = 10for := 0; < ; ++ { := * .Port = 1024 + portRandomizer.Intn(60000) , = .ListenTCP(&)if == nil { .Port = .Portreturn , } }returnnil, fmt.Errorf("ssh: listen on random port failed after %d tries: %v", , )}// RFC 4254 7.1type channelForwardMsg struct { addr string rport uint32}// handleForwards starts goroutines handling forwarded connections.// It's called on first use by (*Client).ListenTCP to not launch// goroutines until needed.func ( *Client) () {go .forwards.handleChannels(.HandleChannelOpen("forwarded-tcpip"))go .forwards.handleChannels(.HandleChannelOpen("forwarded-streamlocal@openssh.com"))}// 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 ( *Client) ( *net.TCPAddr) (net.Listener, error) { .handleForwardsOnce.Do(.handleForwards)if .Port == 0 && isBrokenOpenSSHVersion(string(.ServerVersion())) {return .autoPortListenWorkaround() } := channelForwardMsg{ .IP.String(),uint32(.Port), }// send message , , := .SendRequest("tcpip-forward", true, Marshal(&))if != nil {returnnil, }if ! {returnnil, errors.New("ssh: tcpip-forward request denied by peer") }// If the original port was 0, then the remote side will // supply a real port number in the response.if .Port == 0 {varstruct {uint32 }if := Unmarshal(, &); != nil {returnnil, } .Port = int(.) }// Register this forward, using the port number we obtained. := .forwards.add()return &tcpListener{, , }, 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.Addr c chanforward}// forward represents an incoming forwarded tcpip connection. The// arguments to add/remove/lookup should be address as specified in// the original forward-request.type forward struct { newCh NewChannel// the ssh client channel underlying this forward raddr net.Addr// the raddr of the incoming connection}func ( *forwardList) ( net.Addr) chanforward { .Lock()defer .Unlock() := forwardEntry{laddr: ,c: make(chanforward, 1), } .entries = append(.entries, )return .c}// See RFC 4254, section 7.2type forwardedTCPPayload struct { Addr string Port uint32 OriginAddr string OriginPort uint32}// parseTCPAddr parses the originating address from the remote into a *net.TCPAddr.func parseTCPAddr( string, uint32) (*net.TCPAddr, error) {if == 0 || > 65535 {returnnil, fmt.Errorf("ssh: port number out of range: %d", ) } := net.ParseIP(string())if == nil {returnnil, fmt.Errorf("ssh: cannot parse IP address %q", ) }return &net.TCPAddr{IP: , Port: int()}, nil}func ( *forwardList) ( <-chanNewChannel) {for := range {var (net.Addrnet.Addrerror )switch := .ChannelType(); {case"forwarded-tcpip":varforwardedTCPPayloadif = Unmarshal(.ExtraData(), &); != nil { .Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+.Error())continue }// RFC 4254 section 7.2 specifies that incoming // addresses should list the address, in string // format. It is implied that this should be an IP // address, as it would be impossible to connect to it // otherwise. , = parseTCPAddr(.Addr, .Port)if != nil { .Reject(ConnectionFailed, .Error())continue } , = parseTCPAddr(.OriginAddr, .OriginPort)if != nil { .Reject(ConnectionFailed, .Error())continue }case"forwarded-streamlocal@openssh.com":varforwardedStreamLocalPayloadif = Unmarshal(.ExtraData(), &); != nil { .Reject(ConnectionFailed, "could not parse forwarded-streamlocal@openssh.com payload: "+.Error())continue } = &net.UnixAddr{Name: .SocketPath,Net: "unix", } = &net.UnixAddr{Name: "@",Net: "unix", }default:panic(fmt.Errorf("ssh: unknown channel type %s", )) }if := .forward(, , ); ! {// Section 7.2, implementations MUST reject spurious incoming // connections. .Reject(Prohibited, "no forward for address")continue } }}// remove removes the forward entry, and the channel feeding its// listener.func ( *forwardList) ( net.Addr) { .Lock()defer .Unlock()for , := range .entries {if .Network() == .laddr.Network() && .String() == .laddr.String() { .entries = append(.entries[:], .entries[+1:]...)close(.c)return } }}// closeAll closes and clears all forwards.func ( *forwardList) () { .Lock()defer .Unlock()for , := range .entries {close(.c) } .entries = nil}func ( *forwardList) (, net.Addr, NewChannel) bool { .Lock()defer .Unlock()for , := range .entries {if .Network() == .laddr.Network() && .String() == .laddr.String() { .c <- forward{newCh: , raddr: }returntrue } }returnfalse}type tcpListener struct { laddr *net.TCPAddr conn *Client in <-chanforward}// Accept waits for and returns the next connection to the listener.func ( *tcpListener) () (net.Conn, error) { , := <-.inif ! {returnnil, io.EOF } , , := .newCh.Accept()if != nil {returnnil, }goDiscardRequests()return &chanConn{Channel: ,laddr: .laddr,raddr: .raddr, }, nil}// Close closes the listener.func ( *tcpListener) () error { := channelForwardMsg{ .laddr.IP.String(),uint32(.laddr.Port), }// this also closes the listener. .conn.forwards.remove(.laddr) , , := .conn.SendRequest("cancel-tcpip-forward", true, Marshal(&))if == nil && ! { = errors.New("ssh: cancel-tcpip-forward failed") }return}// Addr returns the listener's network address.func ( *tcpListener) () net.Addr {return .laddr}// DialContext initiates a connection to the addr from the remote host.//// The provided Context must be non-nil. If the context expires before the// connection is complete, an error is returned. Once successfully connected,// any expiration of the context will not affect the connection.//// See func Dial for additional information.func ( *Client) ( context.Context, , string) (net.Conn, error) {if := .Err(); != nil {returnnil, }typestruct {net.Connerror } := make(chan )gofunc() { , := .Dial(, )select {case<- {, }:case<-.Done():if != nil { .Close() } } }()select {case := <-:return ., .case<-.Done():returnnil, .Err() }}// Dial initiates a connection to the addr from the remote host.// The resulting connection has a zero LocalAddr() and RemoteAddr().func ( *Client) (, string) (net.Conn, error) {varChannelswitch {case"tcp", "tcp4", "tcp6":// Parse the address into host and numeric port. , , := net.SplitHostPort()if != nil {returnnil, } , := strconv.ParseUint(, 10, 16)if != nil {returnnil, } , = .dial(net.IPv4zero.String(), 0, , int())if != nil {returnnil, }// Use a zero address for local and remote address. := &net.TCPAddr{IP: net.IPv4zero,Port: 0, }return &chanConn{Channel: ,laddr: ,raddr: , }, nilcase"unix":varerror , = .dialStreamLocal()if != nil {returnnil, }return &chanConn{Channel: ,laddr: &net.UnixAddr{Name: "@",Net: "unix", },raddr: &net.UnixAddr{Name: ,Net: "unix", }, }, nildefault:returnnil, fmt.Errorf("ssh: unsupported protocol: %s", ) }}// 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 ( *Client) ( string, , *net.TCPAddr) (net.Conn, error) {if == nil { = &net.TCPAddr{IP: net.IPv4zero,Port: 0, } } , := .dial(.IP.String(), .Port, .IP.String(), .Port)if != nil {returnnil, }return &chanConn{Channel: ,laddr: ,raddr: , }, nil}// RFC 4254 7.2type channelOpenDirectMsg struct { raddr string rport uint32 laddr string lport uint32}func ( *Client) ( string, int, string, int) (Channel, error) { := channelOpenDirectMsg{raddr: ,rport: uint32(),laddr: ,lport: uint32(), } , , := .OpenChannel("direct-tcpip", Marshal(&))if != nil {returnnil, }goDiscardRequests()return , nil}type tcpChan struct {Channel// the backing channel}// chanConn fulfills the net.Conn interface without// the tcpChan having to hold laddr or raddr directly.type chanConn struct {Channel laddr, raddr net.Addr}// LocalAddr returns the local network address.func ( *chanConn) () net.Addr {return .laddr}// RemoteAddr returns the remote network address.func ( *chanConn) () net.Addr {return .raddr}// SetDeadline sets the read and write deadlines associated// with the connection.func ( *chanConn) ( time.Time) error {if := .SetReadDeadline(); != nil {return }return .SetWriteDeadline()}// 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 ( *chanConn) ( time.Time) error {// for compatibility with previous version, // the error message contains "tcpChan"returnerrors.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 ( *chanConn) ( time.Time) error {returnerrors.New("ssh: tcpChan: deadline not supported")}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.