Dynamic port forward TCP traffic over reverse SSH tunnel - go

I have an SSH tunnel which dial into the server endpoint and starts up a remote listener on a port 1080. My goal is to dynamically forward any TCP traffic from that port to the client side of the tunnel. While I am able to accept TCP stream I am not able to find a way to Dial out on the client side of the SSH tunnel. It looks like the dialer I am passing is the serverConn server SSH end of the tunnel. And when I am attempting to remote, err := dialer.DialTCP("tcp4", local.RemoteAddr().(*net.TCPAddr), addr) I get the traffic dial on the remote end. I want to find a way to dial out at the client side over a reverse tunnel.
I am not looking to forward specific ports to a specific destination on local, but looking to do more dynamic port forwarding similar to ssh -D option. I am managing destination of TCP traffic myself, this is not a concern.
I have tried to create ssh.NewClient from the passed serverConn but it needs to perform an SSH handshake, and Iam only receiving raw TCP on the port 1080 so it's not a valid SSH client. Thanks.
func main(){
type Dialer interface {
DialTCP(net string, laddr, raddr *net.TCPAddr) (net.Conn, error)
}
// SSH server connection
sshConfig := &ssh.ClientConfig{
User: "tester",
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// Connect to SSH remote server using serverEndpoint (port 22)
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 (port 1080)
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()
acceptSLoop(listener, serverConn)
}
func acceptSLoop(listener net.Listener, sshClient *ssh.Client) {
fmt.Printf("Listener: %s\n", listener.Addr().String())
defer listener.Close()
for {
clientConn, err := listener.Accept()
fmt.Printf("local addr %s\n", clientConn.LocalAddr())
if err != nil {
log.Fatal(err)
}
fmt.Printf("New connection found on %s\n", listener.Addr().String())
listenSConnection(clientConn, sshClient, config)
}
}
func listenSConnection(SClientConn net.Conn, sshClient
*ssh.Client) {
// In a regular SSH I need to do
// sshConn, chans, reqs, err :=
ssh.NewServerConn(SClientConn, // config will be
passed in ..)
//
// But since it's TCP handoff I am passing socket data
directly
handleSConn(SClientConn, sshClient)
}
func handleSConn(local net.Conn, dialer Dialer) {
defer local.Close()
// Both of those establish socket from "remote" ssh side (Server) not "local" ssh side (client)
remote, err := dialer.DialTCP("tcp4", local.RemoteAddr().(*net.TCPAddr), addr)
//remote, err := net.Dial("tcp4", addr.String())
// transfer bytes from local SOCKS to remote desired endpoint over SSH
transfer(local, remote)
}
// in - local SOCKS conn, out - remote desired endpoint
func transfer(in, out net.Conn) {
//.... working
}

Related

Go TCP Listener ACL

I realise I have a lot of invalid requests hitting my HTTP server but these are at TCP session and has not gone pass TLS handshake, hence no HTTP request yet. (I can't block it at HTTP layer).
I've written the below to filter malicious traffic at TCP Listener but I think its not optimum, the connection is first accepted and then closed, I like to completely not accept the connection if it comes from a known IP address. The next best is to do it at IPtables/NFtables, but I like to explore if I can filter remote addr prior to conn established at TCP Listener. I try looking at the NET package, and looks like its in the File Descriptor which isn't something easy to do.
func main() {
// listen for incoming connections.
l, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
if err != nil {
fmt.Println("Error listening:", err.Error())
os.Exit(1)
}
// Close the listener when application closes.
defer l.Close()
fmt.Println("listening on ", CONN_PORT)
for {
//listen for an incoming connection.
conn, err := l.Accept()
if err != nil {
fmt.Println("Error accepting: ", err.Error())
os.Exit(1)
}
fmt.Println(conn.RemoteAddr())
p := fmt.Sprintln(conn.RemoteAddr())
ip := strings.Split(p, ":")[0]
if ip == "127.0.0.1" {
conn.Close()
}
// Handle connections in a new goroutine.
go handleRequest(conn)
}
}

Given a TCP server, how to get the connection domain address

I have a simple TCP server and, when a client connects, I want to get the domain address used to connect:
package main
import (
"fmt"
"net"
"os"
)
const (
CONN_HOST = "localhost"
CONN_PORT = "3333"
CONN_TYPE = "tcp"
)
func main() {
// Listen for incoming connections.
l, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
if err != nil {
fmt.Println("Error listening:", err.Error())
os.Exit(1)
}
// Close the listener when the application closes.
defer l.Close()
fmt.Println("Listening on " + CONN_HOST + ":" + CONN_PORT)
for {
// Listen for an incoming connection.
conn, err := l.Accept()
if err != nil {
fmt.Println("Error accepting: ", err.Error())
os.Exit(1)
}
// Handle connections in a new goroutine.
go handleRequest(conn)
}
}
// Handles incoming requests.
func handleRequest(conn net.Conn) {
// Make a buffer to hold incoming data.
buf := make([]byte, 1024)
// Read the incoming connection into the buffer.
_, err := conn.Read(buf)
if err != nil {
fmt.Println("Error reading:", err.Error())
}
// Send a response back to person contacting us.
conn.Write([]byte("Message received."))
// Close the connection when you're done with it.
conn.Close()
}
I tried debugging the conn net.Conn param but I can't find any reference to the domain address. Tried with http://test.127.0.0.1.xip.io:3333/ and I'm interested in getting test.127.0.0.1.xip.io somehow. Any ideas?
What you are trying to do is not possible with plain TCP. TCP works on plain IP-Addresses without domains.
To explain what is going on:
When you are establishing a connection to, e.g. example.com, first of all a DNS Lookup for example.com is done. In this case, the DNS Lookup would result in 93.184.216.34. You can read more about DNS here.
A TCP Connection with 93.184.216.34 is established after that, the original domain name is not sent with the request.
Because you sometimes need the original name the user was trying to connect to, some protocols send the domain name after connecting. HTTP for example does this via the Host header.
Maybe you can do something like that and require to send the original host first through your TCP Connection!

Sending GRPC communications over a specific port

I am running a GRPC server (Server A) listening on a specific port. I want to be able to send a communication to another server (Server B), and have Server B record the incoming address of Server A's connection so that it may later contact Server A.
On Server A, I listen on a port and create a context like such:
lis, err := net.Listen("tcp", "0.0.0.0:6000")
ctx, cancel := context.WithTimeout(context.Background(),
10000*time.Millisecond)
Then create a connection like so:
connection, err = grpc.DialContext(ctx, server2Address,
grpc.WithInsecure(), grpc.WithBlock())
Before finally sending a message to an endpoint on Server B, which attempts to read the IP address of Server A's incoming connection
info, _ := peer.FromContext(ctx)
fmt.Printf(info.Addr.String()) // Returns a random port, NOT 6000,
However, the resulting port printed by Server B is random, like 62056 as opposed to 6000 as intended. My assumption is that, on Server A, GRPC dials from a random port - is it possible to force GRPC to dial from port 6000 as opposed to a random port?
You can specify the source port like this:
cc, err := grpc.Dial("127.0.0.1:6001", grpc.WithInsecure(),
grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
dst, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
return nil, err
}
src := &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 6000,
}
return net.DialTCP("tcp", src, dst)
}))
However if your server is listening on the same port this will result in an error:
panic: dial tcp 127.0.0.1:6000->127.0.0.1:6001: bind: address already in use
A different approach would be to pass the address as metadata. On the client you do:
ctx := context.Background()
ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("address", "127.0.0.1:6000"))
res, err := grpc_health_v1.NewHealthClient(cc).Check(ctx, &grpc_health_v1.HealthCheckRequest{
Service: "test",
})
And on the server:
func (s *server) Check(ctx context.Context, req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) {
if md, ok := metadata.FromIncomingContext(ctx); ok {
addr := md.Get("address")
// addr == "127.0.0.1:6000"
}
return &grpc_health_v1.HealthCheckResponse{
Status: grpc_health_v1.HealthCheckResponse_SERVING,
}, nil
}
And a third approach would be to use streaming.

