gopacket
go get github.com/google/gopacket
pcap 处理
查看版本
version := pcap.Version()
fmt.Println(version)
测试(Win10 x64)
Npcap version 1.00, based on libpcap version 1.9.1
网络接口
类型:pcap.Interface
type Interface struct {
Name string
Description string
Flags uint32
Addresses []InterfaceAddress
}
type InterfaceAddress struct {
IP net.IP
Netmask net.IPMask // Netmask may be nil if we were unable to retrieve it.
Broadaddr net.IP // Broadcast address for this IP may be nil
P2P net.IP // P2P destination address for this IP may be nil
}
查找网络设备
var devices []pcap.Interface
devices, _ = pcap.FindAllDevs()
fmt.Println(devices)
实时捕获
handle, err := pcap.OpenLive("\\Device\\NPF_{5C9384EF-DEBA-43A6-AE6A-5D10C952C481}", int32(65535), true, -1 * time.Second)
if err != nil {
log.Fatal(err)
}
defer handle.Close()
pcap.OpenLive
参数:
- 设备名:
pcap.FindAllDevs()
返回的设备的 Name snaplen
:捕获一个数据包的多少个字节,一般来说对任何情况 65535 是一个好的实践,如果不关注全部内容,只关注数据包头,可以设置成 1024promisc
:设置网卡是否工作在混杂模式,即是否接收目的地址不为本机的包timeout
:设置抓到包返回的超时。如果设置成 30s,那么每 30s 才会刷新一次数据包;设置成负数,会立刻刷新数据包,即不做等待
要记得释放掉 handle
打开 pcap
handle, _ = pcap.OpenOffline("dump.pcap")
defer handle.Close()
创建一个数据包源
通过监听设备的实时流量或者来自文件的数据包,我们可以得到一个 handle,通过这个 handle 得到一个数据包源 packetSource。
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
利用 handle.LinkType()
就不用知道链路类型了。
读取数据包
读取一个数据包:packet, _ := packetSource.NextPacket()
获得一个可以读取数据包的 channel 来读取全部数据包
packet := packetSource.Packets()
for packet := range packet{
fmt.Println(packet)
}
设置过滤器
使用 BPF 语法即可。
handle.SetBPFFilter("tcp and port 80")
创建一个 pcap 用于写入
dumpFile, _ := os.Create("dump.pcap")
defer dumpFile.Close()
packetWriter := pcapgo.NewWriter(dumpFile)
packetWriter.WriteFileHeader(65535, layers.LinkTypeEthernet)
packetWriter.WriteFileHeader
的参数是 snaplen 和链路类型
写入数据包
packet := packetSource.Packets()
for packet := range packet{
packetWriter.WritePacket(packet.Metadata().CaptureInfo, packet.Data())
}
packet 处理
列出所有层
for _, layer := range packet.Layers() {
fmt.Println(layer.LayerType())
fmt.Println(layer.LayerContents())
fmt.Println(layer.LayerPayload())
}
layer.LayerType()
:这层的类型layer.LayerContents()
:当前层的内容layer.LayerPayload()
:当前层承载的 payload(不包括当前层)
分析某层的数据
IPv4
ipLayer := packet.Layer(layers.LayerTypeIPv4)
if ipLayer != nil {
ip, _ := ipLayer.(*layers.IPv4)
fmt.Println(ip.SrcIP, ip.DstIP)
fmt.Println(ip.Protocol)
}
TCP
tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer != nil {
ip, _ := tcpLayer.(*layers.TCP)
fmt.Println(ip.SrcPort, ip.DstPort)
}
这里有一些需要注意的:
tcpLayer
是通过接口packet.Layer
返回的一个Layer
,一个指向layers.TCP
的指针tcp
是layers.TCP
这个具体类型的指针,也可以说tcp
是真实的 tcp 层数据
单独的解码器
可以从多个起点对数据包进行解码。可以解码没 有完整数据的数据包。
// Decode an ethernet packet
ethP := gopacket.NewPacket(packet, layers.LayerTypeEthernet, gopacket.Default)
// Decode an IPv6 header and everything it contains
ipP := gopacket.NewPacket(packet, layers.LayerTypeIPv6, gopacket.Default)
// Decode a TCP header and its payload
tcpP := gopacket.NewPacket(packet, layers.LayerTypeTCP, gopacket.Default)
懒惰解码(Lazy Decoding)
创建一个 packet 包,但是不立刻解码,只有后面需要用的时候再解码。
比如第二行解码 IPv4 层,如果有的话,就解码 IPv4 层,然后不做进一步处理(不解码后续的层);如果没有会解码整个 packet 来寻找 IPv4 层。
packet.Layers()
会解码所有层并且返回,已经解码的层不会再次做解码。
注意:这种方式并不是并发安全的,对 Layers 的每次调用可能会改变数据包。
packet := gopacket.NewPacket(myPacketData, layers.LayerTypeEthernet, gopacket.Lazy)
ip4 := packet.Layer(layers.LayerTypeIPv4)
// Decode all layers and return them. The layers up to the first IPv4 layer
// are already decoded, and will not require decoding a second time.
layers := packet.Layers()
NoCopy 解码
上述两种解码方式会复制切片,对切片的字节进行更改不会影响数据包本身。如果可以保证不修改切片,可以使用 NoCopy。
for data := range myByteSliceChannel {
p := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.NoCopy)
doSomethingWithPacket(p)
}
快速解码
不会创建新的内存结构。会重用内存结构,所以只能用于预定义好层的结构 。
for packet := range packetSource.Packets() {
var eth layers.Ethernet
var ip4 layers.IPv4
var tcp layers.TCP
parser := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, ð, &ip4, &tcp)
var decodedLayers []gopacket.LayerType
parser.DecodeLayers(packet.Data(), &decodedLayers)
for _, layerType := range decodedLayers {
fmt.Println(layerType)
}
}
自定义层数据
注册自定义的层
gopacket.RegisterLayerType()
:传入一个独有的 id,和层类型对应的 Metadata,包括有名字和对应的解码器。
var MyLayerType = gopacket.RegisterLayerType(12345, gopacket.LayerTypeMetadata{Name: "MyLayerType", Decoder: gopacket.DecodeFunc(decodeMyLayer)})
定义层的结构
要实现三个方法。
type MyLayer struct {
MyHeader []byte
MyPayload []byte
}
func (m MyLayer) LayerType() gopacket.LayerType {
return MyLayerType
}
func (m MyLayer) LayerContents() []byte {
return m.MyHeader
}
func (m MyLayer) LayerPayload() []byte {
return m.MyPayload
}
定义解码器
func decodeMyLayer(data []byte, p gopacket.PacketBuilder) error {
p.AddLayer(&MyLayer{data[:4], data[4:]})
return p.NextDecoder(layers.LayerTypeEthernet)
}