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
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。