Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple SSH port forward in Golang

Tags:

go

I'm trying to create (and later close) a simple TCP port forward over SSH with Go. I'm new to Golang and statically typed languages. (Coming from Ruby.)

In a terminal I would simply run ssh -L 9000:localhost:9999 [email protected] and this accomplishes what I need. I want to do the same, programmatically with Go.

I have tried using this example as a starting point and this recent test to try to understand what to do, but now I have a pile of confusing jumbled code when it seems like this is actually a very simple thing to do.

Any help would be very much appreciated! :-)

like image 295
damick Avatar asked Jan 28 '14 00:01

damick


3 Answers

I finally figured out how to do this, I got hints from schmichael in an IRC channel. Thanks to all!

EDIT: A little explanation: A big part of the problem I was having was that I did not realize a local net.Listener (not just a local net.Conn) needed setup to receive a local request and create the net.Conn before forwarding the bytes. Also, there exist both port forwards and reverse port forwards and I hadn't previously thought in detail about the fact that a regular port forward also sends bytes back, so copying the remote reader to local writer was not something I had implemented, yet it's very much needed. Here is an attempt to relate the essence of what this code does:

  • Listen on local port 9000.
  • Upon attempted read from local port 9000: (listener.Accept()),
  • Accept connection and return a local io.Reader and io.Writer and,
  • Connect to remote server and,
  • Connect to remote port 9999 returning a io.Reader and io.Writer.
  • Continually copy local io.Reader bytes to remote io.Writer,
  • Continually copy remote io.Reader bytes to local io.Writer.

Here is the code:

package main

// Forward from local port 9000 to remote port 9999

import (
    "io"
    "log"
    "net"
    "golang.org/x/crypto/ssh"
)

var (
    username         = "root"
    password         = "password"
    serverAddrString = "192.168.1.100:22"
    localAddrString  = "localhost:9000"
    remoteAddrString = "localhost:9999"
)

func forward(localConn net.Conn, config *ssh.ClientConfig) {
    // Setup sshClientConn (type *ssh.ClientConn)
    sshClientConn, err := ssh.Dial("tcp", serverAddrString, config)
    if err != nil {
        log.Fatalf("ssh.Dial failed: %s", err)
    }

    // Setup sshConn (type net.Conn)
    sshConn, err := sshClientConn.Dial("tcp", remoteAddrString)

    // Copy localConn.Reader to sshConn.Writer
    go func() {
        _, err = io.Copy(sshConn, localConn)
        if err != nil {
            log.Fatalf("io.Copy failed: %v", err)
        }
    }()

    // Copy sshConn.Reader to localConn.Writer
    go func() {
        _, err = io.Copy(localConn, sshConn)
        if err != nil {
            log.Fatalf("io.Copy failed: %v", err)
        }
    }()
}

func main() {
    // Setup SSH config (type *ssh.ClientConfig)
    config := &ssh.ClientConfig{
        User: username,
        Auth: []ssh.AuthMethod{
            ssh.Password(password),
        },
    }

    // Setup localListener (type net.Listener)
    localListener, err := net.Listen("tcp", localAddrString)
    if err != nil {
        log.Fatalf("net.Listen failed: %v", err)
    }

    for {
        // Setup localConn (type net.Conn)
        localConn, err := localListener.Accept()
        if err != nil {
            log.Fatalf("listen.Accept failed: %v", err)
        }
        go forward(localConn, config)
    }
}
like image 109
damick Avatar answered Nov 07 '22 14:11

damick


I have used your (damick) example code to build a tiny open source tool: SSHTunnel https://github.com/SommerEngineering/SSHTunnel

Therefore, the code is freely available at GitHub for anyone: Please feel free to use it for learning purposes or for anything else :) I have mentioned your nickname and also linked to this question.

Best regards, Thorsten.

like image 5
SommerEngineering Avatar answered Nov 07 '22 14:11

SommerEngineering


I'v finished a simple SSH port forward tool called mallory. It provides HTTP proxy instead of SOCKS proxy, which is really similar to ssh -D.

The core code is similar to damick's answer.

  1. Create ClientConfig
  2. ssh.Dial to remote SSH server with the config and return Client
  3. Now you can use Client.Dial to forward anything you like.

    Dial initiates a connection to the addr from the remote host. The resulting connection has a zero LocalAddr() and RemoteAddr().

  4. If you want to serve a SOCKS proxy server, use Client.Dial to connect to the remote server.
like image 2
justmao945 Avatar answered Nov 07 '22 14:11

justmao945