You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何为不同数据包设置不同的抓包快照长度(snaplen)

如何为不同数据包设置不同的抓包快照长度(snaplen)

你遇到的这个需求确实很常见——高带宽链路上既要抓全关键包做协议分析,又要通过限制大部分包的快照长度来保证抓包吞吐量,避免丢包。可惜tcpdump、dumpcap、tshark这些常用工具确实没有直接提供“可变snaplen”的配置选项,但你注意到的BPF过滤返回值控制snaplen的特性,正是解决这个问题的关键!

原理说明

其实libpcap(这些抓包工具底层依赖的库)允许BPF过滤程序通过返回值来控制单个包的快照长度:

  • 返回正数:表示对该包抓取指定长度的字节(就是你要的自定义snaplen)
  • 返回0:丢弃该包,不抓取
  • 返回负数:使用程序启动时设置的默认snaplen抓取

所以我们可以利用这个特性,编写自定义的BPF规则,针对不同类型的包返回不同的长度,实现可变snaplen的效果。

具体实现方法

方法1:编写自定义C程序(最灵活)

如果你有基础的C语言能力,直接用libpcap写一个小工具是最可控的方式。下面是一个示例程序,实现“抓取目标端口8080的TCP全量包,其他TCP包只抓前64字节,非TCP/IP包丢弃”的逻辑:

#include <pcap.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    char errbuf[PCAP_ERRBUF_SIZE];
    pcap_t *handle;
    struct bpf_program fp;
    // 自定义BPF字节码:判断IP/TCP协议,匹配目标端口8080返回1500,否则返回64
    u_int8_t bpf_code[] = {
        0x28, 0x00, 0x00, 0x00, 0x08, 0x00, // 检查以太网类型为IP(0x0800)
        0x15, 0x00, 0x08, 0x00, 0x08, 0x00,
        0x28, 0x00, 0x00, 0x00, 0x17, 0x00, // 检查IP协议为TCP(6)
        0x15, 0x00, 0x06, 0x00, 0x06, 0x00,
        0x40, 0x00, 0x00, 0x00, 0x14, 0x00, // 计算TCP目标端口偏移
        0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
        0x15, 0x02, 0x00, 0x00, 0xf0, 0x32, // 匹配目标端口8080(0x32f0)
        0x06, 0x00, 0x00, 0x00, 0xdc, 0x05, // 返回1500(全量抓取)
        0x06, 0x00, 0x00, 0x00, 0x40, 0x00, // 返回64(只抓前64字节)
        0x06, 0x00, 0x00, 0x00, 0x00, 0x00  // 其他包丢弃
    };
    bpf_u_int32 mask;
    bpf_u_int32 net;

    if (argc != 2) {
        fprintf(stderr, "用法: %s <网卡名>\n", argv[0]);
        return 1;
    }

    // 获取网卡网络信息
    if (pcap_lookupnet(argv[1], &net, &mask, errbuf) == -1) {
        fprintf(stderr, "无法获取网卡%s的子网掩码: %s\n", argv[1], errbuf);
        net = 0;
        mask = 0;
    }

    // 打开网卡,不设置默认snaplen(由BPF控制)
    handle = pcap_open_live(argv[1], BUFSIZ, 1, 1000, errbuf);
    if (handle == NULL) {
        fprintf(stderr, "无法打开网卡%s: %s\n", argv[1], errbuf);
        return 2;
    }

    // 加载自定义BPF过滤规则
    fp.bf_len = sizeof(bpf_code)/sizeof(bpf_code[0]);
    fp.bf_insns = (struct bpf_insn *)bpf_code;
    if (pcap_setfilter(handle, &fp) == -1) {
        fprintf(stderr, "无法加载过滤规则: %s\n", pcap_geterr(handle));
        pcap_close(handle);
        return 3;
    }

    printf("正在网卡%s上抓包...\n", argv[1]);
    // 将抓到的包写入文件(这里默认输出到stdout,你可以修改为写入pcap文件)
    pcap_loop(handle, 0, pcap_dump, pcap_open_dead(DLT_EN10MB, 1500));

    pcap_close(handle);
    return 0;
}

编译运行方法:

gcc -o variable_snaplen variable_snaplen.c -lpcap
sudo ./variable_snaplen eth0

你可以根据自己的需求修改BPF字节码,比如调整需要全量抓取的包规则(比如特定源IP、协议类型),或者修改不同包的snaplen值。

方法2:使用BPF编译器配合现有工具(无需写代码)

如果你不想写C程序,可以用bpfc(BPF编译器)编写BPF脚本,编译成字节码后,通过一些工具加载到抓包流程中。比如先写一个BPF脚本custom_filter.bpf

// 匹配IP包
if (ethertype == 0x0800) {
    // 匹配TCP包
    if (ip_proto == 6) {
        // 匹配目标端口8080
        if (tcp_dst == 8080) {
            return 1500; // 全量抓取
        } else {
            return 64; // 只抓前64字节
        }
    }
}
return 0; // 丢弃其他包

然后用bpfc编译成字节码:

bpfc custom_filter.bpf > custom_filter.o

之后可以用libpcap相关的工具加载这个字节码,不过这种方式的工具支持度不如自定义程序广泛,所以更推荐第一种方法。

注意事项

  • 确保你的BPF返回的snaplen值不超过链路的MTU,否则会被截断到MTU长度
  • 在10G/25G高带宽链路上,建议配合使用大的抓包缓冲区(比如在pcap_open_live中调整缓冲区大小),进一步减少丢包概率
  • 测试时可以先用tcpdump -d查看BPF规则的编译结果,确认逻辑符合预期

备注:内容来源于stack exchange,提问作者Evan

火山引擎 最新活动