From 91a63c8cfad3303c3a6f971b2d3197a45de544c0 Mon Sep 17 00:00:00 2001 From: ChillerDragon Date: Fri, 21 Jun 2024 10:25:06 +0800 Subject: [PATCH] Basic chat intercepting proxy --- .github/workflows/main.yml | 47 ++++++++ .gitignore | 4 + README.md | 14 +++ go.mod | 7 ++ go.sum | 4 + proxy.go | 219 +++++++++++++++++++++++++++++++++++++ 6 files changed, 295 insertions(+) create mode 100644 .github/workflows/main.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 proxy.go diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..912ca00 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,47 @@ +name: Go + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: Build + run: go build -v ./... + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: Test + run: go test -v ./... + + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: Format + run: diff -u <(echo -n) <(gofmt -d ./) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de50c27 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +teeworlds_client +teeworlds +proxy + diff --git a/README.md b/README.md new file mode 100644 index 0000000..8087384 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# teeworlds proxy + +WORK IN PROGRESS + +## run the proxy + +``` +go build +./proxy -H 127.0.0.1 -P 8303 -p 8333 +``` + +Start a teeworlds 0.7 server on port 8303 on your machine. +Connect to 8333 with your 0.7 client and you will be proxied to the 8303 server. + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..38cc4eb --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module github.com/teeworlds-go/proxy + +go 1.22.3 + +require github.com/teeworlds-go/teeworlds v0.0.0-20240621015046-db550ce86fda + +require github.com/teeworlds-go/huffman v1.0.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a5463e1 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/teeworlds-go/huffman v1.0.0 h1:XSNMNAJZb+njNrPACsxcDrrLDXTGjZZt35FqLAuHi4I= +github.com/teeworlds-go/huffman v1.0.0/go.mod h1:kjaXpL6C6xL7CM+tWPNYjdEgVZB2GumKhx7rCDdXArU= +github.com/teeworlds-go/teeworlds v0.0.0-20240621015046-db550ce86fda h1:Ho08Zo5Co+t5VfX4wVqiEDHxxDD3F+52AYne/Gs7oj0= +github.com/teeworlds-go/teeworlds v0.0.0-20240621015046-db550ce86fda/go.mod h1:aKhyLQAZ0cXv3e6ivNMiolKLbycQlDTIq9Yldn7klWM= diff --git a/proxy.go b/proxy.go new file mode 100644 index 0000000..df307bb --- /dev/null +++ b/proxy.go @@ -0,0 +1,219 @@ +// Implementation of a UDP proxy + +package main + +import ( + "flag" + "fmt" + "log" + "net" + "os" + "strings" + "sync" + + "github.com/teeworlds-go/teeworlds/messages7" + "github.com/teeworlds-go/teeworlds/network7" + "github.com/teeworlds-go/teeworlds/protocol7" +) + +// Information maintained for each client/server connection +type Connection struct { + ClientAddr *net.UDPAddr // Address of the client + ServerConn *net.UDPConn // UDP connection to server +} + +// Generate a new connection by opening a UDP connection to the server +func NewConnection(srvAddr, cliAddr *net.UDPAddr) *Connection { + conn := new(Connection) + conn.ClientAddr = cliAddr + srvudp, err := net.DialUDP("udp", nil, srvAddr) + if checkreport(1, err) { + return nil + } + conn.ServerConn = srvudp + return conn +} + +// Global state +// Connection used by clients as the proxy server +var ProxyConn *net.UDPConn + +// Address of server +var ServerAddr *net.UDPAddr + +// Mapping from client addresses (as host:port) to connection +var ClientDict map[string]*Connection = make(map[string]*Connection) + +// Mutex used to serialize access to the dictionary +var dmutex *sync.Mutex = new(sync.Mutex) + +func setup(hostport string, port int) bool { + // Set up Proxy + saddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", port)) + if checkreport(1, err) { + return false + } + pudp, err := net.ListenUDP("udp", saddr) + if checkreport(1, err) { + return false + } + ProxyConn = pudp + Vlogf(2, "Proxy serving on port %d\n", port) + + // Get server address + srvaddr, err := net.ResolveUDPAddr("udp", hostport) + if checkreport(1, err) { + return false + } + ServerAddr = srvaddr + Vlogf(2, "Connected to server at %s\n", hostport) + return true +} + +func dlock() { + dmutex.Lock() +} + +func dunlock() { + dmutex.Unlock() +} + +// Go routine which manages connection from server to single client +func RunConnection(conn *Connection, twconn *protocol7.Connection) { + var buffer [1500]byte + for { + // Read from server + n, err := conn.ServerConn.Read(buffer[0:]) + if checkreport(1, err) { + continue + } + + srvMsg := buffer[0:n] + result, err := twconn.OnPacket(srvMsg) + if err != nil { + panic(err) + } + // example of inspecting incoming trafic + for i, msg := range result.Packet.Messages { + if msg.MsgId() == network7.MsgGameSvChat { + var chat *messages7.SvChat + var ok bool + if chat, ok = result.Packet.Messages[i].(*messages7.SvChat); ok { + fmt.Printf("got chat msg: %s\n", chat.Message) + chat.Message = "capitalism." + + // modify chat if this was a proxy + result.Packet.Messages[i] = chat + + srvMsg = result.Packet.Pack(twconn) + } + } + } + + // Relay it to client + _, err = ProxyConn.WriteToUDP(srvMsg, conn.ClientAddr) + if checkreport(1, err) { + continue + } + Vlogf(3, "Relayed '%x' from server to %s.\n", + buffer[0:n], conn.ClientAddr.String()) + } +} + +// Routine to handle inputs to Proxy port +func RunProxy() { + var buffer [1500]byte + for { + n, cliaddr, err := ProxyConn.ReadFromUDP(buffer[0:]) + if checkreport(1, err) { + continue + } + Vlogf(3, "Read '%x' from client %s\n", + buffer[0:n], cliaddr.String()) + saddr := cliaddr.String() + dlock() + conn, found := ClientDict[saddr] + if !found { + conn = NewConnection(ServerAddr, cliaddr) + if conn == nil { + dunlock() + continue + } + ClientDict[saddr] = conn + dunlock() + Vlogf(2, "Created new connection for client %s\n", saddr) + + twconn := &protocol7.Connection{ + ClientToken: [4]byte{0x01, 0x02, 0x03, 0x04}, + ServerToken: [4]byte{0xff, 0xff, 0xff, 0xff}, + Ack: 0, + Players: make([]protocol7.Player, network7.MaxClients), + } + + // Fire up routine to manage new connection + go RunConnection(conn, twconn) + } else { + Vlogf(5, "Found connection for client %s\n", saddr) + dunlock() + } + // Relay to server + _, err = conn.ServerConn.Write(buffer[0:n]) + if checkreport(1, err) { + continue + } + } +} + +var verbosity int = 6 + +// Log result if verbosity level high enough +func Vlogf(level int, format string, v ...interface{}) { + if level <= verbosity { + log.Printf(format, v...) + } +} + +// Handle errors +func checkreport(level int, err error) bool { + if err == nil { + return false + } + Vlogf(level, "Error: %s", err.Error()) + return true +} + +func main() { + var ihelp *bool = flag.Bool("h", false, "Show help information") + var ipport *int = flag.Int("p", 8333, "Proxy port") + var isport *int = flag.Int("P", 8303, "Server port") + var ishost *string = flag.String("H", "localhost", "Server address") + var iverb *int = flag.Int("v", 1, "Verbosity (0-6)") + // var idrop *float64 = flag.Float64("d", 0.0, "Packet drop rate") + flag.Parse() + verbosity = *iverb + if *ihelp { + flag.Usage() + os.Exit(0) + } + if flag.NArg() > 0 { + ok := true + fields := strings.Split(flag.Arg(0), ":") + ok = ok && len(fields) == 2 + if ok { + *ishost = fields[0] + n, err := fmt.Sscanf(fields[1], "%d", isport) + ok = ok && n == 1 && err == nil + } + if !ok { + flag.Usage() + os.Exit(0) + } + } + hostport := fmt.Sprintf("%s:%d", *ishost, *isport) + Vlogf(3, "Proxy port = %d, Server address = %s\n", + *ipport, hostport) + if setup(hostport, *ipport) { + RunProxy() + } + os.Exit(0) +}