安装 gopacket

gopacket 依赖 libpcap,所以需要先安装 libpcap。编译需要开启 CGO。

yum install libpcap-devel      # CentOS
apt install libpcap-dev        # Ubuntu
apk add libpcap-dev            # Alpine

export CGO_ENABLED=1           # 开启 CGO
go get github.com/google/gopacket

读取 pcap 文件

package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"time"

	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
)

func main() {
	// 获取命令行参数
	pcapfile := flag.String("f", "en0.pcap", "pcap file")
	flag.Parse()

	// 读取 pcap 文件
	handle, err := pcap.OpenOffline(*pcapfile)
	if err != nil {
		log.Fatal(err)
	}
	defer handle.Close()

	packetSources := gopacket.NewPacketSource(handle, handle.LinkType())
	for packet := range packetSources.Packets() {
		// 处理每个数据包
		fmt.Println(packet)
	}
}

解析数据包

自定义 Packet 结构体

这里我们定义了一个 Packet 结构体,它嵌入了 gopacket.Packet 接口,这样我们的 Packet 结构体就继承了 gopacket.Packet 的所有方法。同时,我们提供了一个 NewPacket 函数来创建 Packet 实例。

// Packet 自定义 Packet 结构体
type Packet struct {
	gopacket.Packet
}

// NewPacket 自定义Packet结构体
func NewPacket(packet gopacket.Packet) *Packet {
	return &Packet{
		Packet: packet,
	}
}

解析时间

这个方法用于获取数据包的时间戳,直接从数据包的元数据中获取。

// ParseTime 解析时间
func (p *Packet) ParseTime() time.Time {
	return p.Metadata().Timestamp
}

解析 MAC 地址

这个方法用于解析以太网帧中的源 MAC 地址和目的 MAC 地址。

// ParseMac 解析 MAC 地址
func (p *Packet) ParseMac() (srcMac, dstMac net.HardwareAddr) {
	if ethLayer := p.Layer(layers.LayerTypeEthernet); ethLayer != nil {
		if eth, ok := ethLayer.(*layers.Ethernet); ok {
			srcMac = eth.SrcMAC
			dstMac = eth.DstMAC
		}
	}
	return
}

解析 IP 地址

这个方法用于解析 IP 层中的源 IP 地址和目的 IP 地址。

// ParseIP 解析 IP 地址
func (p *Packet) ParseIP() (srcIP, dstIP net.IP) {
	if ip4Layer := p.Layer(layers.LayerTypeIPv4); ip4Layer != nil {
		if ip, ok := ip4Layer.(*layers.IPv4); ok {
			srcIP = ip.SrcIP
			dstIP = ip.DstIP
		}
	} else if ip6Layer := p.Layer(layers.LayerTypeIPv6); ip6Layer != nil {
		if ip, ok := ip6Layer.(*layers.IPv6); ok {
			srcIP = ip.SrcIP
			dstIP = ip.DstIP
		}
	}
	return
}

解析端口号

这个方法用于解析传输层中的源端口号和目的端口号。

// ParsePort 解析端口号
func (p *Packet) ParsePort() (srcPort, dstPort uint16) {
	if tcpLayer := p.Layer(layers.LayerTypeTCP); tcpLayer != nil {
		if tcp, ok := tcpLayer.(*layers.TCP); ok {
			srcPort = uint16(tcp.SrcPort)
			dstPort = uint16(tcp.DstPort)
		}
	} else if udpLayer := p.Layer(layers.LayerTypeUDP); udpLayer != nil {
		if udp, ok := udpLayer.(*layers.UDP); ok {
			srcPort = uint16(udp.SrcPort)
			dstPort = uint16(udp.DstPort)
		}
	}
	return
}

自定义字符串输出

这个方法用于自定义数据包的字符串输出,包含时间、源 MAC 地址、目的 MAC 地址、源 IP 地址、目的 IP 地址、源端口号和目的端口号。

// String 自定义字符串输出
func (p *Packet) String() string {
	timestamp := p.ParseTime().Format("2006-01-02T15:04:05.000Z07:00")
	srcMac, dstMac := p.ParseMac()
	srcIP, dstIP := p.ParseIP()
	srcPort, dstPort := p.ParsePort()
	return fmt.Sprintf(
		"%s %s:%d(%s) -> %s:%d(%s)",
		timestamp,
		srcIP, srcPort, srcMac,
		dstIP, dstPort, dstMac,
	)
}

处理数据包

这里我们使用 NewPacket 函数创建 Packet 实例,并调用 String 方法输出自定义的字符串。

packetSources := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSources.Packets() {
    // 处理每个数据包
    fmt.Println(NewPacket(packet))
}
tcpdump -i en0 -nn -c 10  -w en0.pcap  # 捕获10个数据包并保存到 en0.pcap 文件
go run main.go -f en0.pcap             # 从 en0.pcap 文件中读取数据包并解析
2025-09-04T18:47:21.834+08:00 192.168.111.103:548(00:11:32:fa:ca:27) -> 192.168.111.100:59937(7e:14:5f:df:88:65)
2025-09-04T18:47:21.834+08:00 192.168.111.103:548(00:11:32:fa:ca:27) -> 192.168.111.100:59937(7e:14:5f:df:88:65)
2025-09-04T18:47:21.834+08:00 192.168.111.103:548(00:11:32:fa:ca:27) -> 192.168.111.100:59937(7e:14:5f:df:88:65)
2025-09-04T18:47:21.834+08:00 192.168.111.103:548(00:11:32:fa:ca:27) -> 192.168.111.100:59937(7e:14:5f:df:88:65)
2025-09-04T18:47:21.834+08:00 192.168.111.103:548(00:11:32:fa:ca:27) -> 192.168.111.100:59937(7e:14:5f:df:88:65)
2025-09-04T18:47:21.834+08:00 192.168.111.103:548(00:11:32:fa:ca:27) -> 192.168.111.100:59937(7e:14:5f:df:88:65)
2025-09-04T18:47:21.834+08:00 192.168.111.103:548(00:11:32:fa:ca:27) -> 192.168.111.100:59937(7e:14:5f:df:88:65)
2025-09-04T18:47:21.834+08:00 192.168.111.103:548(00:11:32:fa:ca:27) -> 192.168.111.100:59937(7e:14:5f:df:88:65)
2025-09-04T18:47:21.834+08:00 192.168.111.103:548(00:11:32:fa:ca:27) -> 192.168.111.100:59937(7e:14:5f:df:88:65)
2025-09-04T18:47:21.836+08:00 192.168.111.103:548(00:11:32:fa:ca:27) -> 192.168.111.100:59937(7e:14:5f:df:88:65)