在使用golang的crypto/ssh库上运行多个命令来操作Cisco设备。

huangapple go评论78阅读模式
英文:

Run multiple commands on Cisco using golang crypto/ssh

问题

我有以下拼凑的代码,可以让我发送读写到Cisco设备的ssh控制台,但我目前不确定如何在同一个会话中发送多个命令。

我包含了整个程序,以便作为其他人在将来尝试相同事情的起点,我找不到一个特别适用于Golang<->Cisco的工作示例。

package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	//"strings"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
)

type SSHCommand struct {
	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer
}

type SSHClient struct {
	Config *ssh.ClientConfig
	Host   string
	Port   int
}

func (client *SSHClient) RunCommand(cmd *SSHCommand) error {
	var (
		session *ssh.Session
		err     error
	)

	if session, err = client.newSession(); err != nil {
		return err
	}
	defer session.Close()

	if err = client.prepareCommand(session, cmd); err != nil {
		return err
	}

	err = session.Run("en\r")

	return err
}

func (client *SSHClient) prepareCommand(session *ssh.Session, cmd *SSHCommand) error {

	if cmd.Stdin != nil {
		stdin, err := session.StdinPipe()
		if err != nil {
			return fmt.Errorf("Unable to setup stdin for session: %v", err)
		}
		go io.Copy(stdin, cmd.Stdin)
	}

	if cmd.Stdout != nil {
		stdout, err := session.StdoutPipe()
		if err != nil {
			return fmt.Errorf("Unable to setup stdout for session: %v", err)
		}
		go io.Copy(cmd.Stdout, stdout)
	}

	if cmd.Stderr != nil {
		stderr, err := session.StderrPipe()
		if err != nil {
			return fmt.Errorf("Unable to setup stderr for session: %v", err)
		}
		go io.Copy(cmd.Stderr, stderr)
	}

	return nil
}

func (client *SSHClient) newSession() (*ssh.Session, error) {
	connection, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", client.Host, client.Port), client.Config)
	if err != nil {
		return nil, fmt.Errorf("Failed to dial: %s", err)
	}

	session, err := connection.NewSession()
	if err != nil {
		return nil, fmt.Errorf("Failed to create session: %s", err)
	}

	modes := ssh.TerminalModes{
		// ssh.ECHO:          0,     // disable echoing
		ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
		ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
	}

	if err := session.RequestPty("xterm", 0, 200, modes); err != nil {
		session.Close()
		return nil, fmt.Errorf("request for pseudo terminal failed: %s", err)
	}

	return session, nil
}

func SSHAgent() ssh.AuthMethod {
	if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
		return ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers)
	}
	return nil
}

func main() {

	sshConfig := &ssh.ClientConfig{
		User: "admin",
		Auth: []ssh.AuthMethod{
			ssh.Password("password"),
		},
	}
	sshConfig.Ciphers = []string{"aes128-cbc"}

	client := &SSHClient{
		Config: sshConfig,
		Host:   "10.10.10.10",
		Port:   22,
	}

	cmd := &SSHCommand{
		Stdin:  os.Stdin,
		Stdout: os.Stdout,
		Stderr: os.Stderr,
	}

	fmt.Printf("Running commands\n")
	if err := client.RunCommand(cmd); err != nil {
		fmt.Fprintf(os.Stderr, "command run error: %s\n", err)
		os.Exit(1)
	}

}

如你所见,我在session.Run()中硬编码了"en"命令。如果我简单地在此之后添加一个命令:

err = session.Run("en\r")
err = session.Run("password\r")

它会被忽略。阅读https://godoc.org/golang.org/x/crypto/ssh#Session.Shell 告诉我这是设计如此。所以我的问题是:如何设置一个让我输入命令、读取输出、解析输出并根据输出输入更多命令的东西?

一旦我理解了这个问题,我计划解析路由表并根据需要发出命令。例如,如果路由X丢失,则添加它。

英文:

I have the following code pieced together that will let me send read and write to the ssh console of a Cisco box but I am currently not sure how to send multiple commands in the same session.

I am including the entire program in order to server as a starting point for others attempting the same thing in the future, I could not find a working example specifically for Golang<->Cisco.

