go语言的抓包库--gopacket

什么是 gopacket

gopacket是google出品的golang三方库,质量还是靠的住,项目地址为:https://github.com/google/gopacket

gopacket到底是什么呢?是个抓取网络数据包的库

大家平时可能也会用一些抓包工具:

  • Windows平台下有 Wireshark 抓包工具,其底层抓包库是 npcap(以前是 winpcap );
  • Linux平台下有 Tcpdump,其抓包库是 libpcap

gopacket 库可以说是 libpcapnpcap 的go封装,提供了更方便的go语言操作接口。

libpcap

gopacket是基于libpcap(数据包捕获函数库)的,该库提供的C函数接口用于捕捉经过指定网络接口的数据包,该接口应该是被设为混杂模式。

著名的软件TCPDUMP就是在Libpcap的基础上开发而成的。

Libpcap提供的接口函数实现和封装了与数据包截获有关的过程。

Libpcap可以在绝大多数Linux平台上运行。

主要有以下功能:

  • 数据包捕获:捕获流经网卡的原始数据包
  • 自定义数据包发送:构造任何格式的原始数据包
  • 流量采集与统计:采集网络中的流量信息
  • 规则过滤:提供自带规则过滤功能,按需要选择过滤规则

环境准备

安装第三方库 libpcap或npcap

windows 平台

如果是在windows平台下,需要确保安装了npcap或winpcap

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() 返回的设备的Name
  • snaplen:每个数据包读取的最大长度
  • 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
        }
    }
}

上面程序所做的事情:

  1. 创建一个名为test.pcap的文件并写入PCAP文件头。
  2. 使用pcap库打开网络设备进行抓包。
  3. 从设备读取数据包,并将每个数据包包内容打印到控制台。
  4. 将数据包写入之前创建的test.pacp文件中。
  5. 控制只捕获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步:

  1. 创建自定义层的结构体,并实现Layer接口中的函数LayerType()、LayerContents()、LayerPayload()
  2. 按照解码函数签名来实现自定义解码函数,名称可自行命名。
    解码函数签名如下:
    type DecodeFunc func([]byte, PacketBuilder) error
  3. 使用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,
			&ethLayer,
			&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 来解码

posted @ 2024-09-09 00:04  厚礼蝎  阅读(298)  评论(0编辑  收藏  举报