• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
思念以南
博客园    首页    新随笔    联系   管理    订阅  订阅

SSH客户端三件套

1、远程连接 shell

SSH(Secure Shell)协议在远程登录时比较常用,但是除此之外还有一些其它的功能也很好用,比如端口映射,X11转发,sftp文件传输等。

以下三篇文章将介绍golang版SSH的远程登录功能,端口映射功能及sftp文件传输功能。X11包含GUI的一些操作,没有找到相关的包,故不做介绍

通过golang自带的ssh包 golang.org/x/crypto/ssh 可以实现远程登录功能,默认是不支持tab键和上下箭头的,通过导入 golang.org/x/crypto/ssh/terminal 来创建VT100终端可以支持tab等功能,让golang版本的ssh客户端体验和平时用的其它客户端差不多。

package main

import (
    "golang.org/x/crypto/ssh"
    "golang.org/x/crypto/ssh/terminal"
    "log"
    "os"
    "time"
)

/**
golang版本的SSH客户端
SSH协议RFC文档
https://tools.ietf.org/html/rfc4254

一个ssh连接可以打开多个会话session
linux tty和pty区别
开机后登录系统的终端称为tty
远程登录的终端称为pty
pts是pty的实现方式
w命令可以显示当前系统登录的终端列表
针对交互式会话的操作
1.请求伪终端 pty-req
2.X11转发 x11-req
3.X11通道 x11
4.环境变量 env
5.启动shell或命令 shell/exec/subsystem

默认不支持上下键和tab键,还不支持clear清屏指令
通过VT100终端支持tab和clear指令
VT100终端包括一些控制符,可以在终端中显示不同颜色,支持光标控制,清屏指令等
http://www.termsys.demon.co.uk/vtansi.htm
*/
func main() {
    sshConfig := &ssh.ClientConfig{
        User: "user",
        Auth: []ssh.AuthMethod{
            ssh.Password("123456"),
        },
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
        ClientVersion:   "",
        Timeout:         10 * time.Second,
    }
    //建立与SSH服务器的连接
    sshClient, err := ssh.Dial("tcp", "192.168.1.8:22", sshConfig)
    if err != nil {
        log.Fatalln(err.Error())
    }
    defer sshClient.Close()
    log.Println("sessionId: ", sshClient.SessionID())
    log.Println("user: ", sshClient.User())
    log.Println("ssh server version: ", string(sshClient.ServerVersion()))
    log.Println("ssh client version: ", string(sshClient.ClientVersion()))

    //打开交互式会话(A session is a remote execution of a program.)
    //https://tools.ietf.org/html/rfc4254#page-10
    session, err := sshClient.NewSession()
    if err != nil {
        log.Fatalln("Failed to create ssh session", err)
    }

    defer session.Close()

    modes := ssh.TerminalModes{
        ssh.ECHO:          1,     //打开回显
        ssh.TTY_OP_ISPEED: 14400, //输入速率 14.4kbaud
        ssh.TTY_OP_OSPEED: 14400, //输出速率 14.4kbaud
        ssh.VSTATUS:       1,
    }

    //使用VT100终端来实现tab键提示,上下键查看历史命令,clear键清屏等操作
    //VT100 start
    //windows下不支持VT100
    fd := int(os.Stdin.Fd())
    oldState, err := terminal.MakeRaw(fd)
    if err != nil {
        log.Fatalln(err.Error())
    }
    defer terminal.Restore(fd, oldState)
    //VT100 end

    termWidth, termHeight, err := terminal.GetSize(fd)

    session.Stdin = os.Stdin
    session.Stdout = os.Stdout
    session.Stderr = os.Stderr

    //打开伪终端
    //https://tools.ietf.org/html/rfc4254#page-11
    err = session.RequestPty("xterm", termHeight, termWidth, modes)
    if err != nil {
        log.Fatalln(err.Error())
    }

    //启动一个远程shell
    //https://tools.ietf.org/html/rfc4254#page-13
    err = session.Shell()
    if err != nil {
        log.Fatalln(err.Error())
    }

    //等待远程命令结束或远程shell退出
    err = session.Wait()
    if err != nil {
        log.Fatalln(err.Error())
    }
}

2 、端口映射 port forward