package main
import (
&quot;fmt&quot;
&quot;io&quot;
&quot;io/ioutil&quot;
&quot;net&quot;
&quot;os&quot;
//&quot;strings&quot;
&quot;golang.org/x/crypto/ssh&quot;
&quot;golang.org/x/crypto/ssh/agent&quot;
)
type SSHCommand struct {
Stdin  io.Reader
Stdout io.Writer
Stderr io.Writer
}
type SSHClient struct {
Config *ssh.ClientConfig
Host   string
Port   int
}
func (client *SSHClient) RunCommand(cmd *SSHCommand) error {
var (
session *ssh.Session
err     error
)
if session, err = client.newSession(); err != nil {
return err
}
defer session.Close()
if err = client.prepareCommand(session, cmd); err != nil {
return err
}
err = session.Run(&quot;en\r&quot;)
return err
}
func (client *SSHClient) prepareCommand(session *ssh.Session, cmd *SSHCommand) error {
if cmd.Stdin != nil {
stdin, err := session.StdinPipe()
if err != nil {
return fmt.Errorf(&quot;Unable to setup stdin for session: %v&quot;, err)
}
go io.Copy(stdin, cmd.Stdin)
}
if cmd.Stdout != nil {
stdout, err := session.StdoutPipe()
if err != nil {
return fmt.Errorf(&quot;Unable to setup stdout for session: %v&quot;, err)
}
go io.Copy(cmd.Stdout, stdout)
}
if cmd.Stderr != nil {
stderr, err := session.StderrPipe()
if err != nil {
return fmt.Errorf(&quot;Unable to setup stderr for session: %v&quot;, err)
}
go io.Copy(cmd.Stderr, stderr)
}
return nil
}
func (client *SSHClient) newSession() (*ssh.Session, error) {
connection, err := ssh.Dial(&quot;tcp&quot;, fmt.Sprintf(&quot;%s:%d&quot;, client.Host, client.Port), client.Config)
if err != nil {
return nil, fmt.Errorf(&quot;Failed to dial: %s&quot;, err)
}
session, err := connection.NewSession()
if err != nil {
return nil, fmt.Errorf(&quot;Failed to create session: %s&quot;, err)
}
modes := ssh.TerminalModes{
// ssh.ECHO:          0,     // disable echoing
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
if err := session.RequestPty(&quot;xterm&quot;, 0, 200, modes); err != nil {
session.Close()
return nil, fmt.Errorf(&quot;request for pseudo terminal failed: %s&quot;, err)
}
return session, nil
}
func SSHAgent() ssh.AuthMethod {
if sshAgent, err := net.Dial(&quot;unix&quot;, os.Getenv(&quot;SSH_AUTH_SOCK&quot;)); err == nil {
return ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers)
}
return nil
}
func main() {
sshConfig := &amp;ssh.ClientConfig{
User: &quot;admin&quot;,
Auth: []ssh.AuthMethod{
ssh.Password(&quot;password&quot;),
},
}
sshConfig.Ciphers = []string{&quot;aes128-cbc&quot;}
client := &amp;SSHClient{
Config: sshConfig,
Host:   &quot;10.10.10.10&quot;,
Port:   22,
}
cmd := &amp;SSHCommand{
Stdin:  os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
fmt.Printf(&quot;Running commands\n&quot;)
if err := client.RunCommand(cmd); err != nil {
fmt.Fprintf(os.Stderr, &quot;command run error: %s\n&quot;, err)
os.Exit(1)
}
}

As you can see I have hard coded the "en" command in session.Run().
If I add a command after that by simply doing:

err = session.Run(&quot;en\r&quot;)
err = session.Run(&quot;password\r&quot;)

It gets ignored. Reading https://godoc.org/golang.org/x/crypto/ssh#Session.Shell tells me that this is by design. So my question is : How to set up something that lets me enter commands, read output, parse it, and enter more commands based on that output?

Once I wrap my head around this, I plan on parsing routing tables and issuing commands in response. IE, if route X is missing, add it.

答案1

得分: 1

这可能不是典型的Go语言写法,我还在学习中。我一段时间前写了这个代码来从交换机中获取备份,但我认为它可以实现你所要求的功能。以下是代码的链接:

https://github.com/plod/goSwitchBackup/blob/master/main.go

func writeBuff(command string, sshIn io.WriteCloser) (int, error) {
    returnCode, err := sshIn.Write([]byte(command + "\r"))
    return returnCode, err
}

这段代码的作用是向sshIn写入命令字符串,并返回写入的字节数和可能出现的错误。

英文:

This might not be idiomatic go, I am still learning. I wrote this a while ago to get backups off switches but I think it does what you are asking how to do:

https://github.com/plod/goSwitchBackup/blob/master/main.go

func writeBuff(command string, sshIn io.WriteCloser) (int, error) {
returnCode, err := sshIn.Write([]byte(command + &quot;\r&quot;))
return returnCode, err
}

huangapple
  • 本文由 发表于 2016年2月20日 03:11:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/35513125.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定