From 0784b58dba5578e6fc3e754115c14cd111f56ebe Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 29 Nov 2025 22:22:29 -0700 Subject: [PATCH] feat: add tcpfwd to pipe connections --- cmd/tcpfwd/main.go | 86 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 cmd/tcpfwd/main.go diff --git a/cmd/tcpfwd/main.go b/cmd/tcpfwd/main.go new file mode 100644 index 0000000..a8900f2 --- /dev/null +++ b/cmd/tcpfwd/main.go @@ -0,0 +1,86 @@ +package main + +import ( + "flag" + "io" + "log" + "net" + "os" + "strings" +) + +func main() { + var listenPort string + var target string + flag.StringVar(&listenPort, "port", "", "Local port to listen on (same as target by default)") + flag.StringVar(&target, "target", "", "Target host:port (required)") + flag.Parse() + + if target == "" { + flag.Usage() + os.Exit(1) + } + + if len(listenPort) == 0 { + i := strings.LastIndex(target, ":") + listenPort = target[i+1:] + } + listenAddr := ":" + listenPort + log.Printf("TCP bridge %s → %s", listenAddr, target) + + // Note: allow unprivileged users to use this like so: + // echo 'net.ipv4.ip_unprivileged_port_start=1' | sudo tee /etc/sysctl.d/01-deprivilege-ports.conf + // sudo sysctl -p /etc/sysctl.d/01-deprivilege-ports.conf + listener, err := net.Listen("tcp", listenAddr) + + if err != nil { + log.Fatalf("Failed to bind %s: %v", listenAddr, err) + } + log.Printf("TCP bridge listening on %s → %s", listenAddr, target) + + for { + client, err := listener.Accept() + if err != nil { + log.Printf("Accept error: %v", err) + continue + } + go handleConn(client, target) + } +} + +func handleConn(client net.Conn, target string) { + defer client.Close() + + remote, err := net.Dial("tcp", target) + if err != nil { + log.Printf("Failed to connect to %s: %v", target, err) + return + } + defer remote.Close() + + log.Printf("New connection %s ↔ %s", client.RemoteAddr(), remote.RemoteAddr()) + + // Bidirectional copy with error handling + go func() { _ = copyAndClose(remote, client) }() + func() { _ = copyAndClose(client, remote) }() +} + +// copyAndClose copies until EOF or error, then closes dst +func copyAndClose(dst, src net.Conn) error { + _, err := io.Copy(dst, src) + dst.Close() + if err != nil && !isClosedConn(err) { + log.Printf("Copy error (%s → %s): %v", + src.RemoteAddr(), dst.RemoteAddr(), err) + } + return err +} + +// isClosedConn detects common closed-connection errors +func isClosedConn(err error) bool { + if err == nil { + return false + } + opErr, ok := err.(*net.OpError) + return ok && opErr.Err.Error() == "use of closed network connection" +}