go语言的抓包库--gopacket
什么是 gopacket
gopacket是google出品的golang三方库,质量还是靠的住,项目地址为:https://github.com/google/gopacket
gopacket到底是什么呢?是个抓取网络数据包的库
大家平时可能也会用一些抓包工具:
- Windows平台下有
Wireshark
抓包工具,其底层抓包库是npcap
(以前是winpcap
); - Linux平台下有
Tcpdump
,其抓包库是libpcap
;
而 gopacket
库可以说是 libpcap
和 npcap
的go封装,提供了更方便的go语言操作接口。
libpcap
gopacket是基于libpcap(数据包捕获函数库)的,该库提供的C函数接口用于捕捉经过指定网络接口的数据包,该接口应该是被设为混杂模式。
著名的软件TCPDUMP就是在Libpcap的基础上开发而成的。
Libpcap提供的接口函数实现和封装了与数据包截获有关的过程。
Libpcap可以在绝大多数Linux平台上运行。
主要有以下功能:
- 数据包捕获:捕获流经网卡的原始数据包
- 自定义数据包发送:构造任何格式的原始数据包
- 流量采集与统计:采集网络中的流量信息
- 规则过滤:提供自带规则过滤功能,按需要选择过滤规则
环境准备
安装第三方库 libpcap或npcap
windows 平台
如果是在windows平台下,需要确保安装了npcap或winpcap
- npcap下载地址:https://nmap.org/npcap/
- WinPcap下载地址:https://www.winpcap.org/
linux平台
如果实在linux平台下确保安装了libpcap库
Linux (Debian/Ubuntu)
sudo apt-get update
sudo apt-get install libpcap-dev
Linux (Fedora/RHEL/CentOS)
sudo dnf install libpcap-devel
macOS 平台
使用 Homebrew 安装:
brew install libpcap
如果报错
# github.com/google/gopacket/pcap /usr/local/go/go/pkg/mod/github.com/google/gopacket@v1.1.19/pcap/pcap_unix.go:34:10: fatal error: pcap.h: No such file or directory 34 | #include <pcap.h> | ^~~~~~~~ compilation terminated.
那就是没安装上面的第三方库
安装gopacket库
go get github.com/google/gopacket
使用
简单使用--查看版本
package main
import (
"fmt"
"github.com/google/gopacket/pcap"
)
func main() {
version := pcap.Version()
fmt.Println(version)
}
输出
libpcap version 1.10.1 (with TPACKET_V3)
获取所有的网络设备信息
package main
import (
"fmt"
"github.com/google/gopacket/pcap"
"log"
)
func main() {
// 得到所有的(网络)设备
devices, err := pcap.FindAllDevs()
if err != nil {
log.Fatal(err)
}
// 打印设备信息
fmt.Println("Devices found:")
for _, device := range devices {
fmt.Println("Name: ", device.Name)
fmt.Println("Description: ", device.Description)
for _, address := range device.Addresses {
fmt.Println("- IP地址: ", address.IP)
fmt.Println("- 子网掩码: ", address.Netmask)
}
fmt.Println("")
}
}
输出
Devices found:
Name: enp0s3
Description:
- IP地址: 10.0.2.11
- 子网掩码: ffffff00
- IP地址: fe80::a00:27ff:fefa:c3cd
- 子网掩码: ffffffffffffffff0000000000000000
Name: enp0s8
Description:
- IP地址: 10.1.0.200
- 子网掩码: ffffff00
- IP地址: fe80::a00:27ff:fe1c:2935
- 子网掩码: ffffffffffffffff0000000000000000
Name: br-1fef79fddbd0
Description:
- IP地址: 172.20.0.1
- 子网掩码: ffff0000
- IP地址: fe80::42:eaff:fe5f:396d
- 子网掩码: ffffffffffffffff0000000000000000
Name: br-4e28a0490562
Description:
- IP地址: 172.19.0.1
- 子网掩码: ffff0000
- IP地址: fe80::42:82ff:fee0:677
- 子网掩码: ffffffffffffffff0000000000000000
Name: br-f78bb4c5df86
Description:
- IP地址: 172.18.0.1
- 子网掩码: ffff0000
- IP地址: fe80::42:4ff:fe46:59fa
- 子网掩码: ffffffffffffffff0000000000000000
Name: veth76dac41
Description:
- IP地址: fe80::b4ff:9bff:feba:4284
- 子网掩码: ffffffffffffffff0000000000000000
Name: vetha73ed3f
Description:
- IP地址: fe80::5c36:b4ff:fe17:2c7a
- 子网掩码: ffffffffffffffff0000000000000000
Name: veth4b96f58
Description:
- IP地址: fe80::1801:f3ff:fe07:e02
- 子网掩码: ffffffffffffffff0000000000000000
Name: vethd3b4305
Description:
- IP地址: fe80::ecc6:22ff:fecb:c17d
- 子网掩码: ffffffffffffffff0000000000000000
Name: any
Description: Pseudo-device that captures on all interfaces
Name: lo
Description:
- IP地址: 127.0.0.1
- 子网掩码: ff000000
- IP地址: ::1
- 子网掩码: ffffffffffffffffffffffffffffffff
Name: docker0
Description:
- IP地址: 172.17.0.1
- 子网掩码: ffff0000
Name: bluetooth-monitor
Description: Bluetooth Linux Monitor
Name: nflog
Description: Linux netfilter log (NFLOG) interface
Name: nfqueue
Description: Linux netfilter queue (NFQUEUE) interface
Name: dbus-system
Description: D-Bus system bus
Name: dbus-session
Description: D-Bus session bus
可以看到还有蓝牙之类的设备
调用 pcap.FindAllDevs()
方法
函数签名是 func FindAllDevs() (ifs []Interface, err error)
返回的是一个对象切片
而 Interface
结构体的属性
type Interface struct {
Name string
Description string
Flags uint32
Addresses []InterfaceAddress
}
所以可以遍历出名称和备注,一般没有 Description
的就是网络设备
下面的 Addresses
又是一个对象切片,其中的属性是
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
}
通过遍历这个切片,就可以获得设备上的地址和掩码
对一个网络设备进行抓包
这里以抓取 enp0s3 网口的流量为例
package main
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/pcap"
"log"
"time"
)
var (
device = "enp0s3"
timeout = 5 * time.Second
promiscuous = false
snapshot_len int32 = 1024
err error
handle *pcap.Handle
)
func main() {
// Open device
handle, err = pcap.OpenLive(device, snapshot_len, promiscuous, timeout)
if err != nil {
log.Fatal(err)
}
defer handle.Close()
// Use the handle as a packet source to process all packets
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
// Process packet here
fmt.Println(packet)
fmt.Println("==========================================================================")
}
}
结果
这个抓包是前台阻塞的,所以会一直抓
PACKET: 74 bytes, wire length 74 cap length 74 @ 2024-09-08 16:41:57.105522 +0800 CST
- Layer 1 (14 bytes) = Ethernet {Contents=[..14..] Payload=[..60..] SrcMAC=08:00:27:50:d0:48 DstMAC=08:00:27:fa:c3:cd EthernetType=IPv4 Length=0}
- Layer 2 (20 bytes) = IPv4 {Contents=[..20..] Payload=[..40..] Version=4 IHL=5 TOS=0 Length=60 Id=26048 Flags=DF FragOffset=0 TTL=64 Protocol=TCP Checksum=48343 SrcIP=10.0.2.26 DstIP=10.0.2.11 Options=[] Padding=[]}
- Layer 3 (40 bytes) = TCP {Contents=[..40..] Payload=[] SrcPort=59064 DstPort=1111(lmsocialserver) Seq=2334176263 Ack=0 DataOffset=10 FIN=false SYN=true RST=false PSH=false ACK=false URG=false ECE=false CWR=false NS=false Window=64240 Checksum=16076 Urgent=0 Options=[..5..] Padding=[]}
==========================================================================
PACKET: 54 bytes, wire length 54 cap length 54 @ 2024-09-08 16:41:57.10556 +0800 CST
- Layer 1 (14 bytes) = Ethernet {Contents=[..14..] Payload=[..40..] SrcMAC=08:00:27:fa:c3:cd DstMAC=08:00:27:50:d0:48 EthernetType=IPv4 Length=0}
- Layer 2 (20 bytes) = IPv4 {Contents=[..20..] Payload=[..20..] Version=4 IHL=5 TOS=0 Length=40 Id=0 Flags=DF FragOffset=0 TTL=64 Protocol=TCP Checksum=8876 SrcIP=10.0.2.11 DstIP=10.0.2.26 Options=[] Padding=[]}
- Layer 3 (20 bytes) = TCP {Contents=[..20..] Payload=[] SrcPort=1111(lmsocialserver) DstPort=59064 Seq=0 Ack=2334176264 DataOffset=5 FIN=false SYN=false RST=true PSH=false ACK=true URG=false ECE=false CWR=false NS=false Window=0 Checksum=28019 Urgent=0 Options=[] Padding=[]}
==========================================================================
PACKET: 42 bytes, wire length 42 cap length 42 @ 2024-09-08 16:42:02.279257 +0800 CST
- Layer 1 (14 bytes) = Ethernet {Contents=[..14..] Payload=[..28..] SrcMAC=08:00:27:fa:c3:cd DstMAC=08:00:27:50:d0:48 EthernetType=ARP Length=0}
- Layer 2 (28 bytes) = ARP {Contents=[..28..] Payload=[] AddrType=Ethernet Protocol=IPv4 HwAddressSize=6 ProtAddressSize=4 Operation=1 SourceHwAddress=[..6..] SourceProtAddress=[10, 0, 2, 11] DstHwAddress=[..6..] DstProtAddress=[10, 0, 2, 26]}
==========================================================================
PACKET: 60 bytes, wire length 60 cap length 60 @ 2024-09-08 16:42:02.279724 +0800 CST
- Layer 1 (14 bytes) = Ethernet {Contents=[..14..] Payload=[..46..] SrcMAC=08:00:27:50:d0:48 DstMAC=08:00:27:fa:c3:cd EthernetType=ARP Length=0}
- Layer 2 (28 bytes) = ARP {Contents=[..28..] Payload=[..18..] AddrType=Ethernet Protocol=IPv4 HwAddressSize=6 ProtAddressSize=4 Operation=2 SourceHwAddress=[..6..] SourceProtAddress=[10, 0, 2, 26] DstHwAddress=[..6..] DstProtAddress=[10, 0, 2, 11]}
- Layer 3 (18 bytes) = Payload 18 byte(s)
==========================================================================
PACKET: 60 bytes, wire length 60 cap length 60 @ 2024-09-08 16:42:02.358783 +0800 CST
- Layer 1 (14 bytes) = Ethernet {Contents=[..14..] Payload=[..46..] SrcMAC=08:00:27:50:d0:48 DstMAC=08:00:27:fa:c3:cd EthernetType=ARP Length=0}
- Layer 2 (28 bytes) = ARP {Contents=[..28..] Payload=[..18..] AddrType=Ethernet Protocol=IPv4 HwAddressSize=6 ProtAddressSize=4 Operation=1 SourceHwAddress=[..6..] SourceProtAddress=[10, 0, 2, 26] DstHwAddress=[..6..] DstProtAddress=[10, 0, 2, 11]}
- Layer 3 (18 bytes) = Payload 18 byte(s)
==========================================================================
PACKET: 42 bytes, wire length 42 cap length 42 @ 2024-09-08 16:42:02.358792 +0800 CST
- Layer 1 (14 bytes) = Ethernet {Contents=[..14..] Payload=[..28..] SrcMAC=08:00:27:fa:c3:cd DstMAC=08:00:27:50:d0:48 EthernetType=ARP Length=0}
- Layer 2 (28 bytes) = ARP {Contents=[..28..] Payload=[] AddrType=Ethernet Protocol=IPv4 HwAddressSize=6 ProtAddressSize=4 Operation=2 SourceHwAddress=[..6..] SourceProtAddress=[10, 0, 2, 11] DstHwAddress=[..6..] DstProtAddress=[10, 0, 2, 26]}
==========================================================================
PACKET: 60 bytes, wire length 60 cap length 60 @ 2024-09-08 16:43:39.952469 +0800 CST
- Layer 1 (14 bytes) = Ethernet {Contents=[..14..] Payload=[..46..] SrcMAC=08:00:27:50:d0:48 DstMAC=08:00:27:fa:c3:cd EthernetType=IPv4 Length=0}
- Layer 2 (20 bytes) = IPv4 {Contents=[..20..] Payload=[..14..] Version=4 IHL=5 TOS=0 Length=34 Id=37121 Flags=DF FragOffset=0 TTL=64 Protocol=UDP Checksum=37285 SrcIP=10.0.2.26 DstIP=10.0.2.11 Options=[] Padding=[]}
- Layer 3 (08 bytes) = UDP {Contents=[..8..] Payload=[..6..] SrcPort=53534 DstPort=1111(lmsocialserver) Length=14 Checksum=60246}
- Layer 4 (06 bytes) = Payload 6 byte(s)
==========================================================================
PACKET: 76 bytes, wire length 76 cap length 76 @ 2024-09-08 16:43:39.952505 +0800 CST
- Layer 1 (14 bytes) = Ethernet {Contents=[..14..] Payload=[..62..] SrcMAC=08:00:27:fa:c3:cd DstMAC=08:00:27:50:d0:48 EthernetType=IPv4 Length=0}
- Layer 2 (20 bytes) = IPv4 {Contents=[..20..] Payload=[..42..] Version=4 IHL=5 TOS=192 Length=62 Id=61416 Flags= FragOffset=0 TTL=64 Protocol=ICMPv4 Checksum=29170 SrcIP=10.0.2.11 DstIP=10.0.2.26 Options=[] Padding=[]}
- Layer 3 (08 bytes) = ICMPv4 {Contents=[..8..] Payload=[..34..] TypeCode=DestinationUnreachable(Port) Checksum=5441 Id=0 Seq=0}
- Layer 4 (34 bytes) = Payload 34 byte(s)
==========================================================================
实时捕获
调用方法 pcap.OpenLive()
来打开网络设备
方法的函数签名为 func OpenLive(device string, snaplen int32, promisc bool, timeout time.Duration) (handle *Handle, _ error)
device
:网络设备的名称,如eth0,也可以填充pcap.FindAllDevs()
返回的设备的Namesnaplen
:每个数据包读取的最大长度promisc
:是否将网口设置为混杂模式,即是否接收目的地址不为本机的包timeout
:设置抓到包返回的超时。如果设置成5s,那么每5s才会刷新一次数据包;设置成负数,会立刻刷新数据包,即不做等待handle
:是一个*Handle类型的返回值,可能作为gopacket
其他函数调用时作为函数参数来传递。
注意:
一定要记得释放掉handle,如defer handle.Close()
创建数据包源
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
- 第一个参数为
OpenLive
的返回值,指向Handle类型的指针变量handle。 - 第二个参数为
handle.LinkType()
此参数默认是以太网链路,一般我们抓包,也是从2层以太网链路上抓取。
读取数据包
//packetSource.Packets()是个channel类型,此处是从channel类型的数据通道中持续的读取网络数据包
for packet := range packetSource.Packets() {
// Process packet here
fmt.Println(packet)
}
设置过滤器
只抓取tcp协议80端口的数据
过滤语句跟tcpdump差不多
package main
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/pcap"
"log"
"time"
)
var (
device = "enp0s3"
timeout = -1 * time.Second
promiscuous = false
snapshot_len int32 = 1024
err error
handle *pcap.Handle
)
func main() {
// Open device
handle, err = pcap.OpenLive(device, snapshot_len, promiscuous, timeout)
if err != nil {
log.Fatal(err)
}
defer handle.Close()
// 添加过滤条件
err = handle.SetBPFFilter("tcp and port 80")
if err != nil {
log.Fatal(err)
}
// Use the handle as a packet source to process all packets
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
// Process packet here
fmt.Println(packet)
fmt.Println("==========================================================================")
}
}
使用 handle.SetBPFFilter()
方法来设置过滤条件
抓取结果保存为pcap格式文件
要写一个pcap格式的文件,我们必须使用 gapacket/pcapgo
包。
这是一个Writer接口和两个有用的函数: WriteFileHeader()
和 WritePacket()
package main
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"github.com/google/gopacket/pcapgo"
"os"
"time"
)
var (
deviceName = "enp0s3"
promiscuous bool = false
err error
timeout time.Duration = -1 * time.Second
handle *pcap.Handle
packetCount int = 0
)
func main() {
// 打开输出的pcap文件,并写入头部信息
// "test.pcap" 是输出文件的路径
f, _ := os.Create("test.pcap")
// 创建一个pcap格式的写入器
w := pcapgo.NewWriter(f)
// 设置pcap文件头信息,参数分别为最大捕获长度和链路层类型
w.WriteFileHeader(1024, layers.LinkTypeEthernet)
// 确保在函数结束时关闭文件
defer f.Close()
// 打开指定设备用于数据包捕获
// 参数分别为设备名称、最大捕获长度、是否开启混杂模式以及超时时间
handle, err = pcap.OpenLive(deviceName, 1024, promiscuous, timeout)
if err != nil {
// 如果打开设备失败,则打印错误信息并退出程序
fmt.Printf("Error opening device %s: %v", deviceName, err)
os.Exit(1)
}
// 确保在函数结束时关闭设备句柄
defer handle.Close()
// 创建一个新的数据包源对象,用于从设备中读取数据包
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
// 循环读取数据包
for packet := range packetSource.Packets() {
// 处理数据包
fmt.Println(packet)
// 将数据包写入到之前创建的pcap文件中
w.WritePacket(packet.Metadata().CaptureInfo, packet.Data())
// 记录已处理的数据包数量
packetCount++
// 当捕获到100个数据包后停止捕获
if packetCount > 100 {
break
}
}
}
上面程序所做的事情:
- 创建一个名为test.pcap的文件并写入PCAP文件头。
- 使用pcap库打开网络设备进行抓包。
- 从设备读取数据包,并将每个数据包包内容打印到控制台。
- 将数据包写入之前创建的test.pacp文件中。
- 控制只捕获100个数据包后停止。
这里用到了两个方法
WriteFileHeader 方法
WriteFileHeader 方法是用于设置 pcap 文件的头部信息。表明单包最大捕获长度和链路层类型
函数签名
func (w *Writer) WriteFileHeader(snaplen uint32, linktype layers.LinkType) error
参数
shotLen (uint32)
:表示最大捕获长度。当捕获数据包时,如果数据包的长度超过这个值,将会截断。在这个例子中,1024 表示最大捕获长度为 1024 字节。linktype (layers.LinkType)
:表示链路层类型。不同的网络接口有不同的链路层类型,常见的有 Ethernet、PPP、Loopback 等。layers.LinkTypeEthernet 表示 Ethernet 链路层类型。
上面传入的 layers.LinkTypeEthernet
含义
layers.LinkTypeEthernet
是一个枚举值,表示数据包是在 Ethernet 链路上捕获的。Ethernet 是最常见的局域网(LAN)技术之一,广泛应用于家庭、企业和数据中心网络。
WritePacket 方法
WritePacket
方法是 pcapgo.Writer
接口的一部分,用于将捕获的数据包写入到一个 pcap 格式的文件中。
此方法通常被用来记录网络数据包,以便后续进行分析或调试。
函数签名
func (w *Writer) WritePacket(ci gopacket.CaptureInfo, data []byte) error
参数
ci (gopacket.CaptureInfo)
:这是数据包的元数据,包含了关于数据包的一些信息,比如捕获时间戳、数据包的实际长度和捕获长度等。data ([]byte)
:这是实际的数据包内容,即在网络上传输的原始字节序列。
用法
当你想要将一个数据包保存到一个 pcap 文件中时,可以调用 WritePacket 方法。
例如,在上面提供的代码片段中,packet.Metadata().CaptureInfo
提供了捕获信息,而 packet.Data() 则提供了数据包的内容。
这两个参数一起传递给 WritePacket 方法,使得该数据包能够被正确地写入到 pcap 文件中。
读取pcap格式文件来查看分析网络数据包
不用打开一个设备进行实时捕获,也可以打开pcap文件进行离线检查。可以使用tcpdump创建要使用的测试文件。
抓包
tcpdump -i any host 10.0.2.26 -w test1.pcap
go读取pcap文件
package main
// Use tcpdump to create a test file
// tcpdump -w test.pcap
// or use the example above for writing pcap files
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/pcap"
"log"
)
var (
pcapFile string = "test1.pcap"
handle *pcap.Handle
err error
)
func main() {
// 打开PCAP文件,而不是设备
handle, err = pcap.OpenOffline(pcapFile)
// 如果打开文件时发生错误,记录错误并终止程序
if err != nil {
log.Fatal(err)
}
// 确保在函数结束时关闭文件句柄
defer handle.Close()
// 创建一个新的包源,用于从文件中读取包
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
// 遍历文件中的所有包
for packet := range packetSource.Packets() {
// 处理每个包,这里简单地打印出来
fmt.Println(packet)
}
}
结果是一样的
解码抓取的数据
我们可以使用原始数据包,并且可将其转换为已知格式。
它与不同的层兼容,所以我们可以轻松访问以太网,IP和TCP层。
layers包是Go库中新增的,在底层pcap库中不可用。
这是一个令人难以置信的有用的包,它是gopacket库的一部分。
它允许我们容易地识别包是否包含特定类型的层。
该代码示例将显示如何使用layers包来查看数据包是以太网,IP和TCP,并轻松访问这些头文件中的元素。
查找有效载荷取决于所涉及的所有层。每个协议是不同的,必须相应地计算。
这就是layer包的魅力所在。
gopacket的作者花了时间为诸如以太网,IP,UDP和TCP等众多已知层创建了相应类型。有效载荷是应用层的一部分。
package main
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"log"
"strings"
"time"
)
var (
device string = "enp0s3"
snapshotLen int32 = 1024
promiscuous bool = false
err error
timeout time.Duration = -1 * time.Second
handle *pcap.Handle
)
// 主函数
func main() {
// 打开网络设备
handle, err = pcap.OpenLive(device, snapshotLen, promiscuous, timeout)
if err != nil {
log.Fatal(err)
}
defer handle.Close()
// 创建一个新的PacketSource
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
// 持续读取并处理PacketSource中的数据包
for packet := range packetSource.Packets() {
printPacketInfo(packet)
}
}
// printPacketInfo函数解析并打印数据包信息
// 参数: packet gopacket.Packet - 待解析的数据包
func printPacketInfo(packet gopacket.Packet) {
// 检查数据包是否为以太网数据包
ethernetLayer := packet.Layer(layers.LayerTypeEthernet)
if ethernetLayer != nil {
fmt.Println("检测到以太网层。")
ethernetPacket, _ := ethernetLayer.(*layers.Ethernet)
fmt.Println("源MAC地址: ", ethernetPacket.SrcMAC)
fmt.Println("目的MAC地址: ", ethernetPacket.DstMAC)
fmt.Println("以太网类型: ", ethernetPacket.EthernetType)
fmt.Println()
}
// 检查数据包是否为IPv4数据包
ipLayer := packet.Layer(layers.LayerTypeIPv4)
if ipLayer != nil {
fmt.Println("检测到IPv4层。")
ip, _ := ipLayer.(*layers.IPv4)
// IP层变量:
// Version (版本,可能是4或6)
// IHL (IP头长度,以32位字为单位)
// TOS (服务类型),Length (总长度),Id (标识符),Flags (标志位),FragOffset (片段偏移),TTL (生存时间),Protocol (协议,如TCP)
// Checksum (校验和),SrcIP (源IP地址),DstIP (目标IP地址)
fmt.Printf("从 %s 到 %s\n", ip.SrcIP, ip.DstIP)
fmt.Println("协议: ", ip.Protocol)
fmt.Println()
}
// 检查数据包是否为IPv6数据包
ipv6Layer := packet.Layer(layers.LayerTypeIPv6)
if ipv6Layer != nil {
fmt.Println("检测到IPv6层。")
ipv6, _ := ipv6Layer.(*layers.IPv6)
fmt.Printf("从 %s 到 %s\n", ipv6.SrcIP, ipv6.DstIP)
fmt.Println("协议: ", ipv6.NextHeader)
fmt.Println()
}
// 检查数据包是否为TCP数据包
tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer != nil {
fmt.Println("检测到TCP层。")
tcp, _ := tcpLayer.(*layers.TCP)
// TCP层变量:
// SrcPort (源端口号)、DstPort (目标端口号)、Seq (序列号)、Ack (确认号)、DataOffset (数据偏移量)、Window (窗口大小)、Checksum (校验和)、Urgent (紧急指针)
// 布尔标志位:FIN (结束标志)、SYN (同步标志)、RST (重置标志)、PSH (推送标志)、ACK (确认标志)、URG (紧急标志)、ECE (ECN回显标志)、CWR (拥塞窗口减少标志)、NS (无状态标志)
fmt.Printf("从端口 %d 到 %d\n", tcp.SrcPort, tcp.DstPort)
fmt.Println("序列号: ", tcp.Seq)
fmt.Println("确认号: ", tcp.Ack)
fmt.Println("数据偏移量: ", tcp.DataOffset)
fmt.Println("窗口大小: ", tcp.Window)
fmt.Println()
}
// 检查数据包是否为UDP数据包
udpLayer := packet.Layer(layers.LayerTypeUDP)
if udpLayer != nil {
fmt.Println("检测到UDP层。")
udpPacket, ok := udpLayer.(*layers.UDP)
if !ok {
fmt.Println("无法转换为UDP层。")
return
}
fmt.Printf("从端口 %d 到 %d\n", udpPacket.SrcPort, udpPacket.DstPort)
fmt.Println("长度: ", udpPacket.Length)
fmt.Println("校验和: ", udpPacket.Checksum)
// 处理UDP负载
fmt.Println("UDP负载:")
fmt.Printf("%s\n", udpPacket.Payload)
// 可以在这里添加更多的逻辑来处理负载
fmt.Println()
}
// 打印数据包的所有层类型
fmt.Println("所有数据包层:")
for _, layer := range packet.Layers() {
fmt.Println("- ", layer.LayerType())
}
// 当遍历 packet.Layers() 时,如果列表中包含 Payload 层,则该 Payload 层等同于 applicationLayer。
// applicationLayer 包含了有效载荷。
// 检查并处理应用层负载
applicationLayer := packet.ApplicationLayer()
if applicationLayer != nil {
fmt.Println("找到应用层/负载。")
fmt.Printf("%s\n", applicationLayer.Payload())
// 在负载中查找HTTP字符串
if strings.Contains(string(applicationLayer.Payload()), "HTTP") {
fmt.Println("找到HTTP!")
}
}
// 检查解码过程中的错误
if err := packet.ErrorLayer(); err != nil {
fmt.Println("解码数据包的某部分时出错:", err)
}
}
上面的代码对各种层进行了解析,一般都是先断言数据包的类型,然后再根据不同的类型做不同的解析
而不同的类型可以解析出不同的变量属性
这个里间的介绍几种
IP层的可解析变量
Version
(版本):表示IP协议的版本,可能是4(IPv4)或6(IPv6)。IHL
(IP头长度):表示IP头的长度,以32位字为单位。TOS
(服务类型):表示服务质量的优先级和其他标记。Length
(总长度):表示整个IP数据包的总长度(包括头和数据)。Id
(标识符):用于标识属于同一数据流的不同数据包。Flags
(标志位):用于控制数据包的分片行为。FragOffset
(片段偏移):表示数据包分片的偏移量。TTL
(生存时间):表示数据包在网络中的最大生存时间(跳数)。Protocol
(协议):表示上层协议类型,如TCP、UDP等。Checksum
(校验和):用于检测IP头的完整性。SrcIP
(源IP地址):表示发送方的IP地址。DstIP
(目标IP地址):表示接收方的IP地址。
TCP层可解析变量
SrcPort
(源端口号):发送方的端口号。DstPort
(目标端口号):接收方的端口号。Seq
(序列号):TCP数据包中的序列号,用于标识数据包中的第一个字节。Ack
(确认号):TCP数据包中的确认号,用于标识接收到的最后一个字节的序号加一。DataOffset
(数据偏移量):TCP首部长度,通常表示为32位字的数量。Window
(窗口大小):接收方允许发送方发送的最大字节数。Checksum
(校验和):用于检测传输过程中数据包的完整性。Urgent
(紧急指针):指示紧急数据的结束位置。- 布尔标志位:
FIN
(结束标志):表示连接的一方希望关闭连接。SYN
(同步标志):用于建立连接时的三次握手。RST
(重置标志):用于重置连接。PSH
(推送标志):提示接收方立即传递数据给应用程序。ACK
(确认标志):表示确认号字段有效。URG
(紧急标志):表示紧急指针字段有效。ECE
(ECN回显标志):表示接收方收到了带有ECN-Capable标志的报文。CWR
(拥塞窗口减少标志):表示发送方已经响应了拥塞控制。NS
(无状态标志):用于无状态传输。
自定义层
自定义层有助于实现当前不包含在gopacket layers包中的协议。
package main
import (
"fmt"
"github.com/google/gopacket"
)
// CustomLayer 创建自定义层数据结构,并实现Layer接口中的函数LayerType()、LayerContents()、LayerPayload()
type CustomLayer struct {
// This layer just has two bytes at the front
SomeByte byte
AnotherByte byte
restOfData []byte
}
// CustomLayerType 注册自定义层类型,然后我们才可以使用它
// 第一个参数是ID. 自定义层使用大于2000的数字,它必须是唯一的
var CustomLayerType = gopacket.RegisterLayerType(
2001,
gopacket.LayerTypeMetadata{
"CustomLayerType",
gopacket.DecodeFunc(decodeCustomLayer),
},
)
// LayerType 自定义层实现LayerType
func (l CustomLayer) LayerType() gopacket.LayerType {
return CustomLayerType
}
// LayerContents 自定义层实现LayerContents
func (l CustomLayer) LayerContents() []byte {
return []byte{l.SomeByte, l.AnotherByte}
}
// LayerPayload 自定义层实现LayerPayload
func (l CustomLayer) LayerPayload() []byte {
return l.restOfData
}
// 实现自定义的解码函数
func decodeCustomLayer(data []byte, p gopacket.PacketBuilder) error {
p.AddLayer(&CustomLayer{data[0], data[1], data[2:]})
return p.NextDecoder(gopacket.LayerTypePayload)
}
func main() {
rawBytes := []byte{0xF0, 0x0F, 65, 65, 66, 67, 68}
packet := gopacket.NewPacket(
rawBytes,
CustomLayerType,
gopacket.Default,
)
fmt.Println("Created packet out of raw bytes.")
fmt.Println(packet)
// Decode the packet as our custom layer
customLayer := packet.Layer(CustomLayerType)
if customLayer != nil {
fmt.Println("Packet was successfully decoded with custom layer decoder.")
customLayerContent, _ := customLayer.(*CustomLayer)
// Now we can access the elements of the custom struct
fmt.Println("Payload: ", customLayerContent.LayerPayload())
fmt.Println("SomeByte element:", customLayerContent.SomeByte)
fmt.Println("AnotherByte element:", customLayerContent.AnotherByte)
}
}
结合上述代码可知,实现自定义的层需要3步:
- 创建自定义层的结构体,并实现Layer接口中的函数LayerType()、LayerContents()、LayerPayload()
- 按照解码函数签名来实现自定义解码函数,名称可自行命名。
解码函数签名如下:
type DecodeFunc func([]byte, PacketBuilder) error
- 使用gopacket.RegisterLayerType函数来注册自定义层
更快地解码数据包
如果我们知道我们要预期的得到的层,我们可以使用现有的结构来存储分组信息,而不是为每个需要时间和内存的分组创建新的结构。
使用DecodingLayerParser更快。就像编组和解组数据一样。
package main
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"log"
"time"
)
var (
device string = "enp0s3"
snapshot_len int32 = 1024
promiscuous bool = false
err error
timeout time.Duration = -1 * time.Second
handle *pcap.Handle
// Will reuse these for each packet
ethLayer layers.Ethernet
ipLayer layers.IPv4
tcpLayer layers.TCP
)
func main() {
// Open device
handle, err = pcap.OpenLive(device, snapshot_len, promiscuous, timeout)
if err != nil {
log.Fatal(err)
}
defer handle.Close()
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
parser := gopacket.NewDecodingLayerParser(
layers.LayerTypeEthernet,
ðLayer,
&ipLayer,
&tcpLayer,
)
foundLayerTypes := []gopacket.LayerType{}
err := parser.DecodeLayers(packet.Data(), &foundLayerTypes)
if err != nil {
fmt.Println("Trouble decoding layers: ", err)
}
for _, layerType := range foundLayerTypes {
if layerType == layers.LayerTypeIPv4 {
fmt.Println("IPv4: ", ipLayer.SrcIP, "->", ipLayer.DstIP)
}
if layerType == layers.LayerTypeTCP {
fmt.Println("TCP Port: ", tcpLayer.SrcPort, "->", tcpLayer.DstPort)
fmt.Println("TCP SYN:", tcpLayer.SYN, " | ACK:", tcpLayer.ACK)
}
}
}
}
上面的就是当我们确定了数据包的结构是物理链路层、ip层(ipv4)最后是tcp层,就可以直接使用 gopacket.NewDecodingLayerParser
来解码
本文来自博客园,作者:厚礼蝎,转载请注明原文链接:https://www.cnblogs.com/guangdelw/p/18402767