SOCKS 拨号器提供传输层,SSH 包在其上完成 SSH 握手。事后再调用 ssh.Dial 会另建一条连接,而不会复用这个传输层。

在已经使用 x/net/proxy 和 x/crypto/ssh 的应用里,辅助函数可以直接返回配置好的 SSH 客户端:

func dialSSHViaSOCKS(proxyAddr, sshAddr string,
    config *ssh.ClientConfig) (*ssh.Client, error) {
    dialer, err := proxy.SOCKS5("tcp", proxyAddr, nil, proxy.Direct)
    if err != nil {
        return nil, err
    }

    conn, err := dialer.Dial("tcp", sshAddr)
    if err != nil {
        return nil, err
    }

    transport, channels, requests, err :=
        ssh.NewClientConn(conn, sshAddr, config)
    if err != nil {
        conn.Close()
        return nil, err
    }

    return ssh.NewClient(transport, channels, requests), nil
}

这里的包名是 golang.org/x/net/proxy 和 golang.org/x/crypto/ssh。proxyAddr 指定 SOCKS 监听地址;sshAddr 以 host:port 指定最终服务器。

在 config 中提供应用惯用的 SSH 用户、认证方式和 HostKeyCallback。knownhosts 包可以用可信的 known_hosts 文件构建该回调。把目标主机名传给 NewClientConn,主机密钥比对才会使用预期的 SSH 身份。

nil 参数表示不使用 SOCKS 凭据。对于允许访问的公共 SSH 目标,SOCKS5 代理列表中无需账号的条目可以提供 proxyAddr,前提是它放行实际的 SSH 主机和端口。需要时传入带代理账号的 proxy.Auth;SSH 认证仍然留在 config 里。

NewClient 接收 NewClientConn 返回的入站通道与请求流,并接管这些客户端机制。工作结束后关闭返回的 SSH 客户端。生产环境的连接处理还需要限时拨号与握手策略;这个辅助函数只演示传输层的交接。

相关文章