1

I need to create a proxy server in Golang that acts as an intermediary between a client and a remote MySQL server. The client connects to the proxy server and provides a request without server credentials. The proxy server intercepts the request, adds the necessary server credentials, forwards the modified request to the actual MySQL server, and then returns the response from the server back to the client

I'm not able to modify the request before sending it to remote server.

Here is the code I'm using

package main

import (
    "context"
    proxy2 "go-mysql-proxy/proxy"
    "log"
    "os"
    "os/signal"
)

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    proxy := proxy2.NewProxy("127.0.0.1", ":3306", ctx)
    proxy.EnableDecoding()

    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt)
    go func(){
        for sig := range c {
            log.Printf("Signal received %v, stopping and exiting...", sig)
            cancel()
        }
    }()

    err := proxy.Start("3336")
    if err != nil {
        log.Fatal(err)
    }
}
package proxy

import (
    "context"
    "fmt"
    "log"
    "net"
)

func NewProxy(host, port string, ctx context.Context) *Proxy {
    return &Proxy{
        host: host,
        port: port,
        ctx: ctx,
    }
}

type Proxy struct {
    host           string
    port           string
    connectionId   uint64
    enableDecoding bool
    ctx context.Context
    shutDownAsked bool
}

func (r *Proxy) Start(port string) error {
    log.Printf("Start listening on: %s", port)
    ln, err := net.Listen("tcp", fmt.Sprintf(":%s", port))
    if err != nil {
        return err
    }

    go func() {
        log.Printf("Waiting for shut down signal ^C")
        <-r.ctx.Done()
        r.shutDownAsked = true
        log.Printf("Shut down signal received, closing connections...")
        ln.Close()
    }()

    for {
        conn, err := ln.Accept()
        r.connectionId += 1
        if err != nil {
            log.Printf("Failed to accept new connection: [%d] %s", r.connectionId, err.Error())
            if r.shutDownAsked {
                log.Printf("Shutdown asked [%d]", r.connectionId,)
                break
            }
            continue
        }

        log.Printf("Connection accepted: [%d] %s", r.connectionId, conn.RemoteAddr())
        go r.handle(conn, r.connectionId, r.enableDecoding)
    }

    return nil
}

func (r *Proxy) handle(conn net.Conn, connectionId uint64, enableDecoding bool) {
    connection := NewConnection(r.host, r.port, conn, connectionId, enableDecoding)
    err := connection.Handle()
    if err != nil {
        log.Printf("Error handling proxy connection: %s", err.Error())
    }
}

func (r *Proxy) EnableDecoding() {
    r.enableDecoding = true
}
package proxy

import (
    "fmt"
    "go-mysql-proxy/protocol"
    "io"
    "log"
    "net"
)

func NewConnection(host string, port string, conn net.Conn, id uint64, enableDecoding bool) *Connection {
    return &Connection{
        host: host,
        port: port,
        conn: conn,
        id: id,
        enableDecoding: enableDecoding,
    }
}

type Connection struct {
    id             uint64
    conn           net.Conn
    host           string
    port           string
    enableDecoding bool
}

func (r *Connection) Handle() error {
    address := fmt.Sprintf("%s%s", r.host, r.port)
    mysql, err := net.Dial("tcp", address)
    if err != nil {
        log.Printf("Failed to connection to MySQL: [%d] %s", r.id, err.Error())
        return err
    }

    if !r.enableDecoding {
        // client to server
        go func() {
            copied, err := io.Copy(mysql, r.conn)
            if err != nil {
                log.Printf("Conection error: [%d] %s", r.id, err.Error())
            }

            log.Printf("Connection closed. Bytes copied: [%d] %d", r.id, copied)
        }()

        copied, err := io.Copy(r.conn, mysql)
        if err != nil {
            log.Printf("Connection error: [%d] %s", r.id, err.Error())
        }

        log.Printf("Connection closed. Bytes copied: [%d] %d", r.id, copied)

        return nil
    }

    handshakePacket := &protocol.InitialHandshakePacket{}
    err = handshakePacket.Decode(mysql)
    if err != nil{
        log.Printf("Failed ot decode handshake initial packet: %s", err.Error())
        return err
    }

    fmt.Printf("InitialHandshakePacket:\n%s\n", handshakePacket)

    res, _ := handshakePacket.Encode()

    written, err := r.conn.Write(res)
    if err != nil{
        log.Printf("Failed to write %d: %s", written, err.Error())
        return err
    }

    go func() {
        copied, err := io.Copy(mysql, r.conn)
        if err != nil {
            log.Printf("Conection error: [%d] %s", r.id, err.Error())
        }

        log.Printf("Connection closed. Bytes copied: [%d] %d", r.id, copied)
    }()

    copied, err := io.Copy(r.conn, mysql)
    if err != nil {
        log.Printf("Connection error: [%d] %s", r.id, err.Error())
    }

    log.Printf("Connection closed. Bytes copied: [%d] %d", r.id, copied)

    return nil
}

I'm not able to find the replacement of io.Copy that can modify the request before sending to actual server.

Could someone please help here?

Shadow
  • 33,525
  • 10
  • 51
  • 64
  • There are already SQL proxies around so there is no need for inventing the wheel new – nbk May 21 '23 at 07:07
  • @nbk yes, there are many. But I want to modify the request at proxy level before sending it to actual server. Could you please guide me to any open source proxy that does this? – Prashant Pratap Singh May 21 '23 at 07:15
  • The SQL proxy can change the request, you must defend be the rules, writing a new proxy is very time consuming and as I said unnecessary – nbk May 21 '23 at 10:37
  • @PrashantPratapSingh in order to replace io.Copy you would need to parse the MYSQL wire protocol and make changes to the queries. This would involve have a working MYSQL parser, in Go, that matches the version that you're using and can be easily edited. It might be very difficult to edit things in place, and there might be complexities with the wire protocol that you have to handle. – maxm May 21 '23 at 18:56
  • I see that Vitess has a MySQL binary protocol libary: https://pkg.go.dev/vitess.io/vitess/go/mysql Maybe it could be used for your use-case. You see see from the complexities of the library API that this is a very large undertaking. – maxm May 21 '23 at 18:57
  • This blog post also might help: https://www.galliumdata.com/use-cases/rewriting-mysql-queries-on-the-fly, it reviews various options to rewrite queries. – maxm May 21 '23 at 18:59

0 Answers0