续: 从零写一个兼容MySQL/Oracle的Proxy中件间(一)《初识Oracle的通信协议》 从零写一个兼容MySQL/Oracle的Proxy中件间(二):SQL捕获和改写
1.过去的两天我们实现了以下功能]
- Oracle登录捕获:捕获了Oracle通信协议中的用户登录包
- Oracle用户解析:抓到了用户传用户名和密码的内容(密码是加密串)
- SQL请求包:同时通过对比,确定了用户发送SQL请求的通信包
- OracleSQL日志:分析这些包,把SQL语句拿出来,记到日志里。
- OracleSQL改写:用户发起的SQL 经过中间层改写到了服务端收到的是另一个SQL执行返回结果。
- MySQL兼容:增加配置文件,使中件间可以支持两种数据库
- MySQL协议解析:将经过proxy的MySQL包里的SQL语句解析出来,记录到日志
开始动手:
步骤一:中件间可以同时支持MySQL和Oracle
中件间的配置应该放在哪,理论上是想放在MySQL或zk里,当配置有变更的时候,中件间获得变更,但这个实现有点麻烦,可能得写好久,就先一个本地的配置文件
准备一个配置文件
proxy]
proxytype = mysql
bind = 0.0.0.0:1106
server = 10.26.*.*:3307
isssl = false
iscatchquery = true
iscatchlogin = false
maxsquerysize = 4096
[proxybak]
#proxytype = oracle
#bind = 0.0.0.0:1106
#server = 10.26.*.*:1521
#isssl = false
#iscatchquery = true
#iscatchlogin = false
#maxsquerylsize = 4096
然后在通信进程中收到包时处理
func (t *Proxy) pipeSend(dstCon, srcCon *Conn, chSend chan int64) {
defer pipeClose(dstCon)
switch ProxyType {
case "mysql":
log.Printf("mysql:sqlPipeMySQL\n")
sqlPipeMySQL(srcCon, dstCon)
case "oracle":
log.Printf("oracle:sqlPipeOracle\n")
sqlPipeOracle(srcCon, dstCon)
}
chSend <- 0
}
步骤二:实现sqlPipeMySQL 方法
因为MySQL开源的原因,解析的方法往上到处都是,copy了一个过来。重点是包的第4个字节,当buffer[4]=3的时候,这个就是sql语句。
func sqlPipeMySQL(src, dst *Conn) {
buffer := make([]byte, Bsize)
client_ip, _ := ipPortFromNetAddr(src.conn.RemoteAddr().String())
server_ip, _ := ipPortFromNetAddr(dst.conn.RemoteAddr().String())
defer src.Close()
for {
n, err := src.Read(buffer)
if err != nil {
return
}
if n >= 5 {
switch buffer[4] {
case 1:
log.Printf("抓到:%s-->%s:%s\n", client_ip, server_ip, "quit")
case 4:
log.Printf("抓到:%s-->%s:%s\n", client_ip, server_ip, "show databases")
case 84:
log.Printf("抓到:%s-->%s:%s\n", client_ip, server_ip, "conn prepare")
case 133:
log.Printf("抓到:%s-->%s:%s\n", client_ip, server_ip, "user connect")
case 3:
//SQL 语句
log.Printf("抓到:%s-->%s:%s\n", client_ip, server_ip, removeNewLine(sqlInit(string(buffer[5:n]))))
default:
log.Printf("抓到:%s-->%s:buffer4:%v\n", client_ip, server_ip, buffer[4])
}
}
_, err = dst.Write(buffer[0:n])
if err != nil {
return
}
}
再写个测试脚本:
#!/usr/bin/env python
## coding: utf-8
import cx_Oracle
import MySQLdb
def test_mysql():
conn = MySQLdb.connect("127.0.0.1",port=1106,user="dboopreader",passwd= "dbooppassword", db="test")
print("连接成功")
curs = conn.cursor()
sql = 'select 1 '
curs.execute(sql)
for result in curs:
print(f"执行sql[ select 1 ]返回{str(result)}")
sql = 'select 5 '
curs.execute(sql)
for result in curs:
print(f"执行sql[ select 5 ]返回{str(result)}")
curs.close()
conn.close()
print("连接关闭")
def test_oracle():
conn = cx_Oracle.connect('dboopreader/dbooppassword@127.0.0.1:1106/tlionrdb')
print("连接成功")
curs = conn.cursor()
sql = 'select 1 from dual'
curs.execute(sql)
for result in curs:
print(f"执行sql[ select 1 from dual ]返回{str(result)}")
sql = 'select 5 from dual'
curs.execute(sql)
for result in curs:
print(f"执行sql[ select 5 from dual ]返回{str(result)}")
curs.close()
conn.close()
print("连接关闭")
if __name__ == "__main__":
test_mysql()
#test_oracle()
步骤三:执行测试脚本
## python3.9 script/test.py
连接成功
执行sql[ select 1 ]返回(1,)
执行sql[ select 5 ]返回(5,)
连接关闭
同时服务端收到日志
2022/01/10 18:54:38 抓到:127.0.0.1-->10.26.*.*:buffer4:141
2022/01/10 18:54:38 抓到:127.0.0.1-->10.26.*.*:buffer4:48
2022/01/10 18:54:38 抓到:127.0.0.1-->10.26.*.*:set autocommit=0
2022/01/10 18:54:38 抓到:127.0.0.1-->10.26.*.*:select 1
2022/01/10 18:54:38 抓到:127.0.0.1-->10.26.*.*:select 5
2022/01/10 18:54:38 抓到:127.0.0.1-->10.26.*.*:quit
至此MySQL协议的 抓取/分析/转发 就做完了,跟Oracle的盲猜,MySQL的功能实现容易太多。
>> Home