116 lines
2.9 KiB
Go
116 lines
2.9 KiB
Go
package sshtun
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/Neur0toxine/sshpoke/pkg/errtools"
|
|
"github.com/Neur0toxine/sshpoke/pkg/proto/ssh"
|
|
"github.com/function61/gokit/io/bidipipe"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type Forward struct {
|
|
// local service to be forwarded
|
|
Local Endpoint `json:"local"`
|
|
// remote forwarding port (on remote SSH server network)
|
|
Remote Endpoint `json:"remote"`
|
|
}
|
|
|
|
func AddrToEndpoint(address string) Endpoint {
|
|
host, port, err := net.SplitHostPort(address)
|
|
if err != nil && errtools.IsPortMissingErr(err) {
|
|
return Endpoint{Host: address, Port: 22}
|
|
}
|
|
portNum, err := strconv.Atoi(port)
|
|
if err != nil {
|
|
portNum = 22
|
|
}
|
|
return Endpoint{Host: host, Port: portNum}
|
|
}
|
|
|
|
type Endpoint struct {
|
|
Host string `json:"host"`
|
|
Port int `json:"port"`
|
|
}
|
|
|
|
func (endpoint *Endpoint) String() string {
|
|
return fmt.Sprintf("%s:%d", endpoint.Host, endpoint.Port)
|
|
}
|
|
|
|
func (t *Tunnel) ipFromAddr(addr net.Addr) net.IP {
|
|
host, _, _ := net.SplitHostPort(addr.String())
|
|
return net.ParseIP(host)
|
|
}
|
|
|
|
// blocking flow: calls Listen() on the SSH connection, and if succeeds returns non-nil error
|
|
//
|
|
// nonblocking flow: if Accept() call fails, stops goroutine and returns error on ch listenerStopped
|
|
func (t *Tunnel) reverseForwardOnePort(sshClient *ssh.Client, listenerStopped chan<- error) error {
|
|
var (
|
|
listener net.Listener
|
|
err error
|
|
)
|
|
if t.sessConfig.FakeRemoteHost {
|
|
listener, err = sshClient.ListenTCP(&net.TCPAddr{
|
|
IP: t.ipFromAddr(sshClient.Conn.RemoteAddr()),
|
|
Port: t.forward.Remote.Port,
|
|
}, t.forward.Remote.Host)
|
|
} else {
|
|
listener, err = sshClient.Listen("tcp", t.forward.Remote.String())
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
go func() {
|
|
defer listener.Close()
|
|
t.log.Debugf("forwarding %s <- %s", t.forward.Local.String(), t.forward.Remote.String())
|
|
|
|
for {
|
|
client, err := listener.Accept()
|
|
if err != nil {
|
|
listenerStopped <- fmt.Errorf("error on Accept(): %w", err)
|
|
return
|
|
}
|
|
|
|
go handleReverseForwardConn(client, t.forward, t.log)
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
func handleReverseForwardConn(client net.Conn, forward Forward, log *zap.SugaredLogger) {
|
|
defer client.Close()
|
|
|
|
remote, err := net.Dial("tcp", forward.Local.String())
|
|
if err != nil {
|
|
log.Errorf("cannot dial local service: %s", err.Error())
|
|
return
|
|
}
|
|
|
|
log.Debugf("proxying %s <-> %s", forward.Local.String(), client.RemoteAddr())
|
|
|
|
// pipe data in both directions:
|
|
// - client => remote
|
|
// - remote => client
|
|
//
|
|
// - in effect, we act as a proxy between the reverse tunnel's client and locally-dialed
|
|
// remote endpoint.
|
|
// - the "client" and "remote" strings we give Pipe() is just for error&log messages
|
|
// - this blocks until either of the parties' socket closes (or breaks)
|
|
if err := bidipipe.Pipe(
|
|
bidipipe.WithName("client", client),
|
|
bidipipe.WithName("remote", remote),
|
|
); err != nil {
|
|
// we can safely ignore those errors.
|
|
if strings.Contains(err.Error(), "use of closed network connection") {
|
|
return
|
|
}
|
|
log.Error(err)
|
|
}
|
|
}
|