下面我将详细讲解如何利用Go语言实现流量回放工具的示例代码,并且包含两条示例说明:
1. 安装依赖和工具
- 首先需要安装Go语言环境,跟据Go语言官网的说明安装即可。https://golang.org/
- 安装
dep
包管理工具,可以使用以下命令安装:curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
。 - 接下来使用dep工具下载项目相关依赖,可以使用以下命令下载依赖:
dep ensure
。
2. 实现示例一:HTTP 回放
实现原理
首先,需要通过抓包工具获取到需要回放的请求和响应,然后将它们保存为 json 格式的文件,命名为 req.json
和 resp.json
。
我们在此基础上,编写以下代码实现回放。其中,rr
表示回放器,可以从 json 文件中加载请求和响应,运行时可以直接使用:
import (
"github.com/bradleyjkemp/memviz"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"github.com/google/gopacket/tcpassembly/tcpreader"
"github.com/hjson/hjson-go"
)
type Request struct {
Method string
Path string
Header map[string]string
Body string
}
type Response struct {
Code int
Header map[string]string
Body string
}
type RequestResponse struct {
Request Request
Response Response
}
type requestPrinter struct{}
func (rp *requestPrinter) ProcessPacket(net, transport gopacket.Flow, tcp gopacket.Layer, payload []byte) {
rsp, found := rr.MatchTCP(net, transport, tcp, payload)
if found {
// 找到了要回放的请求
req, _ := rsp.Request.(*Request)
// TODO: 发送回放请求
fmt.Printf("Replaying response for %s %s\n", req.Method, req.Path)
}
}
func main() {
var config Config
_, err := Hjson.Unmarshal([]byte(configJson), &config)
if err != nil {
panic(err)
}
rrs := make([]RequestResponse, len(config.RequestsResponses))
for i, rr := range config.RequestsResponses {
reqStr, err := ioutil.ReadFile(rr.RequestPath)
if err != nil {
panic(err)
}
rspStr, err := ioutil.ReadFile(rr.ResponsePath)
if err != nil {
panic(err)
}
var req Request
err = json.Unmarshal(reqStr, &req)
if err != nil {
panic(err)
}
var rsp Response
err = json.Unmarshal(rspStr, &rsp)
if err != nil {
panic(err)
}
rrs[i] = RequestResponse{
Request: req,
Response: rsp,
}
}
rr, err := NewRoundRobinRequestResponse(rrs)
if err != nil {
log.Panicf("Failed to load RequestResponse: %v", err)
}
rr.Debug = true
handle, err := pcap.OpenOffline(config.PcapFile)
if err != nil {
log.Panicf("Error opening pcap file: %v", err)
}
defer handle.Close()
filter := fmt.Sprintf("tcp port %d", config.Port)
if err := handle.SetBPFFilter(filter); err != nil {
log.Panicf("Error setting BPF filter: %v", err)
}
// Set up assembly
streamFactory := &httpStreamFactory{
requestChan: make(chan *Request),
}
streamPool := tcpassembly.NewStreamPool(streamFactory)
assembler := tcpassembly.NewAssembler(streamPool)
streamFactory.responseChan = make(chan *Response)
// Set up a packet source to read from our pcap file
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
// Iterate over packets
for packet := range packetSource.Packets() {
tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer == nil {
continue
}
tcp, _ := tcpLayer.(*layers.TCP)
assembler.AssembleWithTimestamp(packet.NetworkLayer().NetworkFlow(), tcp, packet.Metadata().Timestamp)
// 发送回放请求
select {
case req := <-streamFactory.requestChan:
rsp, found := rr.MatchRequest(req)
if !found {
log.Printf("No matching response found for request %s %s\n", req.Method, req.Path)
continue
}
streamFactory.responseChan <- rsp.(*Response)
default:
}
}
}
示例解析
在以上的代码中,我们首先定义了3个结构体:
Request
:表示 HTTP 请求。Response
:表示 HTTP 响应。RequestResponse
:表示一次 HTTP 请求和响应对。
在 main
函数中,首先读取配置文件,从配置文件中读取需要回放的请求和响应的信息。然后,使用 NewRoundRobinRequestResponse
函数将这些请求和响应对象封装成一个 rr
,进而在流量包解析的过程中,能够实时找到并回放对应的请求。
最后,我们处理读取 pcap 文件的包,解析出 TCP 层数据、使用 HTTP 解析器从 TCP 并发流中提取 HTTP 请求,再检查其是否属于需要回放的请求并将 HTTP 响应写回,完成 HTTP 回放。
3. 实现示例二:TCP 回放
实现原理
在 TCP 协议下,我们无法像 HTTP 协议那样通过请求和响应来判断是否是我们想要回放的数据包。因此在回放 TCP 流量的时候,我们需要找到一些可以通过唯一标识来识别的信息。
比如在 MySQL 协议中,客户端将会在握手过程中发送一个握手包,服务端会回复一个握手响应包,并且在这两个包中都包含一个 connection id
,用于标识这个连接的唯一性,我们就可以通过这个 connection id
来判断是否是我们想要回放的数据包。
在以下示例代码中,我们通过使用 mysql.ConnParamsFromHandshakePacket
函数来获得握手包中的 connection id
,用于判断数据包是否属于需要回放的TCP流量。在实际操作中,针对不同的协议,需要使用不同的技术来找到唯一标识的信息。
type RequestResponse struct {
Request []byte
Response []byte
ConnectionId uint32
SequenceId uint8
}
type RequestResponseList struct {
List []RequestResponse
ConnectionIdList []uint32
}
func main() {
// ... 读取配置
rrl := RequestResponseList{}
for _, rr := range rrl.List {
fmt.Println("ConnectionId", rr.ConnectionId)
fmt.Println("SequenceId", rr.SequenceId)
}
// 从 pcap 文件中读取包数据
handle, err := pcap.OpenOffline(config.PcapFile)
if err != nil {
panic(err)
}
defer handle.Close()
filter := "tcp and ("
for _, cid := range rrl.ConnectionIdList {
filter += fmt.Sprintf("tcp dst port %d and (tcp[tcpflags] & tcp-syn != 0) and tcp[tcpflags] & tcp-ack = 0 and tcp[tcpflags] & tcp-rst = 0 and tcp[tcpflags] & tcp-fin = 0 and tcp dst port %d) or (tcp src port %d and tcp[tcpflags] & tcp-syn != 0 and tcp[src port] = %d and tcp dst port %d)",
config.Port, cid, cid, config.Port, cid, cid)
}
filter += ")"
fmt.Println("Filter", filter)
if err := handle.SetBPFFilter(filter); err != nil {
panic(err)
}
// 回放 TCP 流量
for _, rr := range rrl.List {
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", config.Host, config.Port))
if err != nil {
panic(err)
}
defer conn.Close()
dstWindow := NewSlidingWindow(len(rr.Response), 256, func(data []byte) (int, error) {
return conn.Write(data)
})
srcWindow := NewSlidingWindow(len(rr.Request), 256, func(data []byte) (int, error) {
return conn.Read(data)
})
tcpstream := NewTCPStream(srcWindow, dstWindow, rr.SequenceId)
tcpstream.Send(rr.Request)
tcpstream.Recv(rr.Response)
}
}
示例解析
在以上代码中,我们首先定义了一个 RequestResponse
结构体,其中包含需要回放的 TCP 请求和响应的字节数组、以及连接标识的信息(ConnectionId
和SequenceId
)。在 RequestResponseList
结构体中,我们将所有需要回放的请求和响应封装成一个列表,并且提取出所有需要回放的连接的标识(ConnectionId
)。
在 main
函数中,读取完配置文件后,使用 pcap.OpenOffline
函数从读取的 pcap 文件中获得包数据,然后使用 connection id
(TCP握手包中的id) 列表生成BPF过滤器,并为每个需要回放的连接建立并发的 TCP 连接。接下来,在每个连接上按照顺序回放请求和响应包,完成 TCP 回放。在实际应用中,我们需要根据具体的流量协议来解析出唯一的流量标识,并且使用类似以上的方式来回放该协议的流量。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:利用Go语言实现流量回放工具的示例代码 - Python技术站