4

I've been able to forward telnet over a websocket using golang, using something like

func forwardtcp(wsconn *websocket.Conn, conn *telnet.Conn) {
    connbuf := bufio.NewReader(conn)
    tcpbuffer := make([]byte, 128)

    for {
        n, err := connbuf.Read(tcpbuffer)

        if err != nil {
            log.Println("TCP Read failed")
            break
        }
        if err == nil {
            wsconn.WriteMessage(websocket.BinaryMessage, tcpbuffer[:n])
        }
    }
}

However I'm unable to do similar with an SSH or shell session. I'm not understanding a fundamental concept with the using the

targetStdout, _ := session.StdoutPipe()
targetStdin, _ := session.StdinPipe()

pieces.

I am able to use io.Copy, but not sure how to format these into a datagram that can be sent with the websocket connection.

Is it possible to treat the targetStdin and targetStdout pipes in a manner that they can be read and written to with bytes, such as those received from the websocket connection? Or is there a better approach to get io from the SSH connection?

1
  • Yes - for a browser-based client. Commented May 27, 2015 at 11:35

2 Answers 2

1

If you want to use SSH for a remote shell session you should not use the websocket package but the golang.org/x/crypto/ssh package. There is an excellent example at godoc.org which I repeat here:

// An SSH client is represented with a ClientConn. Currently only
// the "password" authentication method is supported.
//
// To authenticate with the remote server you must pass at least one
// implementation of AuthMethod via the Auth field in ClientConfig.
config := &ssh.ClientConfig{
    User: "username",
    Auth: []ssh.AuthMethod{
        ssh.Password("yourpassword"),
    },
}
client, err := ssh.Dial("tcp", "yourserver.com:22", config)
if err != nil {
    panic("Failed to dial: " + err.Error())
}

// Each ClientConn can support multiple interactive sessions,
// represented by a Session.
session, err := client.NewSession()
if err != nil {
    panic("Failed to create session: " + err.Error())
}
defer session.Close()

// Once a Session is created, you can execute a single command on
// the remote side using the Run method.
var b bytes.Buffer
session.Stdout = &b
if err := session.Run("/usr/bin/whoami"); err != nil {
    panic("Failed to run: " + err.Error())
}
fmt.Println(b.String())

You probably want to use the fields of the ssh.Session struct instead of using StdoutPipe().

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

The line in the example session.Stdout = &b means that the stdout from the remote process will be written to b. Likewise, you can assign any io.Reader to session.Stdin which will be read as stdin by the remote process. The detailed behavior of ssh.Session can be found at godoc.org

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

2 Comments

Thanks - I do need to forward it to a remote browser-based client though - which is where I'm getting stuck. Executing a single command is a good start, but ideally I'd like to forward for an interactive session.
For posterity: GoTTY project allows you to run a command on remote server and allows you to see the terminal session from a web browser. You may use command-line utility gotty-client instead of a browser, since that uses websockets to talk to GoTTY server.
1

A websocket.Conn is a ReadWriter, so it can be both the source and destination for io.Copy. Both Cmd.StdoutPipe and Session.StdoutPipe are io.Reader and the Stdin versions are io.Writer. So everything should glue together just fine. You just have to copy in both directions.

go io.Copy(targetStdin, wsconn)
io.Copy(wsconn, targetStdout)

3 Comments

I tried using the Gorilla websocket implementation and get an error: cannot use wsConn (type *websocket.Conn) as type io.Reader in argument to io.Copy: *websocket.Conn does not implement io.Reader (missing Read method) and cannot use wsConn (type *websocket.Conn) as type io.Writer in argument to io.Copy: *websocket.Conn does not implement io.Writer (missing Write method) have websocket.write(int, time.Time, ...[]byte) error want Write([]byte) (int, error) I'm trying using NextReader() and NextWriter() but no luck so far.
You should look at golang.org/x/net/websocket instead. But you can also wrap the gorilla version in your own type and then add Read and Write methods to it.
@SimonKnight Any luck using the Gorilla websocket library?

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.