资讯详情

网络嗅探器Sniffer源码实战:抓包、解析与过滤避坑指南

📅 2026/10/26 7:30:35 | 华诺云谱 👁 阅读
网络嗅探器Sniffer源码实战:抓包、解析与过滤避坑指南
简介一套基于VC实现的网络嗅探器Sniffer完整源代码工程面向网络编程初学者与安全攻防爱好者用于系统学习数据包捕获、过滤与协议解析的核心原理并能直接解决“想实现抓包却不知从何入手”的开发难题。压缩包共21个文件包括6个头文件、3个C源文件、1个资源脚本、1个工程配置文件vcproj/sln及7个图标资源整体仅82KB代码量精炼目录结构清晰便于逐文件研读。目前已有750人学习下载工程规模适中适合作为网络编程入门与抓包工具开发的参考资料。源码覆盖WinPCAP库的pcap_open_live、pcap_loop、pcap_setfilter等关键调用清晰演示IP、TCP/UDP头部解析以及捕包线程与MFC对话框界面的交互方式。通过研读工程结构和实现细节读者既能掌握网络嗅探的基本原理也能在此基础上二次开发定制符合自身需求的网络抓包工具。1. 网络嗅探器 Sniffer 源代码.zip 不是什么“直接能用”的工具而是抓包生态的入口网络嗅探器 Sniffer 源代码.zip 这个压缩包名字里最有价值的是“源代码”三个字。它不是一款双击就能打开的图形抓包工具而是需要你编译、运行、再对着协议栈折腾的一堆代码。网络嗅探器的核心能力并不花哨打开网卡、把数据包从内核拿进用户态、解析出 IP/MAC/端口再把过滤后的结果展示或落盘。读透这份源码你能获得的不只是一个 sniffer 二进制而是如何与 libpcap 打交道、如何做协议字段偏移计算、如何处理持续抓包的性能问题。适合打算写网络排查工具、做协议分析或正在学计算机网络的人。请先确认你手头的网络环境是自有设备或明确授权区域再开始动手。2. 读懂 Sniffer 源码先看这三个模块抓包、解析、过滤2.1 抓包核心从网卡到应用层的数据流解压之后先不要急着 make先把目录布局过一遍。常见的 C 语言 Sniffer 会把抓包逻辑放在 capture.c 或者 sniff.c 文件里跑起来之后你会看到它调用了 libpcap。我一般会先打开这个文件因为整条数据流的起点就在这里。libpcap 在上层提供 pcap_open_live在 Linux 底层绑定 AF_PACKET socket在 Windows 则对应 npcap。理解了这一层后面遇到的权限问题和网卡问题都能定位到这一层。下面是一段最小抓包循环很多 Sniffer 源码的骨架都和它差不多只是外包了命令行参数和输出格式化#include pcap.h #include stdio.h static void on_packet(u_char *args, const struct pcap_pkthdr *hdr, const u_char *packet) { printf(captured %u bytes, ts%ld.%06ld\n, hdr-len, (long)hdr-ts.tv_sec, (long)hdr-ts.tv_usec); } int main(int argc, char **argv) { char errbuf[PCAP_ERRBUF_SIZE] {0}; pcap_t *handle; const char *dev eth0; if (argc 1) dev argv[1]; handle pcap_open_live(dev, 65535, 1, 1000, errbuf); if (!handle) { fputs(errbuf, stderr); return 1; } pcap_loop(handle, 5, on_packet, NULL); pcap_close(handle); return 0; }这段代码里最需要理解的是 pcap_open_live 的四个参数。第一个 dev 是网卡名Linux 下可以用 eth0、ens33、wlan0第二个 65535 是 snaplen表示每个包最多从内核拷贝多少字节到用户态65535 能覆盖绝大多数以太网帧不含 jumbo frame第三个 1 表示开启混杂模式也就是接收网卡上所有到达的数据包第四个 1000 是超时毫秒数意思是内核把包交给应用层前最多攒 1000 毫秒。pcap_loop的第二个参数 5 表示抓到 5 个包后返回非常适合拿来做第一次验证。这类源码里还有一个常见变体不用 pcap_loop而是自己循环调 pcap_next_ex。区别是 next_ex 不做回调更容易控制退出条件但流量上来时两者的处理方式差异很大。很多老代码会把 errbuf 的返回值忽略掉导致网卡打不开时只看到没有任何输出这是最早的踩坑点。建议你阅读源码时第一件事就是确认它有没有把 pcap_open_live 的 errbuf 打印出来。2.2 协议解析如何从二进制报文中拆出 IP 和端口抓包循环拿到的是一个 u_char 数组里面从头到尾是完整的链路层帧。代码要做的事情就是在这段内存上按协议头做偏移。以最常见的 Ethernet IP TCP 组合为例先是 14 字节以太网头接着是 20 字节 IP 头再往后才是 TCP 头。Sniffer 源码里通常会定义一组结构体来映射这些头比如struct sniff_ethernet { u_char ether_dhost[6]; u_char ether_shost[6]; u_short ether_type; } __attribute__((packed)); struct sniff_ip { u_char ip_vhl; u_char ip_tos; u_short ip_len; u_short ip_id; u_short ip_off; u_char ip_ttl; u_char ip_p; u_short ip_sum; struct in_addr ip_src, ip_dst; } __attribute__((packed));这段代码最重要的细节是那个__attribute__((packed))。如果没有它编译器会在 u_short 字段前做对齐比如在 ether_dhost[6] 之后按 2 字节对齐结构体内部会多出空洞。直接用结构体指针去解释网卡内存所有字段位置都会错位。很多 Sniffer 源码解析结果乱七八糟不是因为协议变复杂而是结构体没有紧凑排列。拿到结构体指针之后再取字段就有两种方式。一种是指针强转另一种是 memcpy。在线抓包场景里用指针强转更快也写得更简单const struct sniff_ethernet *eth (const struct sniff_ethernet *)packet; const struct sniff_ip *ip (const struct sniff_ip *)(packet sizeof(struct sniff_ethernet)); printf(src%s dst%s ether_type0x%04x\n, inet_ntoa(ip-ip_src), inet_ntoa(ip-ip_dst), ntohs(eth-ether_type));注意最后打印以太网类型时要先ntohs。以太网头的 type 字段是网络字节序0x0800 在内存里实际存成 0x00 0x08直接按主机序读出来就是 0x0008看起来像另一个协议。这类错误最容易出现在端口上。TCP 头里的源端口和目的端口都是 16 位同样要用 ntohs 转换。你如果看到明明访问 80 端口程序却打印出 20480不用怀疑抓包数据就是少了这一步。2.3 过滤规则BPF 表达式才是代码里的“隐形配置”Sniffer 源码里另一个决定行为的地方是过滤规则。libpcap 支持 BPF 表达式它是一段在内核里执行的字节码让网卡驱动或内核网络栈提前把不需要的包丢掉。相比把所有包都一网打尽、到用户态再判断BPF 能省掉大量无效的复制和内存占用。很多源码把过滤表达式写成常量字符串这是最死板的写法。下面这段是给 handle 加过滤规则的典型路径struct bpf_program bpf; const char *filter_expr ip and tcp port 80; if (pcap_compile(handle, bpf, filter_expr, 1, PCAP_NETMASK_UNKNOWN) -1) { fprintf(stderr, compile failed: %s\n, pcap_geterr(handle)); return 1; } if (pcap_setfilter(handle, bpf) -1) { fprintf(stderr, setfilter failed: %s\n, pcap_geterr(handle)); return 1; } pcap_freecode(bpf);pcap_compile 的第四参数 1 表示启用优化器第五参数是子网掩码很多代码用PCAP_NETMASK_UNKNOWN让 libpcap 自己判断能避免广播和组播过滤时因掩码错误导致的奇怪行为。pcap_setfilter 编译结束后要把 bpf 资源释放这个在源码里经常漏掉短程序也许没事长时间抓包就是内存持续增长。过滤表达式是整个 Sniffer 代码里最值得抽成配置的部分。我一般会把它做成命令行参数或配置文件而不是重新编译。比如用./sniffer -f host 192.168.1.1 and port 443这种形式让同一份二进制在不同场景下复用。如果你只在一个固定网段里调试写死也能跑但一旦要换环境就要回源码改字符串再重编非常浪费。过滤表达式属于逻辑配置应该纳入源代码管理这样版本历史里能追踪到规则演进。3. 把 zip 里的源码跑起来环境准备、编译与最小抓包验证3.1 解压与工程结构识别先别急着编译拿到网络嗅探器 Sniffer 源代码.zip 之后第一步是把它放到干净的目录里然后看压缩包内部结构而不是直接整个解压到当前目录。如果 zip 的根目录里散落着一堆 .c 和 .h解压出来会污染工作目录。我的习惯是先列出条目再创建独立目录unzip -l Sniffer_Source.zip unzip Sniffer_Source.zip -d sniffer_src cd sniffer_src find . -maxdepth 2 -type f | sortunzip -l会列出压缩包里的文件路径这一步能看出里面是单个工程根目录还是多层嵌套也能提前发现条目里是否有很长的路径或特殊字符。-d sniffer_src指定了解压目标目录避免文件散落。之后用 find 查看工程结构重点找 README、Makefile、CMakeLists.txt 以及 main.c/sniffer.c 这类入口文件。没有 README 的老代码并不少见那就按文件名猜有 sniff、capture、packet 字样的文件优先看。如果解压时报invalid zip archive: could not find EOCD先停下来。这个提示说明 zip 的结束记录找不到了常见原因是文件没下载完整或者只是改了扩展名的伪 zip。不要用unzip -u强行继续解出来的文件可能残缺后面编译时会冒出一堆诡异的语法错误。先回到原始来源重新下载核对文件大小。如果下载源没问题再用7z x Sniffer_Source.zip试试它对一些非标准 zip 的容错性比 unzip 好。3.2 Linux 上编译 libpcap 程序的最小 Makefile大多数 C 语言 Sniffer 源码依赖 libpcap。在 Debian/Ubuntu 系系统上先装编译工具链和开发库sudo apt-get update sudo apt-get install -y build-essential libpcap-devlibpcap-dev 提供 pcap.h 头文件和 libpcap.so 链接库。如果源码还用了 pthread、ncurses 之类的库再按需补装。接下来写一个最小 Makefile放在工程根目录CC gcc CFLAGS -Wall -O2 -g LDLIBS -lpcap all: sniffer sniffer: main.o $(CC) -o $ $^ $(LDLIBS) main.o: main.c $(CC) $(CFLAGS) -c $ clean: rm -f sniffer *.o这个 Makefile 里隐藏了一个链接顺序教训$(LDLIBS)必须在$^之后。如果把-lpcap放在 main.o 前面旧版链接器从左到右解析符号时遇到 pcap_open_live 时还没看到引用等到 main.o 出现再说找不到符号。为了兼容老工具链老老实实把库写在目标文件后面。-g保留调试信息抓包程序跑起来后如果段错误能直接用 gdb 看到是哪一行。Makefile 写好后直接make。如果报错行号指向 pcap_xxx 函数先确认安装的是 libpcap-dev 而不是只有 libpcap0.8。运行库只提供 .so不提供头文件编译期必然失败。3.3 第一次抓包权限、网卡与超时参数的坑编译成功后第一次运行就会遇到权限问题。libpcap 要从网卡读原始帧需要 CAP_NET_RAW 或 CAP_NET_ADMIN 能力。普通用户运行会直接报错常见做法是 sudosudo ./sniffer eth0如果网卡名不对程序不会报错只是 pcap_open_live 可能打不开设备或者打开了一个看不见流量的接口。先用ip -br link查可用网卡确认当前有线网卡是 eth0、ens33 这类名字无线网卡可能是 wlan0。也可以直接sudo tcpdump -D让系统列出 libpcap 能识别的设备清单。不想每次都用 sudo可以给二进制文件单独加网络抓包所需的能力位sudo setcap cap_net_raw,cap_net_admineip ./sniffer ./sniffer eth0setcap 只对 ELF 可执行文件有效对 shell 脚本无效。这个方式适合在调试阶段反复启动程序省掉 sudo 的密码输入。不过要注意 setcap 在一些文件系统上会被忽略比如部分 NFS 挂载目录或 squashfs 只读镜像。抓包循环启动后如果等了半天一个包都没打印除了网卡问题还要看你用的调用方式。pcap_open_live 里的 timeout 参数只是内核的等待周期并不是说每秒都会强制把包送到用户态。当链路负载很低时缓冲区里只有一个包内核也要等到 timeout 到才交出来。想更敏感可以用 pcap_create 加 pcap_activate 的方式单独设置缓冲区和超时pcap_t *handle pcap_create(dev, errbuf); pcap_set_snaplen(handle, 65535); pcap_set_promisc(handle, 1); pcap_set_timeout(handle, 500); pcap_set_buffer_size(handle, 4 * 1024 * 1024); pcap_activate(handle);pcap_open_live 其实就是上面这几个步骤的快捷封装但它把缓冲区大小留在了内部默认值。流量较高时默认缓冲区可能不够导致内核丢包。pcap_set_buffer_size 的参数单位是字节4MB 是一个比较稳的起点设得太大会增加内存占用太小则持续抓包时丢包率上升。超时从默认 1000 毫秒调到 500 毫秒能让包更快到达用户态代价是稍微增加 CPU 开销。4. 避坑Sniffer 源码常见编译和运行问题排查4.1 编译期undefined reference to pcap_xxx现象是链接时报一串undefined reference to pcap_open_live或者pcap_loop。编译阶段反而能过去因为头文件找得到函数声明正常链接阶段才暴露出没有引用到共享库。原因一般有两种。第一种是 Makefile 里没写-lpcap。第二种是写了但链接顺序不对。前面已经在 Makefile 里提到库要放在目标文件后面。还有一个隐蔽原因系统里同时装了 32 位和 64 位 libpcap而编译器默认走多库路径导致链接器拿到不匹配的库版本。可以先确认当前库路径ldconfig -p | grep libpcap如果输出里有多个路径优先/lib/x86_64-linux-gnu/libpcap.so再检查是否存在/usr/local/lib下的旧版 libpcap。出现这种情况直接在 Makefile 里用LDFLAGS-L/usr/local/lib指定路径。总之把链接行改成类似gcc -o sniffer main.o -lpcap的写法大多数 undefined reference 都能消失。4.2 运行期没有任何输出不是代码有问题现象是 sudo 运行后程序停在那里屏幕始终没有抓包回调打印。很多人会以为是过滤规则写错了或代码死循环其实多数是环境问题。先按三层排查。第一层网卡名是否正确。用tcpdump -D查看系统识别到的设备列表很多时候你看到的 ens33 是虚拟机网卡物理机上可能是 eth0、eno1 或无线网卡 wlan0。第二层网卡上确实有流量吗。开个终端持续 ping 一个网关或者在局域网里发广播包观察抓包程序是否开始输出。第三层混杂模式有没有意义。交换网络下网卡设置混杂模式后只能看到本机收发、广播和组播以及被交换机泛洪到本端口的少量帧其他主机的单播流量并不会主动复制过来。要做全网段流量分析必须依赖交换机端口镜像网络分流器这不是软件层面能突破的限制。这条血泪经验越早记住越好。4.3 协议解析错乱端口数字变成 20480 或 0x5000现象是代码写的是打印 80 端口运行结果却是 20480或者十六进制打印出0x5000。初看像抓到的包是加密的其实是网络字节序问题。TCP/IP 协议栈里多字节整数在网络上统一用大端表示而 x86 主机是小端。80 端口的十六进制是 0x0050网络上先发 0x00 再发 0x50小端机器直接读成 0x5000也就是十进制的 20480。解决的办法是统一使用 ntohs 和 ntohl。对 16 位端口号用ntohs(tcp-th_sport)对 IP 地址这种 32 位字用ntohl或直接交给 inet_ntoa 处理。还有一种极其容易翻车的情况IP 头长度不是固定的 20 字节它有一个 IHL 字段表示以 4 字节为单位的头长度。正确获取 TCP 头指针的方式是int ip_header_len (ip-ip_vhl 0x0F) * 4; const struct sniff_tcp *tcp (const struct sniff_tcp *) (packet sizeof(struct sniff_ethernet) ip_header_len);这里如果忘了处理 IHL遇到带选项字段的 IP 包TCP 头指针会偏移错位端口和标志位全部跟着乱。协议解析错误大多不是玄学就是偏移和字节序两件事没做对。4.4 素材包问题zip 文件损坏或带密码别硬刚现象是解压时提示invalid zip archive: could not find EOCD或者 unzip 要求输入密码。前者说明压缩包本身不完整后者说明压缩时做了保护。不管代码写得多好zip 打不开一切都白搭。原因是下载过程中文件截断是最常见的其次是源文件被论坛附件系统重新打包过。某些工具生成的 zip 会更依赖 ZIP64 扩展老版本 unzip 不支持就报错。解决方法是先跑unzip -t Sniffer_Source.zip校验完整性再用7z x Sniffer_Source.zip尝试解压。7-Zip 对 ZIP64 和非标准压缩方法的支持通常比 unzip 更宽容。如果最终还是报 EOCD 缺失不要急着用 zip -FF 修复那只是把损坏文件拼回来源码中间缺失部分不会自动恢复只能重新获取源包。带密码的 zip 没有太多后悔药可吃。暴力破解慢且不一定成功正规做法是回到压缩包的原始出处查看说明里的密码字段。很多老技术包会把密码写在发布页的显眼位置只是解压时容易忽略。5. 把 Sniffer 源码改成能用的工具协议过滤与流量统计的落地修改5.1 给源码加 HTTP 流量统计从端口过滤到 payload 识别学习型 Sniffer 源码通常只是打印几个字段真要用它排查问题就得加统计逻辑。我的做法是先加一个简单的 HTTP 流量计数看某个时段内 80 端口有多少包、多少字节。先用 BPF 把范围收窄再在回调里按端口累加int ip_header_len (ip-ip_vhl 0x0F) * 4; const struct sniff_tcp *tcp (const struct sniff_tcp *) (packet sizeof(struct sniff_ethernet) ip_header_len); if (ntohs(tcp-th_sport) 80 || ntohs(tcp-th_dport) 80) { http_packets; http_bytes hdr-len; }这段代码的思路是先在回调函数里找到 TCP 头再判断源端口或目的端口是否为 80。端口匹配在绝大多数场景够用但要注意如果服务跑在 8080 等非标准端口端口匹配就会漏。更稳的办法是直接看 payload 开头几个字节是否匹配GET、POST、HTTP/1.这些明文特征。抓包工具本身不解密 TLS所以对 HTTPS 流量只能数连接看不到请求内容。真正投入使用的工具不可能把统计变量全写在回调里因为回调会高频执行随便几条分支就可能吃掉大量 CPU。这个版本只能算验证思路。改成可用工具时把统计变量改成结构体指针由回调更新最后在主循环结束后统一打印。5.2 用 BPF 过滤只抓某端口和主机配置化而不是改代码源码里写死过滤规则是最影响复用性的设计。比如要调查一台主机与某台服务器的连接情况如果过滤表达式是常量每次都要改源码重编。我已经习惯把所有过滤参数收敛成命令行选项然后拼成 BPF 表达式再做编译char filter_expr[256]; snprintf(filter_expr, sizeof(filter_expr), host %s and tcp port %d, target_host, target_port); if (pcap_compile(handle, bpf, filter_expr, 1, PCAP_NETMASK_UNKNOWN) ! -1) pcap_setfilter(handle, bpf);这样运行时传./sniffer -h 192.168.1.10 -p 443就够了不用为了换一个 IP 重新编译。拼表达式时要注意输入校验避免把非数字内容传进端口号导致 BPF 编译失败。IPv6 场景需要单独拼ip6 host不能直接照抄 IPv4 的host关键字。过滤配置是最适合纳入源代码管理的部分但这里说的源代码管理不是把抓包结果入库而是把过滤规则默认写在配置模板里每次调整都有记录。你可以把表达式写成 conf 文件程序启动时读取这样现场排障只需要改配置文件不需要带编译环境。5.3 输出格式与日志轮转长时间抓包的内存和磁盘控制源码里如果只是 printf 打印到终端抓不了一会儿就会刷屏。要保存完整报文libpcap 提供了 pcap_dump 系列接口。把它们接到回调里就能直接写标准 pcap 文件pcap_dumper_t *dumper pcap_dump_open(handle, capture.pcap); pcap_loop(handle, -1, (pcap_handler)pcap_dump, (u_char *)dumper);这里的关键是 pcap_loop 的第二个参数设为 -1表示一直抓到进程退出或收到信号回调直接用 libpcap 内置的 pcap_dump它会按照 pcap 文件头格式把每个包连同时间戳写进文件。要注意 pcap_dump 写的是原始 pcap 格式不是 pcapng如果后续要用 Wireshark 同时看多接口最好生成 pcapng。pcap 文件没有内置轮转机制长期跑会占满磁盘。常见做法是用外部工具tcpdump -w cap.pcap -C 10 -W 20按 10MB 分卷保留最近 20 个文件。如果要在自己的 Sniffer 代码里实现可以在回调里用pcap_dump_ftell检查当前文件偏移超过阈值就关掉旧 dumper再开新文件。输出目录也要定期做时间戳后缀避免重启后覆盖历史样本。抓包工具跑到最后拼的不是抓包能力而是文件管理能力。6. 进阶用抓取的 pcap 文件离线验证 Sniffer 代码只要你开始改 Sniffer 源码很快就会遇到一个尴尬时刻对着真网卡调解析逻辑可线上流量不稳定想复现某个包要等半天。更麻烦的是在别人的网络环境里反复抓包既不礼貌也不安全。所以我把代码入口抽象成两路一路是 pcap_open_live 做在线抓包另一路是 pcap_open_offline 读取保存好的 pcap 文件。两者的调用方式几乎一致改起来很小pcap_t *handle; if (offline_file) { handle pcap_open_offline(offline_file, errbuf); } else { handle pcap_open_live(dev, 65535, 1, 1000, errbuf); }pcap_open_offline 返回的也是 pcap_t 指针之后注册过滤规则、调 pcap_loop、写 pcap_dump 的代码完全不用变。这意味着你可以用tcpdump -i eth0 -c 100 -w sample.pcap先抓一个样本再反复跑./sniffer -r sample.pcap每次修改解析逻辑后都对这个样本做回归比对。这个习惯帮我省了大量时间。第一次跑通在线抓包后我先抓了 100 个混合流量包存档然后开始改解析代码。只要 sample.pcap 的解析结果和上一次一致就说明改动没破坏已有功能万一哪天解析结果对不上用 diff 对比输出几分钟就能定位到偏移错误。把样本文件连同代码一起放进版本仓库存成测试资产后续接手的人跑一条命令就能验证程序状态。这算是把 Sniffer 源码从一次性演示程序变成可维护工具的分界线。记住一点抓包程序的正确性不能靠“感觉有流量就行”要靠离线样本反复验证。我自己的习惯是先离线跑通再上真网卡翻车概率会低很多。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