使用libpcap库来过滤数据包
引入问题
数据包的抓取往往都是非常多的,如何过滤掉不需要的数据包呢?
几乎所有的操作系统( BSD, AIX, Mac OS, Linux 等)都会在内核中提供过滤数据包的方法,主要都是基于 BSD Packet Filter( BPF ) 结构的。libpcap 利用 BPF 来过滤数据包
步骤一:设置过滤条件
前文
BPF 使用一种类似于汇编语言的语法书写过滤表达式,不过 libpcap 和 tcpdump 都把它封装成更高级且更容易的语法了,具体可以通过 man tcpdump查看。
示例
# 只接收源 ip 地址是 192.168.1.177 的数据包
src host 192.168.1.177
# 只接收 tcp/udp 的目的端口是 80 的数据包
dst port 80
# 只接收不使用 tcp 协议的数据包
not tcp
# 只接收 SYN 标志位置位且目标端口是 22 或 23 的数据包( tcp 首部开始的第 13 个字节)
tcp[13] == 0x02 and (dst port 22 or dst port 23)
# 只接收 icmp 的 ping 请求和 ping 响应的数据包
icmp[icmptype] == icmp-echoreply or icmp[icmptype] == icmp-echo
# 只接收以太网 mac 地址是 00:e0:09:c1:0e:82 的数据包
ehter dst 00:e0:09:c1:0e:82
# 只接收 ip 的 ttl=5 的数据包(ip首部开始的第8个字节)
ip[8] == 5
用法
我们可以先将过滤条件的语句存储在字符串中
编译BPF过滤规则
作用
编译 BPF 过滤规则,将过滤条件的字符串语句变成struct bpf_program结构。
函数原型
int pcap_compile(pcap_t *p, struct bpf_program *fp, char *buf, int optimize, bpf_u_int32 mask);
参数详解
- p:pcap_open_live() 返回的 pcap_t 类型的指针
- fp:存放编译后的 bpf,应用过滤规则时需要用到这个指针
- buf:过滤条件
- optimize:是否需要优化过滤表达式
- mask:指定本地网络的网络掩码,不需要时可写 0
C语言代码示例
#include <stdio.h>
#include <pcap.h>
#define BUFSIZE 65535
int main(){
char errbuf[PCAP_ERRBUF_SIZE];
bpf_u_int32 net;
bpf_u_int32 mask;
char *device;
pcap_t *handle;
struct bpf_program filter;
char filter_app[] = "port 23";
device = pcap_lookupdev(errbuf);
if (device){
printf("device: %s\n", device);
pcap_lookupnet(device, &net, &mask, errbuf);
handle = pcap_open_live(device, BUFSIZE, 1, 0, errbuf);
if(handle != NULL){
//将过滤条件存储在struct bpf_program filter中
pcap_compile(handle, &filter, filter_app, 0, net);
printf("do pcap_compile()\n");
}
pcap_close(handle);
}
else{
printf("errbuf: %s\n", errbuf);
}
return 0;
}
应用BPF过滤规则
作用
将struct bpf_program结构的过滤条件应用起来,简单理解为让过滤生效
函数原型
int pcap_setfilter(pcap_t *p, struct bpf_program *fp);
参数详解
- p:捕获句柄,可以由pcap_open_live()或者pcap_create()函数返回得到。
- fp:过滤条件句柄,过滤条件编译出来的结果。
C语言代码示例
#include <stdio.h>
#include <pcap.h>
#define BUFSIZE 65535
int main(){
char errbuf[PCAP_ERRBUF_SIZE];
bpf_u_int32 net;
bpf_u_int32 mask;
char *device;
pcap_t *handle;
struct bpf_program filter;
char filter_app[] = "port 23";
device = pcap_lookupdev(errbuf);
if (device){
printf("device: %s\n", device);
pcap_lookupnet(device, &net, &mask, errbuf);
handle = pcap_open_live(device, BUFSIZE, 1, 0, errbuf);
if(handle != NULL){
//将过滤条件存储在struct bpf_program filter中
pcap_compile(handle, &filter, filter_app, 0, net);
printf("do pcap_compile()\n");
//设置过滤条件
pcap_setfilter(handle, &filter);
printf("do pcap_setfilter()\n");
}
pcap_close(handle);
}
else{
printf("errbuf: %s\n", errbuf);
}
return 0;
}
综合示例
前文
综上所述的三个步骤,应用完过滤表达式之后我们便可以使用 pcap_loop() 或 pcap_next() 等抓包函数来抓包了。
C语言代码示例
#include <pcap.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
void getPacket(u_char *arg, const struct pcap_pkthdr *pkthdr, const u_char *packet){
int * id = (int *)arg;
printf("id: %d\n", ++(*id));
printf("Packet length: %d\n", pkthdr->len);
printf("Number of bytes: %d\n", pkthdr->caplen);
printf("Recieved time: %s", ctime((const time_t *)&pkthdr->ts.tv_sec));
for(int i = 0; i < pkthdr->len; ++i){
printf(" %02x", packet[i]);
if( (i + 1) % 16 == 0 ){
printf("\n");
}
}
printf("\n\n");
}
int main(){
char errBuf[PCAP_ERRBUF_SIZE];
char *dev;
pcap_t * handle;
struct bpf_program filter;
int id = 0;
dev = pcap_lookupdev(errBuf);
if(dev){
printf("success: device: %s\n", dev);
}
else{
printf("error: %s\n", errBuf);
return 1;
}
handle = pcap_open_live(dev, 65535, 1, 0, errBuf);
if(!handle){
printf("error: pcap_open_live(): %s\n", errBuf);
exit(1);
}
pcap_compile(handle, &filter, "dst port 80", 1, 0);
pcap_setfilter(handle, &filter);
pcap_loop(handle, -1, getPacket, (u_char*)&id);
pcap_close(handle);
return 0;
}