Go gopacket 怎样把合成数据包写入离线PCAP文件
我第一次把测试用的合成包落成离线 PCAP 时,真正容易混淆的不是 Ethernet 或 UDP 字段,而是把“构造数据包”和“写入 PCAP 文件”当成了一件事。gopacket.SerializeLayers 只负责把各层变成字节,文件头、捕获时间和长度则要交给 pcapgo.Writer。
官方地址:https://pkg.go.dev/github.com/google/gopacket
最稳妥的做法是先按 Ethernet → IPv4 → UDP → Payload 组织可序列化层,使用FixLengths: true和ComputeChecksums: true生成包字节,再调用WriteFileHeader与WritePacket写入离线 PCAP。整个过程只落盘,不会把数据发送到网卡。
先把两段职责拆开
按我实际排查的顺序,链路可以分成两个边界。第一段是包模型:layers.Ethernet、layers.IPv4、layers.UDP 和 gopacket.Payload 描述“包里有什么”。第二段是文件容器:pcapgo.NewWriter 写文件头,WritePacket 把已经准备好的字节和 CaptureInfo 组成一条捕获记录。

这个拆分很有用:如果包字节不对,先看层顺序和序列化选项;如果文件无法被离线工具识别,再看文件头和捕获信息。不要一上来就把两个问题混在一起。
按层组织一个可重复的合成包
下面的例子只使用私有测试地址和文档保留端口,目的是生成一个可分析的 UDP 样本。UDP.SetNetworkLayerForChecksum 要在序列化前关联 IPv4 层,否则 UDP 校验和没有完整的网络层上下文。
package main
import (
"fmt"
"net"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
func buildPacket() ([]byte, error) {
eth := &layers.Ethernet{
SrcMAC: net.HardwareAddr{0x02, 0x00, 0x00, 0x00, 0x00, 0x01}, // 使用本地测试 MAC,避免借用真实设备地址
DstMAC: net.HardwareAddr{0x02, 0x00, 0x00, 0x00, 0x00, 0x02}, // 目标仅用于离线样本,不代表实际发送对象
EthernetType: layers.EthernetTypeIPv4,
}
ip4 := &layers.IPv4{
Version: 4,
TTL: 64, // 给离线分析保留常见的 IPv4 生存时间字段
Protocol: layers.IPProtocolUDP,
SrcIP: net.IPv4(192, 0, 2, 10),
DstIP: net.IPv4(198, 51, 100, 20),
}
udp := &layers.UDP{
SrcPort: 40000,
DstPort: 9999,
}
udp.SetNetworkLayerForChecksum(ip4) // 让 UDP 能依据 IPv4 伪首部计算校验和
payload := gopacket.Payload([]byte("offline-gopacket-sample")) // 负载只用于离线样本识别
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{
FixLengths: true, // 自动回填 IPv4 总长度和 UDP 长度
ComputeChecksums: true, // 根据序列化后的字段重新计算校验和
}
if err := gopacket.SerializeLayers(buf, opts, eth, ip4, udp, payload); err != nil {
return nil, fmt.Errorf("serialize packet layers: %w", err)
}
return append([]byte(nil), buf.Bytes()...), nil // 复制字节,避免后续复用 buffer 时内容被覆盖
}
这里没有手工填写 IPv4 的 IHL、总长度或 UDP 长度。它们属于由层内容推导出的字段,交给 FixLengths 更不容易因为改了负载却忘记同步长度。append 的复制也值得保留:SerializeLayers 会清空并复用写缓冲区,直接长期持有 buf.Bytes() 容易在下一次序列化后失效。
把字节写进离线 PCAP
包已经是字节后,才进入文件边界。新建 PCAP 时必须先写一次文件头,并且链路类型要和最外层一致;本例最外层是 Ethernet,所以使用 layers.LinkTypeEthernet。随后再用同一份字节填写捕获长度和原始长度。

func writePCAP(path string, packetData []byte) error {
f, err := os.Create(path)
if err != nil {
return fmt.Errorf("create pcap file: %w", err)
}
defer f.Close() // 无论写入成功或失败,都关闭文件句柄
writer := pcapgo.NewWriter(f)
if err := writer.WriteFileHeader(65535, layers.LinkTypeEthernet); err != nil {
return fmt.Errorf("write pcap header: %w", err)
}
now := time.Now()
info := gopacket.CaptureInfo{
Timestamp: now,
CaptureLength: len(packetData), // 本次文件实际保存的字节数
Length: len(packetData), // 合成包没有被截断,原始长度相同
}
if err := writer.WritePacket(info, packetData); err != nil {
return fmt.Errorf("write pcap packet: %w", err)
}
return nil
}
上面的函数需要补齐三个标准库导入:
import (
"fmt"
"os"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcapgo"
)
WriteFileHeader 只针对新文件调用一次;如果是追加到已经存在、且 snaplen 和链路类型一致的文件,处理方式不同,不能每次追加都重写文件头。对于单条或小批量合成包,先让每次写入都返回错误,再决定是否批量封装,排错更直接。
值班式排查:先看哪一个边界出问题
| 现象 | 优先检查 | 处理动作 |
|---|---|---|
| UDP 长度或校验和不符合预期 | 层顺序、SetNetworkLayerForChecksum、序列化选项 | 确认 UDP 紧跟在 IPv4 后,并打开 FixLengths 和 ComputeChecksums |
| 文件打不开或被识别成错误链路类型 | WriteFileHeader 的 snaplen 与 linktype | 新文件先写一次头,Ethernet 层对应 LinkTypeEthernet |
| 离线工具显示包被截断 | CaptureLength 与 Length | 合成包未截断时两者都填 len(packetData);有意截断时再分别记录 |
| 多条包写入后内容像被覆盖 | SerializeBuffer 的生命周期 | 写入前复制 buf.Bytes(),不要跨下一次序列化长期引用原切片 |
我会把排查顺序固定为“层 → 缓冲区 → 文件头 → 捕获信息”。这样每一层都有明确的处理动作和回滚点:如果序列化失败,不创建或替换最终文件;如果写包失败,保留错误并删除本次新建的临时文件,再决定是否重试。
什么时候换成 pcapng
如果目标只是给离线测试、协议解析或回归样本提供一条 Ethernet PCAP,pcapgo.Writer 足够简单。如果你需要多个接口、接口统计信息或更丰富的块结构,应重新评估 pcapgo.NewNgWriter 以及 pcapng 的写入接口。不要因为文件扩展名不同就只改后缀;容器的文件头和写入 API 也要一起切换。
官方 pcapgo 文档同时列出了 PCAP 的 Reader/Writer 和 pcapng 的 NgReader/NgWriter。本篇选择 PCAP,是为了让“合成层字节 → 单个离线容器”这条路径尽量短。
回滚和复盘清单
- 回滚:本次只写新文件,任何
WriteFileHeader或WritePacket错误都停止后续写入,不覆盖上一份样本。 - 回滚:批量生成时为每个文件使用临时路径,全部写完再交给上层替换,避免半个文件被下游消费。
- 告警:记录序列化错误、文件头错误、写包错误和实际字节数,不把它们合并成一个模糊的“生成失败”。
- 复盘:确认层顺序、链路类型、snaplen、捕获时间与长度字段是否在需求变更后仍然一致。
常见问题
写入 PCAP 会不会把合成包发到真实网络?
不会。SerializeLayers 只生成内存字节,pcapgo.Writer 只写文件;只有显式调用网卡发送相关 API 才会进入发送路径。
为什么不能只调用 WritePacket?
新建空文件时,PCAP 还没有文件头,必须先调用一次 WriteFileHeader。追加已有文件时则要确保原文件的 snaplen 和链路类型匹配。
CaptureLength 和 Length 应该总是相等吗?
未截断的合成包通常相等;如果你的流程主动截短了写入字节,CaptureLength 应记录实际保存长度,Length 记录原始包长度。
何时选 pcapng 而不是 PCAP?
需要多个接口、接口统计或 pcapng 块能力时再选 pcapng;单一 Ethernet 离线样本优先保持 PCAP 写入路径简单。
MySQL Version Tokens 怎样列出服务器当前令牌
- 上一篇
- MySQL Version Tokens 怎样列出服务器当前令牌
- 下一篇
- Redis RANDOMKEY 怎样从当前数据库抽取键名
-
- Golang · Go教程 | 8分钟前 |
- Go genetlink 怎样给只读请求封装通用消息头
- 302浏览 收藏
-
- Golang · Go教程 | 8分钟前 | go · Linux ·
- Go genetlink 怎样列出家族提供的多播组
- 379浏览 收藏
-
- Golang · Go教程 | 11分钟前 |
- Go genetlink 怎样查询Generic Netlink家族编号
- 186浏览 收藏
-
- Golang · Go教程 | 30分钟前 |
- Go netlink 怎样解码多部分Netlink响应
- 172浏览 收藏
-
- Golang · Go教程 | 33分钟前 |
- Go netlink 怎样用属性编码器封装嵌套字段
- 456浏览 收藏
-
- Golang · Go教程 | 55分钟前 |
- Go gopacket 怎样从已有数据包提取TCP层
- 144浏览 收藏
-
- Golang · Go教程 | 57分钟前 |
- Go gopacket 怎样解码内存中的以太网数据帧
- 217浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go gofpdf 怎样用MultiCell绘制自动折行文本
- 202浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go gofpdf 怎样嵌入UTF8字体输出中文PDF
- 473浏览 收藏
-
- Golang · Go教程 | 1小时前 | go ·
- Go go-echarts 怎样为图表配置缩放组件
- 466浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 802次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 793次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 881次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 851次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 682次使用
-
- Java 性能优化上线清单:从定位、改造到灰度发布
- 2026-06-11 860浏览
-
- Spring Boot 压测验证:Gatling、JMeter 与性能回归门禁
- 2026-06-11 843浏览
-
- Java NMT 非堆内存排查:Direct Buffer、线程栈与 Metaspace 分析
- 2026-06-11 826浏览
-
- Spring Boot 容器内存优化:JVM 堆、非堆与 MaxRAMPercentage
- 2026-06-11 809浏览
-
- Tomcat 连接与线程参数调优:maxThreads、acceptCount 与 KeepAlive
- 2026-06-11 792浏览

