Go语言MySQL代理服务器无法在转发前修改请求求助
解决MySQL代理中修改请求的问题
要替换io.Copy实现请求修改,核心是按MySQL协议逐包解析、修改后转发,而不是直接复制字节流。因为MySQL是基于数据包的协议,每个请求/响应都有固定格式(前3字节为长度,第4字节为序列号),必须先识别包边界、解析内容后再处理。
针对你需要代理自动添加MySQL认证凭证的需求,具体修改方案如下:
关键修改逻辑
在proxy包的Connection.Handle方法中,替换原有的io.Copy逻辑,改为双向数据包处理循环:
- 客户端→服务器方向:拦截客户端发送的认证请求包,替换其中的用户名、密码为代理预设的凭证,再转发给MySQL服务器;其他类型数据包直接转发。
- 服务器→客户端方向:直接转发服务器响应包给客户端,无需修改。
修改后的代码
以下是更新后的Connection.Handle方法:
func (r *Connection) Handle() error { address := fmt.Sprintf("%s%s", r.host, r.port) mysqlConn, err := net.Dial("tcp", address) if err != nil { log.Printf("Failed to connect to MySQL: [%d] %s", r.id, err.Error()) return err } defer mysqlConn.Close() defer r.conn.Close() // 转发服务器初始握手包给客户端 handshakePacket := &protocol.InitialHandshakePacket{} err = handshakePacket.Decode(mysqlConn) if err != nil { log.Printf("Failed to decode handshake initial packet: [%d] %s", r.id, err.Error()) return err } handshakeBytes, err := handshakePacket.Encode() if err != nil { log.Printf("Failed to encode handshake packet: [%d] %s", r.id, err.Error()) return err } _, err = r.conn.Write(handshakeBytes) if err != nil { log.Printf("Failed to send handshake to client: [%d] %s", r.id, err.Error()) return err } // 启动双向数据处理协程 errChan := make(chan error, 2) // 客户端→代理→MySQL服务器 go func() { seq := uint8(1) // MySQL包序列号,握手包为0,后续从1开始 for { // 读取客户端完整数据包 pkg, err := protocol.ReadPacket(r.conn, seq) if err != nil { errChan <- err return } switch pkg[0] { case 0x01: // 识别认证请求包 // 解析原始认证包 authPkg := &protocol.AuthPacket{} if err := authPkg.Decode(pkg); err != nil { errChan <- fmt.Errorf("decode auth packet failed: [%d] %w", r.id, err) return } // 替换为代理预设的MySQL凭证 authPkg.User = "proxy_user" // 你的MySQL用户名 // 若使用mysql_native_password,需用握手包的salt计算密码哈希 authPkg.Passwd = protocol.CalcPassword(handshakePacket.ScrambleBytes, []byte("proxy_pass")) // 重新编码修改后的认证包 modifiedPkg, err := authPkg.Encode() if err != nil { errChan <- fmt.Errorf("encode modified auth packet failed: [%d] %w", r.id, err) return } // 发送修改后的包到MySQL服务器 if _, err := mysqlConn.Write(modifiedPkg); err != nil { errChan <- fmt.Errorf("send modified auth packet failed: [%d] %w", r.id, err) return } default: // 其他类型数据包直接转发 if _, err := mysqlConn.Write(pkg); err != nil { errChan <- fmt.Errorf("forward packet to mysql failed: [%d] %w", r.id, err) return } } seq++ } }() // MySQL服务器→代理→客户端 go func() { seq := uint8(1) for { pkg, err := protocol.ReadPacket(mysqlConn, seq) if err != nil { errChan <- err return } // 直接转发响应包给客户端 if _, err := r.conn.Write(pkg); err != nil { errChan <- fmt.Errorf("forward packet to client failed: [%d] %w", r.id, err) return } seq++ } }() // 等待任一方向的连接关闭或错误 err = <-errChan log.Printf("Connection closed: [%d], error: %v", r.id, err) return err }
补充实现
需要在protocol包中添加ReadPacket函数,用于读取完整的MySQL数据包:
// protocol/read_packet.go package protocol import ( "io" "net" ) func ReadPacket(conn net.Conn, expectedSeq uint8) ([]byte, error) { // 读取4字节包头部(3字节长度+1字节序列号) header := make([]byte, 4) if _, err := io.ReadFull(conn, header); err != nil { return nil, err } pkgLen := int(uint32(header[0]) | uint32(header[1])<<8 | uint32(header[2])<<16) seq := header[3] if seq != expectedSeq { return nil, io.ErrUnexpectedEOF } // 读取包内容 pkgContent := make([]byte, pkgLen) if _, err := io.ReadFull(conn, pkgContent); err != nil { return nil, err } // 返回完整数据包(头部+内容) return append(header, pkgContent...), nil }
关键说明
- 密码哈希处理:如果MySQL使用
mysql_native_password认证方式,不能直接设置明文密码,必须用握手包中的ScrambleBytes对密码进行哈希计算,protocol.CalcPassword是MySQL协议标准的哈希实现。 - 协议包解析:确保你的
protocol包已正确实现AuthPacket的Decode和Encode方法,能解析和生成MySQL认证数据包。 - 为什么不能用io.Copy:
io.Copy是字节流直接复制,无法识别MySQL的数据包边界,更无法解析包内的认证信息,只有按协议逐包处理才能实现请求修改。
内容的提问来源于stack exchange,提问作者Prashant Pratap Singh
相关产品推荐
相关产品推荐

