背景

在网络问题排查、协议分析或回放测试中,我们经常需要从 pcap/cap 文件中提取报文数据。

但很多 Java 方案会遇到这些问题:

  • pcap4j / jNetPcapwpcap.dll not found
  • ❌ 强依赖 Npcap / WinPcap(部署复杂)
  • ❌ 返回的是整包(Ethernet/IP/UDP 头全在),而不是 Wireshark 里的 UDP payload
  • ❌ 只想要 byte[],却被迫引入一堆协议对象

目标很明确:

用 Java 直接读取 .pcap 文件,
返回 List<byte[]>
每个 byte[] = Wireshark 中看到的 UDP Payload


为什么不用 pcap4j / jNetPcap?

1️⃣ DLL 问题

  • pcap4jjNetPcap 本质是 抓包库
  • 即使只读文件,也可能触发本地库加载
  • Windows 下必然遇到:
Native library (win32-x86-64/wpcap.dll) not found

2️⃣ 过度抽象

  • 返回的是 Packet / IpPacket / UdpPacket
  • 你最终还是要 .getPayload().getRawData()
  • 却承担了巨大的依赖成本

👉 如果你只想离线分析 pcap 文件,完全没必要。


正确思路:直接解析 pcap 文件结构

pcap 文件结构(libpcap)

+-------------------+
| Global Header     | 24 bytes
+-------------------+
| Packet Header     | 16 bytes
+-------------------+
| Packet Data       | incl_len bytes
+-------------------+
| Packet Header     |
+-------------------+
| Packet Data       |
+-------------------+

关键点:

  • 时间戳、抓包长度都在 Packet Header
  • 真正的报文内容在 Packet Data
  • Packet Data = Ethernet + IP + UDP + Payload

如何得到 Wireshark 里的 UDP Payload?

需要手动完成以下步骤:

  1. 跳过 pcap 头
  2. 解析 Ethernet Header
  3. 处理 VLAN Tag(可选)
  4. 解析 IPv4 / IPv6 Header
  5. 判断是否是 UDP(protocol = 17)
  6. 跳过 UDP Header(8 字节)
  7. 剩余部分 = UDP Payload

这正是 Wireshark 在做的事情。


最终实现:返回 List<byte[]>(UDP Payload)

方法签名

public static List<byte[]> readUdpPayloads(String filePath)

特点

  • 纯 Java
  • ✅ 不依赖任何 DLL
  • ✅ 不依赖第三方库
  • ✅ 返回值和 Wireshark 完全一致
  • ✅ 支持:
    • Ethernet
    • VLAN(802.1Q / Q-in-Q)
    • IPv4
    • IPv6(含常见扩展头)

完整代码

⚠️ 仅支持 pcap(libpcap)
若是 pcapng,请用 Wireshark / tshark 转换

public static List<byte[]> readUdpPayloads(String filePath) throws IOException {
    List<byte[]> payloads = new ArrayList<>();

    try (DataInputStream dis = new DataInputStream(
            new BufferedInputStream(new FileInputStream(filePath)))) {

        byte[] globalHeader = new byte[24];
        dis.readFully(globalHeader);

        boolean bigEndian;
        int magic = ByteBuffer.wrap(globalHeader, 0, 4)
                .order(ByteOrder.BIG_ENDIAN).getInt();
        if (magic == 0xa1b2c3d4 || magic == 0xa1b23c4d) {
            bigEndian = true;
        } else if (Integer.reverseBytes(magic) == 0xa1b2c3d4
                || Integer.reverseBytes(magic) == 0xa1b23c4d) {
            bigEndian = false;
        } else {
            throw new IOException("Not a pcap file");
        }

        int linkType = readInt(globalHeader, 20, bigEndian);

        byte[] pktHeader = new byte[16];
        while (true) {
            try {
                dis.readFully(pktHeader);
            } catch (EOFException e) {
                break;
            }

            int inclLen = readInt(pktHeader, 8, bigEndian);
            byte[] packet = new byte[inclLen];
            dis.readFully(packet);

            byte[] payload = extractUdpPayload(packet, linkType);
            if (payload != null) {
                payloads.add(payload);
            }
        }
    }
    return payloads;
}

extractUdpPayload 内部实现略,为 Ethernet + IPv4/IPv6 + UDP 解析)


使用效果

Wireshark 中

User Datagram Protocol
    Source Port: 5060
    Destination Port: 5060
    Length: 248
    Checksum: 0x1234
    [UDP Payload]
        01 00 00 00 7f 45 4c 46 ...

Java 中

List<byte[]> payloads = readUdpPayloads("test.pcap");

byte[] udpPayload = payloads.get(0);
// udpPayload 内容 == Wireshark 里 UDP Payload

pcap vs pcapng

格式支持
pcap
pcapng❌(需转换)

转换方式(推荐)

tshark -F pcap -r input.pcapng -w output.pcap

总结

什么时候用这种方案?

  • ✔ 只做 离线分析
  • ✔ 只关心 UDP payload
  • ✔ 不想引入 JNI / DLL
  • ✔ 希望结果和 Wireshark 100%一致

什么时候不适合?

  • ❌ 实时抓包
  • ❌ 需要复杂协议解析(HTTP / TLS)

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注