从零写一个兼容MySQL/Oracle的Proxy中件间(二):SQL捕获和改写

续上一篇: 从零写一个兼容MySQL/Oracle的Proxy中件间(一)《初识Oracle的通信协议》

0.前言

昨天的文字里写开发这个中间件的原由和要解决的问题,有朋友留言

网上有现成的开源中间件为啥不用

答:网上有很多MySQL的中件间,Oralce目前还没有可以免费使用的中件间. 这可能就是开源和闭源的差别。

Oracle自带的功能已经可以实现想要的功能(高可用/审计日志)

答:

1.昨天我们实现了以下功能]

开始动手:

步骤一:从Oracle通信包中分解出SQL语句

已知有以下两种head的包是在传递SQL

0x1 0xf 0x0 0x0 0x6 0x0 0x0 0x0 0x0 0x0 0x11 0x6b 0x4 0xa5 0x10 0x0 0x0 0x35 0x1c 0x0 0x0 0x1 0x0 0x0 0x0 0x3 0x5e 0x5 0x61 0x80 0x0 0x0 0x0 0x0 0x0 0x0 0xfe 0xff 0xff 0xff 
0x1 0x0 0x0 0x0 0x6 0x0 0x0 0x0 0x0 0x0 0x3 0x5e 0x6 0x61 0x80 0x0 0x0 0x0 0x0 0x0 0x0 0xfe 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0x24 0x0 0x0 0x0 0xfe 0xff 0xff 0xff 0xff 0xff 0xff 

1.写一个Python脚本,用来连接并执行两个SQL

#!/usr/bin/env python
## coding: utf-8
import cx_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("连接关闭")

2.在本地启用一个1106端口的服务,拦截到Oracle请求。并在找到特定两种head头的包,解析其中的SQL

代码如下:

if eqByte(bLogininfo[0:], buffer[0:20]) {
          log.Printf("抓到:%s-->%s的用户登录包\n", sqlInfo.client, sqlInfo.server)
          log.Printf("%s\n", getLogininfo(buffer[58:260]))
        } else if eqByte(sqlfinfo[3:11], buffer[3:11]) || eqByte(sql2info[3:11], buffer[3:11]) {
          log.Printf("抓到:%s-->%s的用户SQL包\n", sqlInfo.client, sqlInfo.server)
          sqlstr := getSQLinfo(buffer[180:])
          log.Printf("SQL:%s\n", sqlstr)
        } else {
          log.Printf("抓到:%s到%s包\n", sqlInfo.client, sqlInfo.server)
          //printBufferHead(buffer, 40)
        }
        
 func getSQLinfo(buffer []byte) string {
  var bufferNew bytes.Buffer
  for _, v := range buffer {
    if v < 0x20 {
      continue
    } else if v == 0x80 {
      break
    } else if v > 0 {
      bufferNew.WriteString(string(v))
    }
  }
  return bufferNew.String()
}     

3.执行Python脚本

proxy测试代码

proxy运行代码

这里有个不太严谨的地方,用0x80当成SQL终止的标识,可能不一定准,但我目前分析的包都没问题,

一个简单的SQL日志功能就完成了

步骤2:SQL改写

既然可以记录用户请求的SQL,是不是可以改写这个包,以实现拦截部分指定SQL,改写指定的表(例如把 user表的请求,改写成user01)

这块有点复杂,先试一个简单的

把用户的请求:select 1 from dual;

改写成 :select 2 from dual;

这样用户select 1,结果返回了2,是不是很崩溃??图片

续着上一段代码,增加了改写SQL部分

if eqByte(bLogininfo[0:], buffer[0:20]) {
    log.Printf("抓到:%s-->%s的用户登录包\n", sqlInfo.client, sqlInfo.server)
    log.Printf("%s\n", getLogininfo(buffer[58:260]))
  } else if eqByte(sqlfinfo[3:11], buffer[3:11]) || eqByte(sql2info[3:11], buffer[3:11]) {
    log.Printf("抓到:%s-->%s的用户SQL包\n", sqlInfo.client, sqlInfo.server)
    sqlstr := getSQLinfo(buffer[180:])
    log.Printf("SQL:%s\n", sqlstr)
    if strings.Contains(sqlstr, "select 1") {
      //如果发现sql里有select 1 将它改写成 select 2
      bIndex, eIndex := getSQLIndex(buffer[0:])
      for j := bIndex; j < eIndex; j++ {
        if buffer[j] == 0x31 {
          log.Printf("这里%d用户发起的select 1,替换成了 select 2", j)
          buffer[j] = 0x32 //这里把用户发起的select 1,替换成了 select 2
          break
          }
        }
      }
  
  } else {
    log.Printf("抓到:%s到%s包\n", sqlInfo.client, sqlInfo.server)
    //printBufferHead(buffer, 40)
  }

proxy运行代码2

这样我们就通过在中间层拦截用户的SQL,并改写了它。

今天就写到这了,还有些其他的工作要处理,明天继续。

>> Home

51ak

2022/01/06

Categories: mysql oracle proxy 数据库代理 Tags: 原创 精品

《数据库工作笔记》公众号
扫描上面的二维码,关注我的《数据库工作笔记》公众号