golang websocket to tcp forwarder
I’ve been writing a simple websocket to tcp forwarder in golang for a project I’m working on.
I previously showed a simple echo example using Gorilla websockets. The echoing example blocks on the sockets until data is available, however that isn’t really going to work here. At first I tried to use the SetReadDeadLine, mirroring non-blocking IO in C. While this worked, it wasn’t really an ideal solution. What I ended up with was my first use of go routines! Basically breaking the code into two threads each of which blocks on reads from the 2 sockets. The code is below for reference (it currently forwards traffic to/from localhost, but the first 12bytes sent from the client should be the destination host port, I’ll update this post then that codes in place):
package main
import (
"github.com/gorilla/websocket"
"net/http"
"net"
"fmt"
"io"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
func print_binary(s []byte) {
fmt.Printf("print b:");
for n := 0;n < len(s);n++ {
fmt.Printf("%d,",s[n]);
}
fmt.Printf("\n");
}
func address_decode(address_bin []byte) (string,string) {
var host string = "127.0.0.1"
var port string = "22";
return host,port
}
func forwardtcp(wsconn *websocket.Conn,conn net.Conn) {
for {
// Receive and forward pending data from tcp socket to web socket
tcpbuffer := make([]byte, 1024)
n,err := conn.Read(tcpbuffer)
if err == io.EOF { fmt.Printf("TCP Read failed"); break; }
if err == nil {
fmt.Printf("Forwarding from tcp to ws: %d bytes: %s\n",n,tcpbuffer)
print_binary(tcpbuffer)
wsconn.WriteMessage(websocket.BinaryMessage,tcpbuffer[:n])
}
}
}
func forwardws (wsconn *websocket.Conn,conn net.Conn) {
for {
// Send pending data to tcp socket
n,buffer,err := wsconn.ReadMessage()
if err == io.EOF { fmt.Printf("WS Read Failed"); break; }
if err == nil {
s := string(buffer[:len(buffer)])
fmt.Printf("Received (from ws) forwarding to tcp: %d bytes: %s %d\n",len(buffer),s,n)
print_binary(buffer)
conn.Write(buffer)
}
}
}
func wsProxyHandler(w http.ResponseWriter, r *http.Request) {
wsconn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
//log.Println(err)
return
}
// get connection address and port
address := make([]byte, 16)
n,address,err := wsconn.ReadMessage()
if err != nil {
fmt.Printf("address read error");
fmt.Printf("read %d bytes",n);
}
print_binary(address)
host, port := address_decode(address)
conn, err := net.Dial("tcp", host + ":" + port)
if err != nil {
// handle error
}
go forwardtcp(wsconn,conn)
go forwardws(wsconn,conn)
fmt.Printf("websocket closed");
}
func main() {
http.HandleFunc("/echo", wsProxyHandler)
http.Handle("/", http.FileServer(http.Dir(".")))
err := http.ListenAndServe(":8080", nil)
if err != nil {
panic("Error: " + err.Error())
}
}