SSH端口映射的例子,通过创建ssh隧道,将远程服务器上的5900端口映射到本地的5900端口

package main

import (
    "fmt"
    "golang.org/x/crypto/ssh"
    "io"
    "log"
    "net"
    "sync"
    "time"
)

const (
    //本地监听地址
    laddr = "localhost:5900"
    //远程服务地址
    raddr   = "localhost:5900"
    sshaddr = "192.168.1.8:22"
)

/**
基于ssh连接的端口转发
https://tools.ietf.org/html/rfc4254#page-16 TCP/IP Port Forwarding

通过ssh隧道将远程服务器(192.168.1.8)上的5900端口映射到本地5900端口
*/
func main() {

    sshConfig := &ssh.ClientConfig{
        User: "user",
        Auth: []ssh.AuthMethod{
            ssh.Password("123456"),
        },
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
        ClientVersion:   "",
        Timeout:         10 * time.Second,
    }

    //监听本地映射端口
    listener, err := net.Listen("tcp", laddr)
    if err != nil {
        log.Fatalln(err.Error())
    }
    defer listener.Close()
    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Println(err)
            continue
        }
        //客户端连接
        go portForward(conn, sshConfig)
    }
}

/**
conn: 客户端到本地映射端口的连接
sshConfig: ssh配置
*/
func portForward(conn net.Conn, sshConfig *ssh.ClientConfig) {
    defer conn.Close()

    //建立与SSH服务器的连接
    sshClient, err := ssh.Dial("tcp", sshaddr, sshConfig)
    if err != nil {
        log.Fatalln(err.Error())
    }
    defer sshClient.Close()

    //建立ssh到后端服务的连接
    forwardConn, err := sshClient.Dial("tcp", raddr)
    if err != nil {
        log.Fatalln(err.Error())
    }

    log.Println("ssh端口映射隧道建立成功")

    defer forwardConn.Close()

    var wait2close = sync.WaitGroup{}
    wait2close.Add(1)

    go func() {
        n, err := io.Copy(forwardConn, conn)
        if err != nil {
            log.Fatalln(err.Error())
            wait2close.Done()
        }
        log.Printf("入流量共%s", formatFlowSize(n))
    }()

    go func() {
        n, err := io.Copy(conn, forwardConn)
        if err != nil {
            log.Fatalln(err.Error())
            wait2close.Done()
        }
        log.Printf("出流量共%s", formatFlowSize(n))
    }()

    wait2close.Wait()
    log.Println("ssh端口映射隧道关闭")
}

// 字节的单位转换 保留两位小数
func formatFlowSize(s int64) (size string) {
    if s < 1024 {
        return fmt.Sprintf("%.2fB", float64(s)/float64(1))
    } else if s < (1024 * 1024) {
        return fmt.Sprintf("%.2fKB", float64(s)/float64(1024))
    } else if s < (1024 * 1024 * 1024) {
        return fmt.Sprintf("%.2fMB", float64(s)/float64(1024*1024))
    } else if s < (1024 * 1024 * 1024 * 1024) {
        return fmt.Sprintf("%.2fGB", float64(s)/float64(1024*1024*1024))
    } else if s < (1024 * 1024 * 1024 * 1024 * 1024) {
        return fmt.Sprintf("%.2fTB", float64(s)/float64(1024*1024*1024*1024))
    } else { //if s < (1024 * 1024 * 1024 * 1024 * 1024 * 1024)
        return fmt.Sprintf("%.2fEB", float64(s)/float64(1024*1024*1024*1024*1024))
    }
}

3、 文件传输sftp

sftp (SSH File Transfer Protocol) 基于ssh安全的文件传输协议

通过sftp可以实现文件上传下载操作

需要导入https://github.com/pkg/sftp包来实现

简单的例子:

上传本地文件到远程服务器

下载远程服务器上的文件到本地

package main

import (
    "fmt"
    "github.com/pkg/sftp"
    "golang.org/x/crypto/ssh"
    "io"
    "log"
    "math/rand"
    "os"
    "time"
)