Go net.dial issue

I'm having some issues connecting to a local golang TLS server via tls.dial or net.dial. The server ist started with the address localhost:10001 and I am able to connect to it with netcat (simply netcat localhost 10001 ) but trying it with golangs's dial method doesn't work (connection refused). What could be the reason for this? One important thing to point out is that I'm testing it on a Debian VM where I deinstalled the network manager so I could configure the network interfaces myself (static ip). My guess is that net.dial has some issues finding the right interface but what does netcat do that golang's methods don't? I tried all different kinds of addresses on both sides (server on localhost, ::1, 127.0.0.1; client same). On my windows host system it works (so the issue is probably not with the server).
Thanks in advance.
conn, err := tls.Dial("tcp", h.IP+":"+h.Port, conf)
if err != nil {
log.Println(err)
return empty
}
// do cleanup
defer conn.Close()
d := Craft_PKG( data )
//write package
WriteChunks( conn, d )
this is the client code and this
ln, err := tls.Listen("tcp", "localhost:"+(*port), config)
if err != nil {
log.Println(err)
return err
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
log.Println(err)
continue
}
// start the handler for every incoming connection
go h(conn)
}
the server code. It is all very straightforward and works with netcat and in my Host system as I mentioned.

SSH Reverse Tunnel with GO

