commit cfa445db14ba635292fee87b118a65d21e133d5a Author: Dylan Parker Date: Sun Jan 8 03:26:04 2023 -0600 initial commit diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0d7258d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "http server", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/cmd/http/main.go", + "env": { + "LILPROXY_TARGET": "localhost:9001", + "LILPROXY_PORT":":9000" + } + } + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bf607b2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM golang:alpine + +# Move to /dist directory as the place for resulting binary folder +WORKDIR /dist + +# Copy binary from build to main folder +COPY ./main . + +# Command to run when starting the container +ENTRYPOINT [ "/dist/main" ] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..75b3ced --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +go-build: + go build cmd/http/main.go \ No newline at end of file diff --git a/cmd/http/main.go b/cmd/http/main.go new file mode 100644 index 0000000..8fd135f --- /dev/null +++ b/cmd/http/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "log" + "os" + + "github.com/dgparker/lilproxy/pkg/udpproxy" +) + +func main() { + target := os.Getenv("LILPROXY_TARGET") + if target == "" { + log.Fatal("env LILPROXY_TARGET required") + } + + port := os.Getenv("LILPROXY_PORT") + if port == "" { + log.Fatal("env LILPROXY_PORT required") + } + + c, err := udpproxy.New(port, target) + if err != nil { + log.Fatal(err) + } + + log.Printf("lilproxy initialized") + + log.Fatal(c.ListenAndServe()) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..89c72bd --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/dgparker/lilproxy + +go 1.18 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/main b/main new file mode 100755 index 0000000..d224f4c Binary files /dev/null and b/main differ diff --git a/pkg/udpproxy/session.go b/pkg/udpproxy/session.go new file mode 100644 index 0000000..72f56a1 --- /dev/null +++ b/pkg/udpproxy/session.go @@ -0,0 +1,66 @@ +package udpproxy + +import ( + "log" + "net" + "time" +) + +type Session struct { + serverConn *net.UDPConn + proxyConn *net.UDPConn + caddr *net.UDPAddr + updateTime time.Time +} + +func createSession(caddr *net.UDPAddr, raddr *net.UDPAddr, proxyConn *net.UDPConn) (*Session, error) { + serverConn, err := net.DialUDP("udp", nil, raddr) + if err != nil { + return nil, err + } + + session := &Session{ + serverConn: serverConn, + proxyConn: proxyConn, + caddr: caddr, + updateTime: time.Now(), + } + + go session.listen() + + return session, nil +} + +func (s *Session) listen() error { + for { + buf := make([]byte, 1024) + _, err := s.serverConn.Read(buf) + if err != nil { + log.Println(err) + continue + } + + go s.proxyFrom(buf) + } +} + +func (s *Session) proxyFrom(buf []byte) error { + s.updateTime = time.Now() + _, err := s.proxyConn.WriteToUDP(buf, s.caddr) + if err != nil { + return err + } + + return nil +} + +func (s *Session) proxyTo(buf []byte) error { + s.updateTime = time.Now() + _, err := s.serverConn.Write(buf) + if err != nil { + log.Println(err) + return err + } + + return nil +} diff --git a/pkg/udpproxy/udpproxy.go b/pkg/udpproxy/udpproxy.go new file mode 100644 index 0000000..60102c8 --- /dev/null +++ b/pkg/udpproxy/udpproxy.go @@ -0,0 +1,84 @@ +package udpproxy + +import ( + "log" + "net" + "sync" + "time" +) + +type Client struct { + laddr *net.UDPAddr + raddr *net.UDPAddr + + proxyConn *net.UDPConn + + mutex sync.RWMutex + sessions map[string]*Session +} + +func New(port, target string) (*Client, error) { + laddr, err := net.ResolveUDPAddr("udp", port) + if err != nil { + return nil, err + } + + raddr, err := net.ResolveUDPAddr("udp", target) + if err != nil { + return nil, err + } + + return &Client{ + laddr: laddr, + raddr: raddr, + mutex: sync.RWMutex{}, + sessions: map[string]*Session{}, + }, nil +} + +func (c *Client) ListenAndServe() error { + var err error + c.proxyConn, err = net.ListenUDP("udp", c.laddr) + if err != nil { + return err + } + + go c.pruneSessions() + + for { + buf := make([]byte, 2048) + _, caddr, err := c.proxyConn.ReadFromUDP(buf) + if err != nil { + log.Println(err) + } + + session, found := c.sessions[caddr.String()] + if !found { + session, err = createSession(caddr, c.raddr, c.proxyConn) + if err != nil { + log.Println(err) + continue + } + + c.sessions[caddr.String()] = session + } + + go session.proxyTo(buf) + } +} + +func (c *Client) pruneSessions() { + ticker := time.NewTicker(1 * time.Minute) + + // the locks here could be abusive and i dont even know if this is a real + // problem but we definitely need to clean up stale sessions + for range ticker.C { + for _, session := range c.sessions { + c.mutex.RLock() + if time.Since(session.updateTime) > time.Minute*5 { + delete(c.sessions, session.caddr.String()) + } + c.mutex.RUnlock() + } + } +} diff --git a/pkg/udpproxy/udpproxy_test.go b/pkg/udpproxy/udpproxy_test.go new file mode 100644 index 0000000..b2a78b9 --- /dev/null +++ b/pkg/udpproxy/udpproxy_test.go @@ -0,0 +1,83 @@ +package udpproxy + +import ( + "log" + "net" + "testing" + "time" +) + +func TestSendAndReceive(t *testing.T) { + go runLilProxy() + go runUDPServer() + + paddr, err := net.ResolveUDPAddr("udp", "localhost:9000") + if err != nil { + t.Fatal(err) + } + + conn, err := net.DialUDP("udp", nil, paddr) + if err != nil { + t.Fatal(err) + } + + go func() { + for { + buf := make([]byte, 2048) + _, _, err = conn.ReadFromUDP(buf) + if err != nil { + log.Fatal(err) + } + + log.Printf("response received: %s", string(buf)) + } + }() + + for { + time.Sleep(1 * time.Second) + _, err = conn.Write([]byte("hi\n")) + if err != nil { + log.Fatal(err) + } + } +} + +func runLilProxy() { + port := ":9000" + target := "localhost:9001" + + c, err := New(port, target) + if err != nil { + log.Fatal(err) + } + + log.Fatal(c.ListenAndServe()) +} + +func runUDPServer() { + taddr, err := net.ResolveUDPAddr("udp", ":9001") + if err != nil { + log.Fatal(err) + } + + conn, err := net.ListenUDP("udp", taddr) + if err != nil { + log.Fatal(err) + } + + for { + buf := make([]byte, 2048) + _, caddr, err := conn.ReadFromUDP(buf) + if err != nil { + log.Fatal(err) + } + + log.Printf("request received: %s", string(buf)) + + _, err = conn.WriteToUDP([]byte("bye\n"), caddr) + if err != nil { + log.Fatal(err) + } + + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt new file mode 100644 index 0000000..8e6453e --- /dev/null +++ b/vendor/modules.txt @@ -0,0 +1,4 @@ +# github.com/google/martian/v3 v3.3.2 +## explicit; go 1.11 +# golang.org/x/sys v0.3.0 +## explicit; go 1.17