//https://github.com/pkg/sftp
//
func main() {
    sshConfig := &ssh.ClientConfig{
        User: "user",
        Auth: []ssh.AuthMethod{
            ssh.Password("123456"),
        },
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
        ClientVersion:   "",
        Timeout:         10 * time.Second,
    }

    //建立与SSH服务器的连接
    sshClient, err := ssh.Dial("tcp", "192.168.1.8:22", sshConfig)
    if err != nil {
        log.Fatalln(err.Error())
    }
    defer sshClient.Close()

    sftpClient, err := sftp.NewClient(sshClient)
    if err != nil {
        log.Fatalln(err.Error())
    }

    defer sftpClient.Close()

    //获取当前目录
    cwd, err := sftpClient.Getwd()
    if err != nil {
        log.Fatalln(err.Error())
    }
    log.Println("当前目录:", cwd)

    //显示文件/目录详情
    fi, err := sftpClient.Lstat(cwd)
    log.Println(fi)

    {
        //上传文件(将本地file.dat文件通过sftp传到远程服务器)
        remoteFileName := fmt.Sprintf("upload_by_sftp_%d.dat", rand.Int())
        remoteFile, err := sftpClient.Create(sftp.Join(cwd, remoteFileName))
        if err != nil {
            log.Fatalln(err.Error())
        }
        defer remoteFile.Close()

        localFileName := "file.dat"
        //打开本地文件file.dat
        localFile, err := os.Open(localFileName)
        if err != nil {
            log.Fatalln(err.Error())
        }
        defer localFile.Close()

        //本地文件流拷贝到上传文件流
        n, err := io.Copy(remoteFile, localFile)
        if err != nil {
            log.Fatalln(err.Error())
        }

        //获取本地文件大小
        localFileInfo, err := os.Stat(localFileName)
        if err != nil {
            log.Fatalln(err.Error())
        }

        log.Printf("文件上传成功[%s->%s]本地文件大小:%s,上传文件大小:%s", localFileName, remoteFileName, formatFileSize(localFileInfo.Size()), formatFileSize(n))

        //计算文件MD5
        //windows计算:certutil -hashfile .\file.dat MD5
        //linux计算:md5sum upload_by_sftp_5577006791947779410.dat
    }
    {
        //下载文件
        //将远程服务器的/bin/bash文件下载到本地
        remoteFileName := "/bin/bash"
        remoteFile, err := sftpClient.Open(remoteFileName)
        if err != nil {
            log.Fatalln(err.Error())
        }
        defer remoteFile.Close()

        localFileName := "local-bash"
        localFile, err := os.Create(localFileName)
        if err != nil {
            log.Fatalln(err.Error())
        }
        defer localFile.Close()
        n, err := io.Copy(localFile, remoteFile)
        if err != nil {
            log.Fatalln(err.Error())
        }

        //获取远程文件大小
        remoteFileInfo, err := sftpClient.Stat(remoteFileName)
        if err != nil {
            log.Fatalln(err.Error())
        }
        log.Printf("文件下载成功[%s->%s]远程文件大小:%s,下载文件大小:%s", remoteFileName, localFileName, formatFileSize(remoteFileInfo.Size()), formatFileSize(n))
    }
}

// 字节的单位转换 保留两位小数
func formatFileSize(s int64) (size string) {
    if s < 1024 {
        return fmt.Sprintf("%.2fB", float64(s)/float64(1))
    } else if s < (1024 * 1024) {
        return fmt.Sprintf("%.2fKB", float64(s)/float64(1024))
    } else if s < (1024 * 1024 * 1024) {
        return fmt.Sprintf("%.2fMB", float64(s)/float64(1024*1024))
    } else if s < (1024 * 1024 * 1024 * 1024) {
        return fmt.Sprintf("%.2fGB", float64(s)/float64(1024*1024*1024))
    } else if s < (1024 * 1024 * 1024 * 1024 * 1024) {
        return fmt.Sprintf("%.2fTB", float64(s)/float64(1024*1024*1024*1024))
    } else { //if s < (1024 * 1024 * 1024 * 1024 * 1024 * 1024)
        return fmt.Sprintf("%.2fEB", float64(s)/float64(1024*1024*1024*1024*1024))
    }
}

作者:写个代码容易么
链接:https://www.jianshu.com/p/935a43a41e5e
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

posted @ 2022-05-10 14:54  思念以南  阅读(410)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3