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?