diff --git a/Makefile b/Makefile index e5c6095..6313d80 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ OSARCH = "!darwin/arm !windows/arm" build: mkdir -p ${OUTPUT_DIR} - GOARM=5 gox -os=${OS} -arch=${ARCH} -osarch=${OSARCH} -output "${OUTPUT_DIR}/{{.Dir}}_{{.OS}}_{{.Arch}}" ./cmd/vexd + GOARM=5 gox -os=${OS} -arch=${ARCH} -osarch=${OSARCH} -output "${OUTPUT_DIR}/{{.Dir}}_{{.OS}}_{{.Arch}}" ./cmd/vexd ./cmd/vex clean: rm -rf ${OUTPUT_DIR} diff --git a/README.md b/README.md index ad8c039..ca4f002 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Vex is a reverse HTTP proxy tunnel via secure SSH connections. It is compatible

-### Establish tunnel via running vex server on bleenco.space +### Establish tunnel with vexd server on bleenco.space (ssh client) Let's say you are running HTTP server locally on port 6500, then command would be: @@ -27,6 +27,28 @@ $ ssh -R 10500:localhost:6500 bleenco.space -p 2200 Then open generated URL in the browser to check if works, then share the URL if needed. +### Establish tunnel with vexd server on bleenco.space (vex client) + +```sh +$ vex -s bleenco.space -p 2200 -ls localhost -lp 7500 +``` + +`vex` client options: + +``` +Usage: vex [options] + +Options: + +-s, SSH server remote host (default: bleenco.space) + +-p, SSH server remote port (default: 2200) + +-ls, Local HTTP server host (default: localhost) + +-lp, Local HTTP server port (default: 7500) +``` + ### Run cross-compilation build ```sh diff --git a/cmd/vex/main.go b/cmd/vex/main.go new file mode 100644 index 0000000..e479fb3 --- /dev/null +++ b/cmd/vex/main.go @@ -0,0 +1,151 @@ +package main + +import ( + "flag" + "fmt" + "io" + "log" + "math/rand" + "net" + "os" + "os/signal" + "time" + + "golang.org/x/crypto/ssh" +) + +type Endpoint struct { + Host string + Port int +} + +func (endpoint *Endpoint) String() string { + return fmt.Sprintf("%s:%d", endpoint.Host, endpoint.Port) +} + +var help = ` + Usage: vex [options] + + Options: + + -s, SSH server remote host (default: bleenco.space) + + -p, SSH server remote port (default: 2200) + + -ls, Local HTTP server host (default: localhost) + + -lp, Local HTTP server port (default: 7500) + + Read more: + https://github.com/bleenco/vex +` + +var ( + remoteServer = flag.String("s", "bleenco.space", "") + remotePort = flag.Int("p", 2200, "") + localServer = flag.String("ls", "localhost", "") + localPort = flag.Int("lp", 7500, "") +) + +func main() { + flag.Usage = func() { + fmt.Print(help) + os.Exit(1) + } + flag.Parse() + + // local service to be forwarded + var localEndpoint = Endpoint{ + Host: *localServer, + Port: *localPort, + } + + // remote SSH server + var serverEndpoint = Endpoint{ + Host: *remoteServer, + Port: *remotePort, + } + + // remote forwarding port (on remote SSH server network) + var remoteEndpoint = Endpoint{ + Host: "localhost", + Port: randomPort(11000, 65000), + } + + sshConfig := &ssh.ClientConfig{ + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + // Connect to SSH remote server using serverEndpoint + serverConn, err := ssh.Dial("tcp", serverEndpoint.String(), sshConfig) + if err != nil { + log.Fatalln(fmt.Printf("Dial INTO remote server error: %s", err)) + } + + go func() { + session, err := serverConn.NewSession() + if err != nil { + log.Fatalln(fmt.Printf("Cannot create session error: %s", err)) + } + + stdout, err := session.StdoutPipe() + if err != nil { + log.Fatalln(fmt.Printf("Unable to setup stdout for session: %s", err)) + } + + go io.Copy(os.Stdout, stdout) + }() + + // Listen on remote server port + listener, err := serverConn.Listen("tcp", remoteEndpoint.String()) + if err != nil { + log.Fatalln(fmt.Printf("Listen open port ON remote server error: %s", err)) + } + defer listener.Close() + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + + for { + // Open a (local) connection to localEndpoint whose content will be forwarded so serverEndpoint + local, err := net.Dial("tcp", localEndpoint.String()) + if err != nil { + log.Fatalln(fmt.Printf("Dial INTO local service error: %s", err)) + } + + client, err := listener.Accept() + if err != nil { + log.Fatalln(err) + } + + go handleClient(client, local) + } +} + +func handleClient(client net.Conn, remote net.Conn) { + defer client.Close() + chDone := make(chan bool) + + go func() { + _, err := io.Copy(client, remote) + if err != nil { + log.Println(fmt.Sprintf("error while copy remote->local: %s", err)) + } + chDone <- true + }() + + go func() { + _, err := io.Copy(remote, client) + if err != nil { + log.Println(fmt.Sprintf("error while copy local->remote: %s", err)) + } + chDone <- true + }() + + <-chDone +} + +func randomPort(min, max int) int { + rand.Seed(time.Now().Unix()) + return rand.Intn(max-min) + min +}