Last active
September 8, 2023 16:05
-
-
Save tsingakbar/325b64e55d5d5fa3f1e6237a27aa195a to your computer and use it in GitHub Desktop.
golang implementation of `ssh -R`, which will be convenient for environments lacking ssh installation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"io" | |
"log" | |
"net" | |
"os" | |
"os/signal" | |
"syscall" | |
"time" | |
"github.com/BurntSushi/toml" | |
"golang.org/x/crypto/ssh" | |
) | |
type Config struct { | |
SshdServer string | |
User string | |
Password string | |
SshdSideListenOn string | |
SshSideForwardTo string | |
} | |
// runTunnel runs a tunnel between two connections; as soon as one connection | |
// reaches EOF or reports an error, both connections are closed and this | |
// function returns. | |
func runTunnel(local, remote net.Conn) { | |
defer local.Close() | |
defer remote.Close() | |
done := make(chan struct{}, 2) | |
go func() { | |
io.Copy(local, remote) | |
done <- struct{}{} | |
}() | |
go func() { | |
io.Copy(remote, local) | |
done <- struct{}{} | |
}() | |
<-done | |
} | |
func main() { | |
if len(os.Args) != 2 { | |
log.Fatalln("Usage: <exe> /path/to/config.toml") | |
} | |
var conf Config | |
if _, err := toml.DecodeFile(os.Args[1], &conf); err != nil { | |
log.Fatalln(err) | |
} | |
sshConf := &ssh.ClientConfig{ | |
User: conf.User, | |
Auth: []ssh.AuthMethod{ | |
ssh.Password(conf.Password), | |
}, | |
HostKeyCallback: ssh.InsecureIgnoreHostKey(), | |
} | |
sshClient, err := ssh.Dial("tcp", conf.SshdServer, sshConf) | |
if err != nil { | |
log.Fatalf("Failed to dial sshd: %s\n", err) | |
} | |
defer sshClient.Close() | |
log.Printf("sshd %s connected, ready for forwarding...\n", conf.SshdServer) | |
sshdSideListener, err := sshClient.Listen("tcp", conf.SshdSideListenOn) | |
if err != nil { | |
log.Fatalf("failed to listen on sshd side endpoint: %s\n", err) | |
} | |
defer sshdSideListener.Close() | |
sshdSideIncomeConnChan := make(chan net.Conn) | |
go func() { | |
for { | |
sshdSideIncomeConn, err := sshdSideListener.Accept() | |
if err != nil { | |
log.Printf("sshdSideListener.Accept() failed: %s\n", err) | |
close(sshdSideIncomeConnChan) | |
return | |
} | |
sshdSideIncomeConnChan <- sshdSideIncomeConn | |
} | |
}() | |
signalChan := make(chan os.Signal, 1) | |
signal.Notify(signalChan, syscall.SIGINT) | |
keepAliveTimer := time.NewTicker(time.Minute) | |
defer keepAliveTimer.Stop() | |
for { | |
select { | |
case sshdSideIncomeConn, ok := <-sshdSideIncomeConnChan: | |
if !ok { | |
return | |
} | |
go func() { | |
sshSideForwardConn, err := net.Dial("tcp", conf.SshSideForwardTo) | |
if err != nil { | |
log.Printf("sshd side connection %s accepted, but failed to forward it to ssh side endpoint %s: %s", | |
sshdSideIncomeConn.RemoteAddr().String(), conf.SshSideForwardTo, err) | |
return | |
} | |
log.Printf("established %s <- %s <- ssh <- %s <- %s\n", | |
sshSideForwardConn.RemoteAddr().String(), | |
sshSideForwardConn.LocalAddr().String(), | |
sshdSideIncomeConn.LocalAddr().String(), | |
sshdSideIncomeConn.RemoteAddr().String(), | |
) | |
runTunnel(sshSideForwardConn, sshdSideIncomeConn) | |
}() | |
case <-keepAliveTimer.C: | |
_, _, err = sshClient.SendRequest("[email protected]", true, nil) | |
log.Println("ssh connection keepalive: sshClient.SendRequest") | |
if err != nil { | |
log.Fatal("Failed to send keep alive to sshd: ", err) | |
} | |
case <-signalChan: | |
log.Println("shutting down...") | |
keepAliveTimer.Stop() | |
sshClient.Close() | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
personally I use this together with a local
ruby -r webrick/httpproxy -e "WEBrick::HTTPProxyServer.new(:Port => 3128).start"
to make sshd side has an http proxy to access internet.Note: webrick's http proxy is so naive that it forwards response to http client only after it's fully downloaded. for large response(like file downloading), a http proxy support real time streaming will be a better choice.