-
-
Save codref/473351a24a3ef90162cf10857fac0ff3 to your computer and use it in GitHub Desktop.
/* | |
Go-Language implementation of an SSH Reverse Tunnel, the equivalent of below SSH command: | |
ssh -R 8080:127.0.0.1:8080 [email protected] | |
which opens a tunnel between the two endpoints and permit to exchange information on this direction: | |
server:8080 -----> client:8080 | |
once authenticated a process on the SSH server can interact with the service answering to port 8080 of the client | |
without any NAT rule via firewall | |
Copyright 2017, Davide Dal Farra | |
MIT License, http://www.opensource.org/licenses/mit-license.php | |
*/ | |
package main | |
import ( | |
"fmt" | |
"io" | |
"io/ioutil" | |
"log" | |
"net" | |
"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) | |
} | |
// From https://sosedoff.com/2015/05/25/ssh-port-forwarding-with-go.html | |
// Handle local client connections and tunnel data to the remote server | |
// Will use io.Copy - http://golang.org/pkg/io/#Copy | |
func handleClient(client net.Conn, remote net.Conn) { | |
defer client.Close() | |
chDone := make(chan bool) | |
// Start remote -> local data transfer | |
go func() { | |
_, err := io.Copy(client, remote) | |
if err != nil { | |
log.Println(fmt.Sprintf("error while copy remote->local: %s", err)) | |
} | |
chDone <- true | |
}() | |
// Start local -> remote data transfer | |
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 publicKeyFile(file string) ssh.AuthMethod { | |
buffer, err := ioutil.ReadFile(file) | |
if err != nil { | |
log.Fatalln(fmt.Sprintf("Cannot read SSH public key file %s", file)) | |
return nil | |
} | |
key, err := ssh.ParsePrivateKey(buffer) | |
if err != nil { | |
log.Fatalln(fmt.Sprintf("Cannot parse SSH public key file %s", file)) | |
return nil | |
} | |
return ssh.PublicKeys(key) | |
} | |
// local service to be forwarded | |
var localEndpoint = Endpoint{ | |
Host: "localhost", | |
Port: 8080, | |
} | |
// remote SSH server | |
var serverEndpoint = Endpoint{ | |
Host: "146.148.22.123", | |
Port: 22, | |
} | |
// remote forwarding port (on remote SSH server network) | |
var remoteEndpoint = Endpoint{ | |
Host: "localhost", | |
Port: 8080, | |
} | |
func main() { | |
// refer to https://godoc.org/golang.org/x/crypto/ssh for other authentication types | |
sshConfig := &ssh.ClientConfig{ | |
// SSH connection username | |
User: "operatore", | |
Auth: []ssh.AuthMethod{ | |
// put here your private key path | |
publicKeyFile("/home/operatore/.ssh/id_rsa"), | |
}, | |
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)) | |
} | |
// 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() | |
// handle incoming connections on reverse forwarded tunnel | |
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) | |
} | |
handleClient(client, local) | |
} | |
} |
Hi,
any ideas on how to implement a reverse tunnel that will enable a direct connection to the remote host? In other words an equivalent of this SSH command:
ssh -R 146.148.22.123:8080:127.0.0.1:8080 [email protected]
upd: Found a solution:
var remoteEndpoint = Endpoint{
Host: "146.148.22.123",
Port: 8080,
}
Thanks for the awesome gist! I improved it quite a bit (parallel connections, logging, retries on errors, start on system startup via systemd, configuration from JSON file, prebuilt binaries): https://github.com/function61/holepunch-client
It seems that your code is great for a standalone reverse ssh tool :)
However, when starting/stopping asynchronously, it seems to keep remote listenner up :(
This is an awesome implementation. Works great.
-amit
Thanks for the awesome gist! I improved it quite a bit (parallel connections, logging, retries on errors, start on system startup via systemd, configuration from JSON file, prebuilt binaries): https://github.com/function61/holepunch-client
Hi joonas-fi,
I am new to this and I am trying to build and use your code by following the README. But I am falling short. Could you please update the README on how I can build holepunch client and execute it? Also, if the remote server has the sshd running on a port other than 22, then how can I introduce custom port in the json file?
So far I have done this:
- Edited a file holepunch.json (similar to holepunch.example.json) and kept it in cmd/holepunch directory.
- Invoked the command: go build main.go
But I am unable to make this work. Getting errors.
Thanks,
-amit
Sorry, I didn't get a notification here. But we worked out the issue: function61/holepunch-client#10 (in case anyone else has trouble)
Hi,
I'm trying to use this example but, when I run it I get an error "Cannot parse SSH public key file /Users/rojalval/.ssh/id_rsa. Error: ssh: cannot decode encrypted private keys"
I've checked that the process is reading the correct id_rsa file so, what can be happening? could you help me?