
Go 如何用 pcap 抓包做旁路监听
Go 本身不内置抓包能力,必须依赖 libpcap(Linux/macOS)或 Npcap(Windows),所以第一步不是写代码,而是确认底层抓包库已就位。常见错误是直接 go run 就报 undefined: pcap.OpenLive——其实是 github.com/google/gopacket/pcap 没装好,或系统缺 libpcap-dev(Ubuntu)/libpcap-devel(CentOS)/brew install libpcap(macOS)。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
go get github.com/google/gopacket/pcap,别用旧版github.com/akrennmair/gopcap - 抓包前先用
pcap.FindAllDevs()看接口列表,别硬写"eth0";容器里可能只有"br-xxxx"或"docker0" - 设置
promisc=true,但注意:交换机端口镜像(SPAN)下无需混杂模式,硬开反而增加内核负担 - 超时设为
100 * time.Millisecond,太长会卡住读取,太短(如1ms)导致 CPU 空转
gopacket 解析 TCP 流时丢包或乱序怎么办
镜像流量常含重复、失序、重传包,gopacket 默认按单包解析,不会自动重组 TCP 流。你看到的 TCP 层字段(如 Seq, Ack)是对的,但应用层数据(HTTP/HTTPS 载荷)可能被切在多个包里,直接取 layer.LayerContents() 只能得到碎片。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
gopacket/tcpassembly+tcpassembly.StreamFactory组装流,不是所有场景都需要——只分析 DNS/ICMP 这类无连接协议时可跳过 - 每个流需独立分配
tcpassembly.Assembler实例,共享 assembler 会导致流间污染 - 注意内存:组装器默认缓存 1MB/流,高并发镜像下易 OOM,可通过
assembler.MaxBufferedPagesPerConnection限制 - HTTPS 流量即使解密成功,也只到 TLS 记录层;真正 HTTP 内容仍需解析
application_data,不能靠http.Decode直接喂原始 TCP payload
镜像流量中如何过滤出目标服务(如 80/443)并避开干扰
旁路镜像通常是全端口流量,但你只关心某几台后端的请求。如果用 Go 全量解析再 if 判端口,CPU 和 GC 压力极大。更稳的方式是在抓包层就过滤,把无效包挡在内核外。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
pcap.CompileFilter写 BPF 过滤器,例如只抓目标 IP+端口:"host 192.168.1.100 and (port 80 or port 443)" - 避免用
portrange 1-1000这类宽泛表达,BPF 编译慢且匹配效率低 - 注意方向:镜像口收到的是双向流量,
src port 443是响应包,dst port 443是请求包,按需选择 - 若目标服务用非标端口(如 8443),别漏加;K8s Service ClusterIP 流量走
10.96.0.0/12,BPF 中写net 10.96.0.0/12比逐个 IP 更可靠
Go 程序跑在容器里怎么拿到宿主机镜像口
容器默认网络命名空间隔离,pcap.FindAllDevs() 返回的通常是 lo 或 eth0(容器虚拟网卡),根本看不到物理口或镜像口(如 mirror0)。强行用 --network=host 有安全风险,且无法限制带宽。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 启动容器时加
--cap-add=NET_RAW --cap-add=NET_ADMIN,再挂载宿主机的/dev/bpf(eBPF 场景)或确保libpcap能访问/sys/class/net/ - 更稳妥做法:在宿主机起一个轻量代理(如用
afpacket抓包 + Unix Socket 转发),Go 容器只连 socket,规避权限和设备可见性问题 - 若用 Docker,镜像口需提前
ip link set dev mirror0 up,容器内执行ip link看不到它,不代表宿主机没启用 - K8s 场景优先考虑 CNI 插件(如 Cilium 的
hubble)导出 flow 数据,比自己抓包更稳定
真实环境里,BPF 过滤器写错、TCP 组装缓冲溢出、容器 Capabilities 漏配——这三个点占了 80% 的调试时间。别急着写分析逻辑,先让包稳稳进到 PacketSource.NextPacket() 里。










