上回博客我们讲到了内核抓包内核端的实现,通过上篇博客的例子我们就能够开始抓包了,整个抓包的拓扑图如下:
当开始抓包后,抓包机器将抓到的报文送到服务器上,假设服务器地址是192.168.199.123:8099,如果在抓包服务器上打开wireshark可以看到送过来的报文,如下图这样子:
可以看到抓包服务器上收到了报文,但是这样子的报文我们是无法直接拿来分析的,因为实际的报文是封装起来的,需要把数据提取出来封装成wireshark能够识别的格式才行,本篇博客就介绍一下抓包服务器的实现原理。
在看实际的代码之前,我们先来看一下抓包服务器开启后的样子,首先监听本地8099端口
其次将送到该端口的数据封装成pcap格式,使用wireshark打开后如下:
这是实际抓到的报文,能够清楚看到报文里面的各项数据。
下面介绍抓包服务器实现的原理。
抓包服务器功能大致如下:
1. 启动后监听报文送来的地址,在本例中是8099
2. 收到数据后将数据保存到文件中并以wireshark可识别的格式保存。
通常使用wireshark抓包保存的文件都已.pcap为后缀名。里面存储报文的实际数据。pcap文件格式如下:
每个pcap文件都有一个pcap首部,占24字节,之后就是pcap报文头和报文数据,报文头存储了抓包的时间戳等信息。
抓包服务器的任务就是新建一个pcap文件,写入pcap文件头后,之后每收到一个数据就写入一个pcap报文头和报文数据。这样等保存文件后就可以直接使用wireshark打开了。
winpcap是windows版本的开源抓包库,里面提供了包括pcap文件创建、保存等一系列接口,可以直接拿来使用,我这里的例子是windows64位的 golang版本的,Linux版本下有libpcap库可供使用。
代码如下:
// main.go
package main
import (
"fmt"
"net"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
//"github.com/google/gopacket/pcap"
"github.com/google/gopacket/pcapgo"
)
type CaptureManager struct {
F *os.File //抓包文件
W *pcapgo.Writer //用于写文件
conn *net.UDPConn //udp连接
}
func CaptureSaveFile() {
for {
//套接字接收缓存
buffer := make([]byte, 65535)
//接收数据
num, _, err := CapMng.conn.ReadFromUDP(buffer)
if nil != err {
continue
} else {
captureInfo := gopacket.CaptureInfo{
Timestamp: time.Now().UTC(),
CaptureLength: num,
Length: num,
InterfaceIndex: 0,
}
//写入pcap数据包头以及数据
CapMng.W.WritePacket(captureInfo, buffer[:num])
CapMng.F.Seek(0, os.SEEK_END)
}
}
}
var CapMng *CaptureManager
func main() {
//全局管理控制块
CapMng = &CaptureManager{}
//创建目录
os.MkdirAll("capture_packet", 0777)
//创建文件名字
t := time.Now()
fil := "capture_packet" + "/" + "capture" + "_" + t.Format("20060102150405") + ".pcap"
//创建文件
CapMng.F, _ = os.Create(fil)
//填充pcap文件头部24字节
CapMng.W = pcapgo.NewWriter(CapMng.F)
CapMng.W.WriteFileHeader(1024, layers.LinkTypeEthernet)
//设置监听地址
Addr, err := net.ResolveUDPAddr("udp", "0.0.0.0:"+strconv.Itoa(8099))
if nil != err {
fmt.Println("resulve udp addr fail")
return
} else {
//创建udp连接
CapMng.conn, err = net.ListenUDP("udp", Addr)
if nil != err {
fmt.Println("create conn fail")
return
} else {
fmt.Println("Capture Start")
go CaptureSaveFile()
}
}
chSignal := make(chan os.Signal, 5)
//signal.Notify(chSignal)
//监听指定信号
signal.Notify(chSignal, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT,
syscall.SIGSEGV, syscall.SIGABRT)
fmt.Println("capture server start")
//阻塞直至有信号传入
sig := <-chSignal
switch sig {
case syscall.SIGSEGV, syscall.SIGABRT:
default:
}
fmt.Println("*************Server Recive signal %s, program exit", sig.String())
CapMng.F.Close()
CapMng.conn.Close()
fmt.Println("CLOSE")
time.After(1 * time.Second)
}
代码在github上,下载后可在安装了go环境下的windows上直接运行。
git@github.com:FuYuanDe/capture_demo.git
编译go程序可能有点麻烦,但既然都看到这了,估计这点麻烦也不会难道你
到目前位置,内核抓包功能基本的框架已经搭起来了,但是和tcpdump相比,还有很多不足之处,比方说能否根据过滤条件抓取报文?其次,送到远端服务器上的时候如果mtu值很小怎么办?要不要分片?还有就是在POST_ROUTING上的hook函数抓到的报文是没有mac地址的!!!啥?没MAC地址! 没错,本机出去的报文在IP层还没有填写MAC地址,这时候抓包的话是无法得到目的mac地址的。有空的话再聊聊这些问题的解决方法。有问题的同志想要交流的话可以加群讨论一下奥
参考资料:
1.
Winpcap的安装使用方法和问题总结
https://blog.csdn.net/yu314092706/article/details/52937189
2.
golang使用gopacket包进行数据包捕获,注入和分析
https://blog.csdn.net/ptmozhu/article/details/72652310?utm_source=itdadao&utm_medium=referral
3. pcap文件格式分析
https://www.cnblogs.com/2017Crown/p/7162303.html
またね!