implementl get user and database

Signed-off-by: zhuhuijun <zhuhuijunzhj@gmail.com>
This commit is contained in:
zhuhuijun
2022-12-07 18:07:00 +08:00
parent add66245ba
commit af88bad075
166 changed files with 58325 additions and 102 deletions

34
pkg/model/result.go Normal file
View File

@@ -0,0 +1,34 @@
package model
import "encoding/json"
// MysqlQueryPiece 查询信息
type MysqlQueryPiece struct {
BaseQueryPiece
ClientHost string `json:"cip"`
ClientPort string `json:"cport"`
VisitUser string `json:"user"`
VisitDB string `json:"db"`
QuerySQL string `json:"sql"`
CostTimeInMS int64 `json:"cms"`
Message string `json:"message"`
}
// BaseQueryPiece 查询信息
type BaseQueryPiece struct {
ServerIP string `json:"sip"`
ServerPort string `json:"sport"`
CapturePacketRate float64 `json:"cpr"`
EventTime int64 `json:"bt"`
}
func (p MysqlQueryPiece) ToString() string {
bytes, err := json.Marshal(&p)
if err != nil {
return ""
}
return string(bytes)
}

10
pkg/model/session.go Normal file
View File

@@ -0,0 +1,10 @@
package model
type MysqlSession struct {
UserName string
DBName string
ClientIP string
ClientPort string
ServerIP string
ServerPort string
}

161
pkg/parse/auth.go Normal file
View File

@@ -0,0 +1,161 @@
package parse
import (
"bytes"
"encoding/binary"
"errors"
)
var (
ErrMalformPacket = errors.New("malform packet error")
)
type handshakeResponse41 struct {
Capability uint32
Collation uint8
User string
DBName string
Auth []byte
}
// AuthInfoParse parse username, dbname from mysql client auth info
func AuthInfoParse(data []byte) (userName, dbName string, err error) {
var resp handshakeResponse41
pos, err := parseHandshakeResponseHeader(&resp, data)
if err != nil {
return
}
// Read the remaining part of the packet.
if err = parseHandshakeResponseBody(&resp, data, pos); err != nil {
return
}
userName = resp.User
dbName = resp.DBName
return
}
// parseHandshakeResponseHeader parses the common header of SSLRequest and HandshakeResponse41.
func parseHandshakeResponseHeader(packet *handshakeResponse41, data []byte) (parsedBytes int, err error) {
// Ensure there are enough data to read:
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest
if len(data) < 4+4+1+23 {
return 0, ErrMalformPacket
}
offset := 0
// capability
capability := binary.LittleEndian.Uint32(data[:4])
packet.Capability = capability
offset += 4
// skip max packet size
offset += 4
// charset, skip, if you want to use another charset, use set names
packet.Collation = data[offset]
offset++
// skip reserved 23[00]
offset += 23
return offset, nil
}
// parseHandshakeResponseBody parse the HandshakeResponse (except the common header part).
func parseHandshakeResponseBody(packet *handshakeResponse41, data []byte, offset int) (err error) {
defer func() {
// Check malformat packet cause out of range is disgusting, but don't panic!
if r := recover(); r != nil {
err = ErrMalformPacket
}
}()
// user name
packet.User = string(data[offset : offset+bytes.IndexByte(data[offset:], 0)])
offset += len(packet.User) + 1
if packet.Capability&ClientPluginAuthLenencClientData > 0 {
// MySQL client sets the wrong capability, it will set this bit even server doesn't
// support ClientPluginAuthLenencClientData.
// https://github.com/mysql/mysql-server/blob/5.7/sql-common/client.c#L3478
num, null, off := parseLengthEncodedInt(data[offset:])
offset += off
if !null {
packet.Auth = data[offset : offset+int(num)]
offset += int(num)
}
} else if packet.Capability&ClientSecureConnection > 0 {
// auth length and auth
authLen := int(data[offset])
offset++
packet.Auth = data[offset : offset+authLen]
offset += authLen
} else {
packet.Auth = data[offset : offset+bytes.IndexByte(data[offset:], 0)]
offset += len(packet.Auth) + 1
}
if packet.Capability&ClientConnectWithDB > 0 {
if len(data[offset:]) > 0 {
idx := bytes.IndexByte(data[offset:], 0)
packet.DBName = string(data[offset : offset+idx])
offset = offset + idx + 1
}
}
if packet.Capability&ClientPluginAuth > 0 {
// TODO: Support mysql.ClientPluginAuth, skip it now
idx := bytes.IndexByte(data[offset:], 0)
offset = offset + idx + 1
}
if packet.Capability&ClientConnectAtts > 0 {
if len(data[offset:]) == 0 {
// Defend some ill-formated packet, connection attribute is not important and can be ignored.
return nil
}
}
return nil
}
func parseLengthEncodedInt(b []byte) (num uint64, isNull bool, n int) {
switch b[0] {
// 251: NULL
case 0xfb:
n = 1
isNull = true
return
// 252: value of following 2
case 0xfc:
num = uint64(b[1]) | uint64(b[2])<<8
n = 3
return
// 253: value of following 3
case 0xfd:
num = uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16
n = 4
return
// 254: value of following 8
case 0xfe:
num = uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 |
uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 |
uint64(b[7])<<48 | uint64(b[8])<<56
n = 9
return
}
// 0-250: value of first byte
num = uint64(b[0])
n = 1
return
}
func extractMysqlPayloadSize(header []byte) int {
return int(uint32(header[0]) | uint32(header[1])<<8 | uint32(header[2])<<16)
}
func bytesToInt(contents []byte) int {
return int(uint32(contents[0]) | uint32(contents[1])<<8 | uint32(contents[2])<<16 | uint32(contents[3])<<24)
}

