3

Thanks in advance for everyone's help. I've marked lines 41, 42, and 58 where the problem occurs. I have not been able to track what is throwing the error. It appears all the variables are properly assigned. I use the command:

go run file.go 'command' 'ip address'
ex. - go run file.go uptime 8.8.8.8

The error is:

panic: runtime error: invalid memory address or nil pointer dereference  
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x54fb36]

goroutine 20 [running]:
golang.org/x/crypto/ssh.(*Client).NewSession(0x0, 0x3, 0xc42009e310, 0x10)
    /home/user/go/src/golang.org/x/crypto/ssh/client.go:130 +0x26  
main.executeCmd(0x7ffce5f83a51, 0x6, 0x7ffce5f83a58, 0xd, 0x5d3077, 0x2, 0xc42009a820, 0x0, 0x0)  
    /home/user/ssh.go:58 +0x16f  
main.main.func1(0xc4200d2000, 0x7ffce5f83a51, 0x6, 0xc42009a820,   0x7ffce5f83a58, 0xd, 0x5d3077, 0x2)  
    /home/user/ssh.go:42 +0x7a  
created by main.main  
    /home/user/ssh.go:41 +0x41b

package main

import (
    "bytes"
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "time"

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

func main() {
    cmd := os.Args[1]
    hosts := os.Args[2:]

    results := make(chan string, 10)
    timeout := time.After(10 * time.Second)

    port := "22"

    var hostKey ssh.PublicKey
    key, err := ioutil.ReadFile("/home/user/file.pem")
    if err != nil {
        log.Fatalf("unable to read private key: %v", err)
    }
    signer, err := ssh.ParsePrivateKey(key)
    if err != nil {
        log.Fatalf("unable to parse private key: %v", err)
    }

    config := &ssh.ClientConfig{
        User: "ec2-user",
        Auth: []ssh.AuthMethod{
            ssh.PublicKeys(signer),
        },
        HostKeyCallback: ssh.FixedHostKey(hostKey),
    }
    for _, hostname := range hosts {
        go func(hostname string, port string) {  // LINE 41
            results <- executeCmd(cmd, hostname, port, config) // LINE 42
        }(hostname, port)
    }

    for i := 0; i < len(hosts); i++ {
        select {
        case res := <-results:
            fmt.Print(res)
        case <-timeout:
            fmt.Println("Timed out!")
            return
        }
    }
}
func executeCmd(command, hostname string, port string, config *ssh.ClientConfig) (string, error) {
conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%s", hostname, port), config)
if err != nil {
    return "", err
}
defer conn.Close()
session, err := conn.NewSession() //LINE 58
if err != nil {
    return "", err
}
defer session.Close()

var stdoutBuf bytes.Buffer
session.Stdout = &stdoutBuf
if err := session.Run(command); err != nil {
    return "", err
}

return fmt.Sprintf("%s -> %s", hostname, stdoutBuf.String()), nil

}

2
  • 1
    Step one is to handle the error you are ignoring on line 57. Most likely conn is nil, and the error returned would tell you why. Commented Aug 30, 2018 at 22:41
  • Also, you never assign a value to the hostKey variable and so that will bite you as well, albeit with an error instead of a panic. If you want to trust all hosts implicitly, use InsecureIgnoreHostKey instead of FixedHostKey. Commented Aug 30, 2018 at 23:55

1 Answer 1

4

As @JoshuaKolden pointed out, you need to check the error coming out of ssh.Dial and then not continue if the value is not nil. Here's a version of your executeCmd function with error checking:

func executeCmd(command, hostname string, port string, config *ssh.ClientConfig) (string, error) {
    conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%s", hostname, port), config)
    if err != nil {
        return "", err
    }
    defer conn.Close()
    session, err := conn.NewSession() //LINE 58
    if err != nil {
        return "", err
    }
    defer session.Close()

    var stdoutBuf bytes.Buffer
    session.Stdout = &stdoutBuf
    if err := session.Run(command); err != nil {
        return "", err
    }

    return fmt.Sprintf("%s -> %s", hostname, stdoutBuf.String()), nil
}

You will of course need to adapt the goroutine that calls executeCmd for error handling as well. Maybe print the error message to os.Stderr to find out what the problem is.

The reason why you get a panic is because you have an error coming out of ssh.Dial, leaving the value of the variable conn as nil. If you look at client.go line 130 in the ssh package you can see that it does a method call on the pointer receiver c to OpenChannel. Normally this isn't a problem for struct methods that take a pointer receiver, but in this case OpenChannel is a method of the Conn interface that is embedded in the Client struct. Go panics when an interface method is invoked with a nil receiver.

Edit: A potential way to use this new version of the function in your goroutine:

go func(hostname string, port string) {  // LINE 41
    result, err := executeCmd(cmd, hostname, port, config) // LINE 42
    if err != nil {
        fmt.Fprintf(os.Stderr, "error running cmd on host %s port %s: %s", hostname, port, err)
        return
    }
    results <- result
}(hostname, port)

Unfortunately, you cannot send to a channel and assign to another variable in the same statement. Also, you need to check for the error and do something if it's not nil, in this case I chose printing it to standard error.

Sign up to request clarification or add additional context in comments.

2 Comments

thanks for your help. I modified the goroutine to err, results := <- executeCmd with no luck. am i adding error handling wrong? I'm getting error - multiple-value executeCmd() in single-value context
I've updated the answer with an example of how to use it

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.