前言
最近学习了 WinPcap,对教程中的 Demo 做一些函数说明补充
官方中文文档:[WinPcap: WinPcap 中文技术文档 (redicecn.com)](http://www.redicecn.com/htdocs/WinPcap Document V4.01/docs_cn/html/main.html)
Demo1: 获取接口列表
- pcap_findalldevs_ex 函数:获取接口列表
- pcap_freealldevs(pcap_if_t*):释放接口列表资源
1 | int pcap_findalldevs_ex( |
pcap_if_t 结构体是 pcap_if 的 typedef,相关类型定义如下
1 | // 接口表项 |
上述结构的关系如下图所示,一个接口可以拥有多个网络地址,都是以链表形式连接

Demo2:获取接口高级信息
对每个 pcap_if 对象打印其中的所有信息
- name:接口名
- description:接口描述
- flags:flags & PCAP_IF_LOOPBACK,判断是否是环回地址
- addresses:pcap_addr 地址列表
IPV4 地址转换字符串
1 | /** |
IPv6 地址转换字符串
1 | /** |
Demo3:打开接口捕获数据包
打开接口
- pcap_open:打开接口
- pcap_close(pcap_t*):关闭接口
1 | // 打开接口 |
该函数返回的 pcap_t 结构是后续操作该接口的描述符,pcap 结构体对用户不可见,由 wpcap.dll 维护,一个可能的描述(cite. winpcap - What structure pcap_t have? - Stack Overflow)
1 | struct pcap { |
捕获数据包
打开接口后,调用 pcap_loop 捕获数据包,同时还有 pcap_dispatch 也可捕获数据包,两者参数相同
两者的不同在于 pcap_loop 在超时时,如果未捕获到数据包,会使进程阻塞,因而可以持续捕获,而 pcap_dispatch 在超时时会直接返回,不能持续捕获
1 | int pcap_loop( |
Demo4:非回调捕获包
使用 pcap_next_ex 捕获一个数据包
1 | int pcap_next_ex( |
Demo5:过滤数据包
- pcap_compile:编译过滤表达式
- pcap_setfilter:为捕获会话设置一个过滤器
1 | int pcap_compile( |
Demo6:UDPdump
主流程
- 获取接口列表
- 选择接口,获取 pcap_if
- pcap_open 打开接口,获取描述符 handle
- pcap_datalink 检查接口的数据链路层类型,DLT_EN10MB 为以太网
- pcap_compile 编译过滤表达式为 bpf_program
- pcap_setfilter 设置 handle 的过滤器
- pcap_freealldevs 释放接口列表
- pcap_loop 开始捕获
回调处理流程
-
通过 pcap_pkthdr 结构获取元数据,打印时间戳等信息
-
将 pkt_data 解析出 IP 首部和 UDP 首部
处理流程主要是对捕获到的字节数组进行解析,通常的做法就是先定位到要解析的部分的首地址,然后将指针强转为其他类型,指针强转就是改变指针指向的单位,换句话说,就是不同类型的指针移动时以不同的单位进行移动,改变指针类型就是改变指针移动的步长,当指针指向解析部分首地址时,改变指针类型为解析类型,就可以得到解析部分的完整结构了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28/* 获得IP数据包头部的位置 */
ip_header* ip = (ip_header*)(pkt_data + 14); // 以太网头部长度,单位B
/* 获得UDP首部的位置 */
// ip->ver_ihl & 0xf获取低4位值,即ihl
// 首部长度单位4B,ihl * 4 = IP首部长度字节数
u_int ip_len = (ip->ver_ihl & 0xf) * 4;
// ip指针转为字节表示,ip + ip_len = IP包数据部分第一字节,再转为udp_header*,得到UDP首部
udp_header* udp = (udp_header*)((u_char*)ip + ip_len);
/* 将网络字节序列转换成主机字节序列 */
// 获取源端口和目的端口并转换,主机字节序和网络字节序不一定相同
sport = ntohs(udp->sport);
dport = ntohs(udp->dport);
// 类似有ntohl, htons,htonl
/* 打印IP地址和UDP端口 */
printf("%d.%d.%d.%d:%d -> %d.%d.%d.%d:%d\n",
ip->saddr.byte1,
ip->saddr.byte2,
ip->saddr.byte3,
ip->saddr.byte4,
sport,
ip->daddr.byte1,
ip->daddr.byte2,
ip->daddr.byte3,
ip->daddr.byte4,
dport);
Demo7:处理脱机堆文件
保存堆文件
将捕获的数据包数据保存到文件中
- pcap_dump_open:创建并打开堆文件,通常文件名为
*.pcap
- pcap_dump:将数据包写入堆文件
1 | pcap_dumper_t* pcap_dump_open( |
读取堆文件
- pcap_createscrstr:根据参数生成一个描述接口的 source 字符串,可用于创建文件接口,使用 pcap_open 打开,pcap_loop 捕获
- pcap_open_offline:专用于打开文件接口
1 | int pcap_createsrcstr( |
Demo8:发送数据包
发送单个数据包
pcap_sendpacket:发送单个数据包
1 | int pcap_sendpacket( |
发送队列
使用发送队列,发送队列相关函数
- pcap_sendqueue_alloc:创建指定大小的发送队列
- pcap_sendqueue_queue:将包添加到发送队列
- pcap_sendqueue_transmit:传输发送队列
- pcap_sendqueue_destroy:销毁发送队列
1 | pcap_send_queue* pcap_sendqueue_alloc( |
Demo9:收集并统计网络流量
pcap_setmode:设置接口为统计模式
1 | int pcap_setmode( |
开始捕获后,pkt_header 和 pkt_data 为统计信息,具体如下所示
-
pkt_header 中包含 ts 时间戳,成功捕获长度为包内容大小 (统计信息),pkt_data 共 16B
-
pkt_data 中前 8B 为 AcceptedPackets 已捕获的数据包数量,后 8B 为 AcceptedBytes 已捕获字节数
*((LONGLONG*)pkt_data)
获取 AcceptedPackets 数值*((LONGLONG*)(pkt_data + 8))
获取 AcceptedBytes 数值

补充:LARGE_INTEGER 类型
LARGE_INTEGER 可表示一个 64 位符号数,定义如下
1 | typedef union _LARGE_INTEGER { |
LowPart 存储 64 位数的低 32 位,HighPart 存储 64 位数的低 32 位,当编译器不支持 64 位数时,LARGE_INTEGER 通过 LowPart 和 HighPart 表示一个 64 位数,当支持 64 位数时,LARGE_INTEGER 等价于 LONGLONG(aka. __int64, long long),可直接使用 QuadPart