4

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 (
    "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)
    }

}

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("en\r")
err = session.Run("password\r")

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.

3
  • Why can't you open multiple sessions, one for each command? Commented Feb 19, 2016 at 19:17
  • I was under the impression that a new session would not maintain context. Ie, I need to enter a specific set of commands in a specific order in the same "thread" , ie: command two depends on command one. I equated context to session, is that incorrect? Commented Feb 19, 2016 at 19:37
  • 3
    An ssh session can only run one command. This is how the ssh protocol works. The only way to maintain "state" is to open a shell (in which case you need to parse the cli interactively), or execute a script (which can only maintain state during the execution of the script). Commented Feb 19, 2016 at 19:39

1 Answer 1

1

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 + "\r"))
    return returnCode, err
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.