零依赖抓包:Go 实现 SIP/RTP 捕获与 pcap 导出

0 阅读2分钟

Wireshark 依赖 libpcap/WinPcap 驱动,现场部署常受阻。GBDoctor 用纯 Go 实现 SIP/RTP 捕获,并导出标准 pcap 文件。本文解析其实现原理与关键代码。

  1. 为什么不用 libpcap Windows 需安装 WinPcap/Npcap 驱动,管理员权限

Linux 需 root 或 CAP_NET_RAW

交叉编译复杂,CGO 依赖

现场用户往往没有安装权限

GBDoctor 的思路:不抓网卡原始流量,而是抓自己收发的 SIP/RTP 报文。因为诊断工具本身就是通信端点,它收发的报文已经覆盖了排查所需。

  1. 捕获点:UDP 读写封装 所有 SIP 报文都经过 net.UDPConn。在读写处埋点:

    type CaptureConn struct { conn *net.UDPConn pcap *PcapWriter }

func (c *CaptureConn) ReadFromUDP(b []byte) (int, *net.UDPAddr, error) { n, addr, err := c.conn.ReadFromUDP(b) if err == nil { c.pcap.WriteUDP(addr, c.conn.LocalAddr().(*net.UDPAddr), b[:n]) } return n, addr, err }

func (c *CaptureConn) WriteToUDP(b []byte, addr *net.UDPAddr) (int, error) { n, err := c.conn.WriteToUDP(b, addr) if err == nil { c.pcap.WriteUDP(c.conn.LocalAddr().(*net.UDPAddr), addr, b[:n]) } return n, err } 这样所有进出报文都被记录,且不需要 raw socket。

  1. pcap 文件格式 pcap 格式很简单:

24 字节全局头

每个包:16 字节包头 + 数据

全局头:

type PcapGlobalHeader struct { MagicNumber uint32 // 0xa1b2c3d4 VersionMajor uint16 // 2 VersionMinor uint16 // 4 ThisZone int32 // 0 SigFigs uint32 // 0 SnapLen uint32 // 65535 Network uint32 // 1 = Ethernet } 包记录:

type PcapPacketHeader struct { TsSec uint32 TsUsec uint32 InclLen uint32 OrigLen uint32 } 4. 构造 Ethernet/IP/UDP 头 要导出标准 pcap,需伪造链路层头。GBDoctor 用固定 MAC 和 IPv4 头:

func buildEthernet(srcMAC, dstMAC net.HardwareAddr, etherType uint16) []byte { buf := make([]byte, 14) copy(buf[0:6], dstMAC) copy(buf[6:12], srcMAC) binary.BigEndian.PutUint16(buf[12:14], etherType) return buf }

func buildIPv4(src, dst net.IP, protocol byte, payloadLen int) []byte { buf := make([]byte, 20) buf[0] = 0x45 buf[1] = 0x00 binary.BigEndian.PutUint16(buf[2:4], uint16(20+payloadLen)) // ... 省略 ID、Flags、TTL、Checksum copy(buf[12:16], src.To4()) copy(buf[16:20], dst.To4()) buf[9] = protocol // 17 = UDP return buf } UDP 头:

func buildUDP(srcPort, dstPort uint16, payload []byte) []byte { buf := make([]byte, 8+len(payload)) binary.BigEndian.PutUint16(buf[0:2], srcPort) binary.BigEndian.PutUint16(buf[2:4], dstPort) binary.BigEndian.PutUint16(buf[4:6], uint16(8+len(payload))) // checksum 可置 0(IPv4 UDP 可选) copy(buf[8:], payload) return buf } 5. RTP 捕获 RTP 是 UDP 载荷。GBDoctor 在 RTP 收发处同样埋点。为了区分 SIP 和 RTP,用端口范围:SIP 默认 5060,RTP 由 SDP 协商。

func (c *CaptureConn) WriteRTP(srcPort, dstPort uint16, rtp []byte) { ipHdr := buildIPv4(c.localIP, c.remoteIP, 17, 8+len(rtp)) udpHdr := buildUDP(srcPort, dstPort, rtp) ethHdr := buildEthernet(c.localMAC, c.remoteMAC, 0x0800) packet := append(ethHdr, append(ipHdr, udpHdr...)...) c.pcap.WritePacket(packet) } 6. 导出与 Wireshark 兼容 最终写出的 pcap 文件可直接用 Wireshark 打开,看到 SIP 和 RTP 报文。用户点击「下载 pcap」即可导出。

  1. 总结 零依赖抓包的关键:不抓网卡,抓自己收发的包。用纯 Go 构造 pcap 格式,既满足诊断需求,又避免驱动安装与权限问题。对于诊断工具而言,这是一个工程上更优的取舍。

项目地址 Gitee:suoten/GBDoctor GitHub:suoten/GBDoctor 搜不到?在 Gitee 或 GitHub 搜索:GBDoctor 或 suoten GBDoctor