I am trying to write a server / client that can help get around Firewalls / Nat Issues.
I noticed SSH has built into support for doing this already.
(http://rustyrazorblade.com/2010/03/ssh-reverse-tunnel-to-access-box-behind-firewall/)
I tried a few different SSH examples and none seem to be working. I found one project that says it implemented the Remote Port Fowarding -> https://godoc.org/dev.justinjudd.org/justin/easyssh
The Server says it is Listening for connections but I am unable to SSH from Server Machine To Client Machine. (ssh localhost 8080 on remote machine should forward to client machine.
Client ->
package main
import (
"log"
"dev.justinjudd.org/justin/easyssh"
"golang.org/x/crypto/ssh"
)
func main() {
config := &ssh.ClientConfig{
User: "test",
Auth: []ssh.AuthMethod{
ssh.Password("test"),
},
}
conn, err := easyssh.Dial("tcp", "*SSH-SERVER*:22", config)
if err != nil {
log.Fatalf("unable to connect: %s", err)
}
defer conn.Close()
err = conn.RemoteForward("0.0.0.0:8080", "127.0.0.1:22")
if err != nil {
log.Fatalf("unable to forward local port: %s", err)
}
}
Server ->
package main
import (
"fmt"
"io/ioutil"
"log"
"dev.justinjudd.org/justin/easyssh"
"golang.org/x/crypto/ssh"
)
func main() {
privateBytes, err := ioutil.ReadFile("id_rsa")
if err != nil {
log.Fatal("Failed to load private key (./id_rsa)")
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
log.Fatal("Failed to parse private key")
}
config := &ssh.ServerConfig{
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
if c.User() == "test" && string(pass) == "test" {
log.Printf("User logged in: %s", c.User())
return nil, nil
}
return nil, fmt.Errorf("password rejected for %s", c.User())
},
}
config.AddHostKey(private)
easyssh.HandleChannel(easyssh.SessionRequest, easyssh.SessionHandler())
easyssh.HandleChannel(easyssh.DirectForwardRequest, easyssh.DirectPortForwardHandler())
easyssh.HandleRequestFunc(easyssh.RemoteForwardRequest, easyssh.TCPIPForwardRequest)
easyssh.ListenAndServe(":22", config, nil)
}
I found a bug related to remote port forwarding in easyssh:
https://dev.justinjudd.org/justin/easyssh/src/master/tcpip.go#L107
the ssh.DiscardRequests(reqs) should be run in a separated goroutine, or else the next data transfer will not be executed.
I'm not sure that you understand how SSH tunneling works looking on your code.
You need to have SSH connectivity to the remote (server).
Then you setup SSH tunnel so local TCP:8080 port will be forwarded to the remote server TCP:8080 port using SSH connection. Actual 8080 port can be closed via firewall.
Can you connect from your client to your server with SSH?
You need to check localhost:8080 port and you need to be sure that your server 8080 port is listened by some application too.
Take a look here for some examples and theory.

Resources