go.crypto/ssh: import gosshnew.
See https://groups.google.com/d/msg/Golang-nuts/AoVxQ4bB5XQ/i8kpMxdbVlEJ R=hanwen CC=golang-codereviews https://golang.org/cl/86190043
This commit is contained in:
+137
-440
@@ -5,403 +5,158 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ClientConn represents the client side of an SSH connection.
|
||||
type ClientConn struct {
|
||||
transport *transport
|
||||
config *ClientConfig
|
||||
chanList // channels associated with this connection
|
||||
forwardList // forwarded tcpip connections from the remote side
|
||||
globalRequest
|
||||
// Client implements a traditional SSH client that supports shells,
|
||||
// subprocesses, port forwarding and tunneled dialing.
|
||||
type Client struct {
|
||||
Conn
|
||||
|
||||
// Address as passed to the Dial function.
|
||||
dialAddress string
|
||||
|
||||
serverVersion string
|
||||
forwards forwardList // forwarded tcpip connections from the remote side
|
||||
mu sync.Mutex
|
||||
channelHandlers map[string]chan NewChannel
|
||||
}
|
||||
|
||||
type globalRequest struct {
|
||||
sync.Mutex
|
||||
response chan interface{}
|
||||
// HandleChannelOpen returns a channel on which NewChannel requests
|
||||
// for the given type are sent. If the type already is being handled,
|
||||
// nil is returned. The channel is closed when the connection is closed.
|
||||
func (c *Client) HandleChannelOpen(channelType string) <-chan NewChannel {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.channelHandlers == nil {
|
||||
// The SSH channel has been closed.
|
||||
c := make(chan NewChannel)
|
||||
close(c)
|
||||
return c
|
||||
}
|
||||
|
||||
ch := c.channelHandlers[channelType]
|
||||
if ch != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ch = make(chan NewChannel, 16)
|
||||
c.channelHandlers[channelType] = ch
|
||||
return ch
|
||||
}
|
||||
|
||||
// Client returns a new SSH client connection using c as the underlying transport.
|
||||
func Client(c net.Conn, config *ClientConfig) (*ClientConn, error) {
|
||||
return clientWithAddress(c, "", config)
|
||||
// NewClient creates a Client on top of the given connection.
|
||||
func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
|
||||
conn := &Client{
|
||||
Conn: c,
|
||||
channelHandlers: make(map[string]chan NewChannel, 1),
|
||||
}
|
||||
|
||||
go conn.handleGlobalRequests(reqs)
|
||||
go conn.handleChannelOpens(chans)
|
||||
go func() {
|
||||
conn.Wait()
|
||||
conn.forwards.closeAll()
|
||||
}()
|
||||
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip"))
|
||||
return conn
|
||||
}
|
||||
|
||||
func clientWithAddress(c net.Conn, addr string, config *ClientConfig) (*ClientConn, error) {
|
||||
conn := &ClientConn{
|
||||
transport: newTransport(c, config.rand(), true /* is client */),
|
||||
config: config,
|
||||
globalRequest: globalRequest{response: make(chan interface{}, 1)},
|
||||
dialAddress: addr,
|
||||
// NewClientConn establishes an authenticated SSH connection using c
|
||||
// as the underlying transport. The Request and NewChannel channels
|
||||
// must be serviced or the connection will hang.
|
||||
func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) {
|
||||
fullConf := *config
|
||||
fullConf.SetDefaults()
|
||||
conn := &connection{
|
||||
sshConn: sshConn{conn: c},
|
||||
}
|
||||
|
||||
if err := conn.handshake(); err != nil {
|
||||
conn.transport.Close()
|
||||
return nil, fmt.Errorf("handshake failed: %v", err)
|
||||
if err := conn.clientHandshake(addr, &fullConf); err != nil {
|
||||
c.Close()
|
||||
return nil, nil, nil, fmt.Errorf("ssh: handshake failed: %v", err)
|
||||
}
|
||||
go conn.mainLoop()
|
||||
return conn, nil
|
||||
conn.mux = newMux(conn.transport)
|
||||
return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
func (c *ClientConn) Close() error { return c.transport.Close() }
|
||||
|
||||
// LocalAddr returns the local network address.
|
||||
func (c *ClientConn) LocalAddr() net.Addr { return c.transport.LocalAddr() }
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
func (c *ClientConn) RemoteAddr() net.Addr { return c.transport.RemoteAddr() }
|
||||
|
||||
// handshake performs the client side key exchange. See RFC 4253 Section 7.
|
||||
func (c *ClientConn) handshake() error {
|
||||
clientVersion := []byte(packageVersion)
|
||||
if c.config.ClientVersion != "" {
|
||||
clientVersion = []byte(c.config.ClientVersion)
|
||||
// clientHandshake performs the client side key exchange. See RFC 4253 Section
|
||||
// 7.
|
||||
func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) error {
|
||||
c.clientVersion = []byte(packageVersion)
|
||||
if config.ClientVersion != "" {
|
||||
c.clientVersion = []byte(config.ClientVersion)
|
||||
}
|
||||
|
||||
serverVersion, err := exchangeVersions(c.transport.Conn, clientVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.serverVersion = string(serverVersion)
|
||||
|
||||
clientKexInit := kexInitMsg{
|
||||
KexAlgos: c.config.Crypto.kexes(),
|
||||
ServerHostKeyAlgos: supportedHostKeyAlgos,
|
||||
CiphersClientServer: c.config.Crypto.ciphers(),
|
||||
CiphersServerClient: c.config.Crypto.ciphers(),
|
||||
MACsClientServer: c.config.Crypto.macs(),
|
||||
MACsServerClient: c.config.Crypto.macs(),
|
||||
CompressionClientServer: supportedCompressions,
|
||||
CompressionServerClient: supportedCompressions,
|
||||
}
|
||||
kexInitPacket := marshal(msgKexInit, clientKexInit)
|
||||
if err := c.transport.writePacket(kexInitPacket); err != nil {
|
||||
return err
|
||||
}
|
||||
packet, err := c.transport.readPacket()
|
||||
var err error
|
||||
c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var serverKexInit kexInitMsg
|
||||
if err = unmarshal(&serverKexInit, packet, msgKexInit); err != nil {
|
||||
c.transport = newClientTransport(
|
||||
newTransport(c.sshConn.conn, config.Rand, true /* is client */),
|
||||
c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr())
|
||||
if err := c.transport.requestKeyChange(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
algs := findAgreedAlgorithms(&clientKexInit, &serverKexInit)
|
||||
if algs == nil {
|
||||
return errors.New("ssh: no common algorithms")
|
||||
}
|
||||
|
||||
if serverKexInit.FirstKexFollows && algs.kex != serverKexInit.KexAlgos[0] {
|
||||
// The server sent a Kex message for the wrong algorithm,
|
||||
// which we have to ignore.
|
||||
if _, err := c.transport.readPacket(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
kex, ok := kexAlgoMap[algs.kex]
|
||||
if !ok {
|
||||
return fmt.Errorf("ssh: unexpected key exchange algorithm %v", algs.kex)
|
||||
}
|
||||
|
||||
magics := handshakeMagics{
|
||||
clientVersion: clientVersion,
|
||||
serverVersion: serverVersion,
|
||||
clientKexInit: kexInitPacket,
|
||||
serverKexInit: packet,
|
||||
}
|
||||
result, err := kex.Client(c.transport, c.config.rand(), &magics)
|
||||
if err != nil {
|
||||
if packet, err := c.transport.readPacket(); err != nil {
|
||||
return err
|
||||
} else if packet[0] != msgNewKeys {
|
||||
return unexpectedMessageError(msgNewKeys, packet[0])
|
||||
}
|
||||
|
||||
err = verifyHostKeySignature(algs.hostKey, result.HostKey, result.H, result.Signature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if checker := c.config.HostKeyChecker; checker != nil {
|
||||
err = checker.Check(c.dialAddress, c.transport.RemoteAddr(), algs.hostKey, result.HostKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.transport.prepareKeyChange(algs, result)
|
||||
|
||||
if err = c.transport.writePacket([]byte{msgNewKeys}); err != nil {
|
||||
return err
|
||||
}
|
||||
if packet, err = c.transport.readPacket(); err != nil {
|
||||
return err
|
||||
}
|
||||
if packet[0] != msgNewKeys {
|
||||
return UnexpectedMessageError{msgNewKeys, packet[0]}
|
||||
}
|
||||
return c.authenticate()
|
||||
return c.clientAuthenticate(config)
|
||||
}
|
||||
|
||||
// Verify the host key obtained in the key exchange.
|
||||
func verifyHostKeySignature(hostKeyAlgo string, hostKeyBytes []byte, data []byte, signature []byte) error {
|
||||
hostKey, rest, ok := ParsePublicKey(hostKeyBytes)
|
||||
if len(rest) > 0 || !ok {
|
||||
return errors.New("ssh: could not parse hostkey")
|
||||
}
|
||||
|
||||
sig, rest, ok := parseSignatureBody(signature)
|
||||
// verifyHostKeySignature verifies the host key obtained in the key
|
||||
// exchange.
|
||||
func verifyHostKeySignature(hostKey PublicKey, result *kexResult) error {
|
||||
sig, rest, ok := parseSignatureBody(result.Signature)
|
||||
if len(rest) > 0 || !ok {
|
||||
return errors.New("ssh: signature parse error")
|
||||
}
|
||||
if sig.Format != hostKeyAlgo {
|
||||
return fmt.Errorf("ssh: unexpected signature type %q", sig.Format)
|
||||
}
|
||||
|
||||
if !hostKey.Verify(data, sig.Blob) {
|
||||
return errors.New("ssh: host key signature error")
|
||||
}
|
||||
return nil
|
||||
return hostKey.Verify(result.H, sig)
|
||||
}
|
||||
|
||||
// mainLoop reads incoming messages and routes channel messages
|
||||
// to their respective ClientChans.
|
||||
func (c *ClientConn) mainLoop() {
|
||||
defer func() {
|
||||
c.transport.Close()
|
||||
c.chanList.closeAll()
|
||||
c.forwardList.closeAll()
|
||||
}()
|
||||
|
||||
for {
|
||||
packet, err := c.transport.readPacket()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
// TODO(dfc) A note on blocking channel use.
|
||||
// The msg, data and dataExt channels of a clientChan can
|
||||
// cause this loop to block indefinitely if the consumer does
|
||||
// not service them.
|
||||
switch packet[0] {
|
||||
case msgChannelData:
|
||||
if len(packet) < 9 {
|
||||
// malformed data packet
|
||||
return
|
||||
}
|
||||
remoteId := binary.BigEndian.Uint32(packet[1:5])
|
||||
length := binary.BigEndian.Uint32(packet[5:9])
|
||||
packet = packet[9:]
|
||||
|
||||
if length != uint32(len(packet)) {
|
||||
return
|
||||
}
|
||||
ch, ok := c.getChan(remoteId)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch.stdout.write(packet)
|
||||
case msgChannelExtendedData:
|
||||
if len(packet) < 13 {
|
||||
// malformed data packet
|
||||
return
|
||||
}
|
||||
remoteId := binary.BigEndian.Uint32(packet[1:5])
|
||||
datatype := binary.BigEndian.Uint32(packet[5:9])
|
||||
length := binary.BigEndian.Uint32(packet[9:13])
|
||||
packet = packet[13:]
|
||||
|
||||
if length != uint32(len(packet)) {
|
||||
return
|
||||
}
|
||||
// RFC 4254 5.2 defines data_type_code 1 to be data destined
|
||||
// for stderr on interactive sessions. Other data types are
|
||||
// silently discarded.
|
||||
if datatype == 1 {
|
||||
ch, ok := c.getChan(remoteId)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch.stderr.write(packet)
|
||||
}
|
||||
default:
|
||||
decoded, err := decode(packet)
|
||||
if err != nil {
|
||||
if _, ok := err.(UnexpectedMessageError); ok {
|
||||
fmt.Printf("mainLoop: unexpected message: %v\n", err)
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
switch msg := decoded.(type) {
|
||||
case *channelOpenMsg:
|
||||
c.handleChanOpen(msg)
|
||||
case *channelOpenConfirmMsg:
|
||||
ch, ok := c.getChan(msg.PeersId)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch.msg <- msg
|
||||
case *channelOpenFailureMsg:
|
||||
ch, ok := c.getChan(msg.PeersId)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch.msg <- msg
|
||||
case *channelCloseMsg:
|
||||
ch, ok := c.getChan(msg.PeersId)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch.Close()
|
||||
close(ch.msg)
|
||||
c.chanList.remove(msg.PeersId)
|
||||
case *channelEOFMsg:
|
||||
ch, ok := c.getChan(msg.PeersId)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch.stdout.eof()
|
||||
// RFC 4254 is mute on how EOF affects dataExt messages but
|
||||
// it is logical to signal EOF at the same time.
|
||||
ch.stderr.eof()
|
||||
case *channelRequestSuccessMsg:
|
||||
ch, ok := c.getChan(msg.PeersId)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch.msg <- msg
|
||||
case *channelRequestFailureMsg:
|
||||
ch, ok := c.getChan(msg.PeersId)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch.msg <- msg
|
||||
case *channelRequestMsg:
|
||||
ch, ok := c.getChan(msg.PeersId)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch.msg <- msg
|
||||
case *windowAdjustMsg:
|
||||
ch, ok := c.getChan(msg.PeersId)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !ch.remoteWin.add(msg.AdditionalBytes) {
|
||||
// invalid window update
|
||||
return
|
||||
}
|
||||
case *globalRequestMsg:
|
||||
// This handles keepalive messages and matches
|
||||
// the behaviour of OpenSSH.
|
||||
if msg.WantReply {
|
||||
c.transport.writePacket(marshal(msgRequestFailure, globalRequestFailureMsg{}))
|
||||
}
|
||||
case *globalRequestSuccessMsg, *globalRequestFailureMsg:
|
||||
c.globalRequest.response <- msg
|
||||
case *disconnectMsg:
|
||||
return
|
||||
default:
|
||||
fmt.Printf("mainLoop: unhandled message %T: %v\n", msg, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle channel open messages from the remote side.
|
||||
func (c *ClientConn) handleChanOpen(msg *channelOpenMsg) {
|
||||
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
|
||||
c.sendConnectionFailed(msg.PeersId)
|
||||
}
|
||||
|
||||
switch msg.ChanType {
|
||||
case "forwarded-tcpip":
|
||||
laddr, rest, ok := parseTCPAddr(msg.TypeSpecificData)
|
||||
if !ok {
|
||||
// invalid request
|
||||
c.sendConnectionFailed(msg.PeersId)
|
||||
return
|
||||
}
|
||||
|
||||
l, ok := c.forwardList.lookup(*laddr)
|
||||
if !ok {
|
||||
// TODO: print on a more structured log.
|
||||
fmt.Println("could not find forward list entry for", laddr)
|
||||
// Section 7.2, implementations MUST reject spurious incoming
|
||||
// connections.
|
||||
c.sendConnectionFailed(msg.PeersId)
|
||||
return
|
||||
}
|
||||
raddr, rest, ok := parseTCPAddr(rest)
|
||||
if !ok {
|
||||
// invalid request
|
||||
c.sendConnectionFailed(msg.PeersId)
|
||||
return
|
||||
}
|
||||
ch := c.newChan(c.transport)
|
||||
ch.remoteId = msg.PeersId
|
||||
ch.remoteWin.add(msg.PeersWindow)
|
||||
ch.maxPacket = msg.MaxPacketSize
|
||||
|
||||
m := channelOpenConfirmMsg{
|
||||
PeersId: ch.remoteId,
|
||||
MyId: ch.localId,
|
||||
MyWindow: channelWindowSize,
|
||||
MaxPacketSize: channelMaxPacketSize,
|
||||
}
|
||||
|
||||
c.transport.writePacket(marshal(msgChannelOpenConfirm, m))
|
||||
l <- forward{ch, raddr}
|
||||
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.transport.writePacket(marshal(msgChannelOpenFailure, m))
|
||||
}
|
||||
}
|
||||
|
||||
// sendGlobalRequest sends a global request message as specified
|
||||
// in RFC4254 section 4. To correctly synchronise messages, a lock
|
||||
// is held internally until a response is returned.
|
||||
func (c *ClientConn) sendGlobalRequest(m interface{}) (*globalRequestSuccessMsg, error) {
|
||||
c.globalRequest.Lock()
|
||||
defer c.globalRequest.Unlock()
|
||||
if err := c.transport.writePacket(marshal(msgGlobalRequest, m)); err != nil {
|
||||
// NewSession opens a new Session for this client. (A session is a remote
|
||||
// execution of a program.)
|
||||
func (c *Client) NewSession() (*Session, error) {
|
||||
ch, in, err := c.OpenChannel("session", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := <-c.globalRequest.response
|
||||
if r, ok := r.(*globalRequestSuccessMsg); ok {
|
||||
return r, nil
|
||||
}
|
||||
return nil, errors.New("request failed")
|
||||
return newSession(ch, in)
|
||||
}
|
||||
|
||||
// sendConnectionFailed rejects an incoming channel identified
|
||||
// by remoteId.
|
||||
func (c *ClientConn) sendConnectionFailed(remoteId uint32) error {
|
||||
m := channelOpenFailureMsg{
|
||||
PeersId: remoteId,
|
||||
Reason: ConnectionFailed,
|
||||
Message: "invalid request",
|
||||
Language: "en_US.UTF-8",
|
||||
func (c *Client) handleGlobalRequests(incoming <-chan *Request) {
|
||||
for r := range incoming {
|
||||
// This handles keepalive messages and matches
|
||||
// the behaviour of OpenSSH.
|
||||
r.Reply(false, nil)
|
||||
}
|
||||
return c.transport.writePacket(marshal(msgChannelOpenFailure, m))
|
||||
}
|
||||
|
||||
// handleChannelOpens channel open messages from the remote side.
|
||||
func (c *Client) handleChannelOpens(in <-chan NewChannel) {
|
||||
for ch := range in {
|
||||
c.mu.Lock()
|
||||
handler := c.channelHandlers[ch.ChannelType()]
|
||||
c.mu.Unlock()
|
||||
|
||||
if handler != nil {
|
||||
handler <- ch
|
||||
} else {
|
||||
ch.Reject(UnknownChannelType, fmt.Sprintf("unknown channel type: %v", ch.ChannelType()))
|
||||
}
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
for _, ch := range c.channelHandlers {
|
||||
close(ch)
|
||||
}
|
||||
c.channelHandlers = nil
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// parseTCPAddr parses the originating address from the remote into a *net.TCPAddr.
|
||||
@@ -413,7 +168,7 @@ func parseTCPAddr(b []byte) (*net.TCPAddr, []byte, bool) {
|
||||
return nil, b, false
|
||||
}
|
||||
port, b, ok := parseUint32(b)
|
||||
if !ok {
|
||||
if !ok || port == 0 || port > 65535 {
|
||||
return nil, b, false
|
||||
}
|
||||
ip := net.ParseIP(string(addr))
|
||||
@@ -423,102 +178,44 @@ func parseTCPAddr(b []byte) (*net.TCPAddr, []byte, bool) {
|
||||
return &net.TCPAddr{IP: ip, Port: int(port)}, b, true
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Dial starts a client connection to the given SSH server. It is a
|
||||
// convenience function that connects to the given network address,
|
||||
// initiates the SSH handshake, and then sets up a Client. For access
|
||||
// to incoming channels and requests, use net.Dial with NewClientConn
|
||||
// instead.
|
||||
func Dial(network, addr string, config *ClientConfig) (*Client, error) {
|
||||
conn, err := net.Dial(network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return clientWithAddress(conn, addr, config)
|
||||
c, chans, reqs, err := NewClientConn(conn, addr, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewClient(c, chans, reqs), nil
|
||||
}
|
||||
|
||||
// A ClientConfig structure is used to configure a ClientConn. After one has
|
||||
// been passed to an SSH function it must not be modified.
|
||||
// A ClientConfig structure is used to configure a Client. It must not be
|
||||
// modified after having been passed to an SSH function.
|
||||
type ClientConfig struct {
|
||||
// Rand provides the source of entropy for key exchange. If Rand is
|
||||
// nil, the cryptographic random reader in package crypto/rand will
|
||||
// be used.
|
||||
Rand io.Reader
|
||||
// Config contains configuration that is shared between clients and
|
||||
// servers.
|
||||
Config
|
||||
|
||||
// The username to authenticate.
|
||||
// User contains the username to authenticate as.
|
||||
User string
|
||||
|
||||
// A slice of ClientAuth methods. Only the first instance
|
||||
// of a particular RFC 4252 method will be used during authentication.
|
||||
Auth []ClientAuth
|
||||
// Auth contains possible authentication methods to use with the
|
||||
// server. Only the first instance of a particular RFC 4252 method will
|
||||
// be used during authentication.
|
||||
Auth []AuthMethod
|
||||
|
||||
// HostKeyChecker, if not nil, is called during the cryptographic
|
||||
// handshake to validate the server's host key. A nil HostKeyChecker
|
||||
// HostKeyCallback, if not nil, is called during the cryptographic
|
||||
// handshake to validate the server's host key. A nil HostKeyCallback
|
||||
// implies that all host keys are accepted.
|
||||
HostKeyChecker HostKeyChecker
|
||||
HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
|
||||
|
||||
// Cryptographic-related configuration.
|
||||
Crypto CryptoConfig
|
||||
|
||||
// The identification string that will be used for the connection.
|
||||
// If empty, a reasonable default is used.
|
||||
// ClientVersion contains the version identification string that will
|
||||
// be used for the connection. If empty, a reasonable default is used.
|
||||
ClientVersion string
|
||||
}
|
||||
|
||||
func (c *ClientConfig) rand() io.Reader {
|
||||
if c.Rand == nil {
|
||||
return rand.Reader
|
||||
}
|
||||
return c.Rand
|
||||
}
|
||||
|
||||
// Thread safe channel list.
|
||||
type chanList struct {
|
||||
// protects concurrent access to chans
|
||||
sync.Mutex
|
||||
// chans are indexed by the local id of the channel, clientChan.localId.
|
||||
// The PeersId value of messages received by ClientConn.mainLoop is
|
||||
// used to locate the right local clientChan in this slice.
|
||||
chans []*clientChan
|
||||
}
|
||||
|
||||
// Allocate a new ClientChan with the next avail local id.
|
||||
func (c *chanList) newChan(p packetConn) *clientChan {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
for i := range c.chans {
|
||||
if c.chans[i] == nil {
|
||||
ch := newClientChan(p, uint32(i))
|
||||
c.chans[i] = ch
|
||||
return ch
|
||||
}
|
||||
}
|
||||
i := len(c.chans)
|
||||
ch := newClientChan(p, uint32(i))
|
||||
c.chans = append(c.chans, ch)
|
||||
return ch
|
||||
}
|
||||
|
||||
func (c *chanList) getChan(id uint32) (*clientChan, bool) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
if id >= uint32(len(c.chans)) {
|
||||
return nil, false
|
||||
}
|
||||
return c.chans[id], true
|
||||
}
|
||||
|
||||
func (c *chanList) remove(id uint32) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.chans[id] = nil
|
||||
}
|
||||
|
||||
func (c *chanList) closeAll() {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
for _, ch := range c.chans {
|
||||
if ch == nil {
|
||||
continue
|
||||
}
|
||||
ch.Close()
|
||||
close(ch.msg)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user