56
pkg/parse/const.go Normal file
View File

@@ -0,0 +1,56 @@
package parse
import "time"
const (
maxSQLLen = 5 * 1024 * 1024
)
// Client information.
const (
ClientLongPassword uint32 = 1 << iota
ClientFoundRows
ClientLongFlag
ClientConnectWithDB
ClientNoSchema
ClientCompress
ClientODBC
ClientLocalFiles
ClientIgnoreSpace
ClientProtocol41
ClientInteractive
ClientSSL
ClientIgnoreSigpipe
ClientTransactions
ClientReserved
ClientSecureConnection
ClientMultiStatements
ClientMultiResults
ClientPSMultiResults
ClientPluginAuth
ClientConnectAtts
ClientPluginAuthLenencClientData
)
// Auth name information.
const (
AuthName = "mysql_native_password"
)
// MySQL database and tables.
const (
// SystemDB is the name of system database.
SystemDB = "mysql"
// UserTable is the table in system db contains user info.
UserTable = "User"
// DBTable is the table in system db contains db scope privilege info.
DBTable = "DB"
// GlobalVariablesTable is the table contains global system variables.
GlobalVariablesTable = "GLOBAL_VARIABLES"
// GlobalStatusTable is the table contains global status variables.
GlobalStatusTable = "GLOBAL_STATUS"
)
const (
millSecondUnit = int64(time.Millisecond)
)

5
pkg/parse/parse.go Normal file
View File

@@ -0,0 +1,5 @@
package parse
func IsAuth(val byte) bool {
return val == 133 || val == 15
}

20
pkg/parse/session.go Normal file
View File

@@ -0,0 +1,20 @@
package parse
import (
"github.com/google/gopacket"
"go-sniffer/pkg/model"
)
func GenSession(net, transport gopacket.Flow) *model.MysqlSession {
clientIp := net.Src().String()
sourceIp := net.Dst().String()
clientPort := transport.Src().String()
serverPort := transport.Dst().String()
return &model.MysqlSession{
ClientIP: clientIp,
ClientPort: clientPort,
ServerIP: sourceIp,
ServerPort: serverPort,
}
}