libpcap(packet capture library),即数据包捕获函数库,是Unix/Linux平台下的网络数据包捕获函数库。它是一个独立于系统的用户层的包捕获API接口,为底层网络监测提供了一个可移植的框架。
一、工作原理
libpcap主要由两部分组成:网络分接头(network tap)和数据过滤器(packet filter)。网络分接头从网络设备驱动程序中收集数据进行拷贝,过滤器决定是否接收该数据包。libpcap利用BSD packet filter(BPF)算法对网卡接收到的链路层数据包进行过滤。BPF算法的基本思想是在有BPF监听的网络中,网卡驱动将接收到的数据包复制一份交给BPF过滤器,过滤器根据用户定义的规则决定是否接收此数据包以及需要拷贝该数据包的哪些内容,然后将过滤后的数据交给与过滤器关联的上层应用程序。
libpcap的包捕获机制就是在数据链路层加一个旁路处理。当一个数据包到达网络接口时,libpcap首先利用已经创建的套接字从链路层驱动程序中获得该数据包的拷贝,再通过Tap函数将数据包发给BPF过滤器。BPF过滤器根据用户已经定义好的过滤规则对数据包进行逐一匹配,匹配成功则放入内核缓冲区,并传递给用户缓冲区,匹配失败则直接丢弃。如果没有设置过滤规则,所有数据包都将放入内核缓冲区,并传递给用户层缓冲区。
二、libpcap抓包框架
其实pcap的应用格式很简单,总的来说可以分为如下5部分:
- 首先我们需要定义我们要进行嗅探的接口(网卡)。在Linux平台可能是eth0等,在BSD平台可能是xl1等。我们可以自己用一个字符串定义接口,也可以要求pcap提供接口的名字
- 接下来是初始化pcap,这也是我们告知pcap嗅探哪个接口。我们可以嗅探多个接口,使用句柄进行区分。就像打开一个文件用于写或读一样,我们必须命名我们所嗅探的“session”(会话),这样使其和其他类似的会话区分开
- 如果我们想要嗅探特定的包(例如TCP/IP包,发往端口23的包等),我们需要创建一个过滤器。创建过滤器的过程总共有三步,首先将规则集合置于一个字符串内,然后是编译这个字符串得到pcap可读的形式,最终应用这个可读形式来过滤得到我们想要的包
- 经过上面三步后,我们可以告知pcap执行抓包过程。在这个阶段内,pcap一直工作直到它接受了到了所有我们想要的包为止。
- 在嗅探完成后,我们最终关闭会话结束整个过程
具体对应函数如下:
- pcap_lookupdev()用于寻找网络设备(网卡),返回可被pcap_open_liv()函数调用的网络设备名指针
- pcap_open_live()函数用于打开网络设备,并且返回用于捕获网络数据包的数据包捕获文件描述符。对于此网络设备的操作都要基于这个描述符。
- pcap_lookupnet()函数获得指定网络设备的网络号和子网掩码
- pcap_compile()函数用于将用户指定的过滤策略编译到过滤程序中
- pcap_setfilter()函数用于设置过滤器
- pcap_loop()函数和pcap_dispatch()函数用于捕获数据包,捕获后还可以进行处理,此外pcap_next()和pcap_next_ex()两个函数也可以用来捕获数据包
- pcap_close()函数用于关闭网络设备,释放资源
三、实现libpcap的每一个步骤
3.1 设置设备
有两种方法设置我们想要嗅探的设备。
第一种,我们可以简单的让用户告诉我们,考虑下面的程序:
#include<stdio.h>
#include<pcap.h>
int main(int argc,char *argv[]){
char *dev=argv[1];
printf("Device:%s",dev);
return(0);
}
这种情况要求用户通过传递给程序的第一个参数来指定设备。字符串“dev”以pcap能”理解“的格式保存了我们要嗅探的接口的名字(当然,这要求用户必须提供一个真正存在的接口)。
另一种也比较简单,观察如下的程序:
#include<stdio.h>
#include<pcap.h>
int main(int argc,char *argv[]){
char *dev,errbuf[PCAP_ERRBUF_SIZE];
dev=pcap_lookupdev(errbuf);
if(dev==NULL){
fprintf(stderr,"Couldn't find default device: %s\n",errbuf);
return(2);
}
printf("Device: %s",dev);
return 0;
}
3.2 打开句柄并设定参数
打开设备使用pcap_open_live()函数,其函数原型如下:
pcap_t *pcap_open_live(const char *device,int snaplen,int promisc,int to_ms,char *errbuf);
使用这个函数的代码片如下:
#include<pcap.h>
...
pcap_t *handle;
handle=pcap_open_live(dev,BUFSIZ,1,1000,errbuf);
if(handle==NULL){
fprintf(stderr,"Couldn't open device %s: %s\n",dev,errbuf);
return(2);
}
上面的代码嗅探字符串dev指定的设备,告知pcap读取BUFSIZ(这个参数在pcap.h中进行了定义)个字节。设定设备为混杂模式。

获得指定网络设备的网络号和子网掩码使用pcap_lookupnet函数,其函数原型如下:
int pcap_lookupnet(const char *device, bpf_u_int32 *netp, bpf_u_int32 *maskp, char *errbuf);
当函数执行成功时返回0,执行失败时返回-1
3.3 过滤通信
实现这一过程由pcap_compile()与pcap_setfilter()这两个函数完成。总的过程分三步:
- 构造一个过滤表达式
- 编译这个表达式
- 应用这个过滤器
过滤表达式被保存在一个字符串中(字符数组),其在pcap_filter中定义了格式。
一些过滤表达式的例子如下:
- src host 192.168.1.177:只接收源ip地址是192.168.1.177的数据包
- dst port 80:只接收tcp/udp的目的端口是80的数据包
- not tcp:只接收不使用tcp协议的数据包
- tcp[13] == 0x02 and (dst port 22 or dst port 23):只接收SYN标志位置位且目标端口是22或23的数据包(tcp首部开始的第13个字节)
- icmp[icmptype] == icmp-echoreply or icmp[icmptype] == icmp-echo:只接收icmp的ping请求和ping响应的数据包
- ehter dst 00:e0:09:c1:0e:82:只接收以太网mac地址是00:e0:09:c1:0e:82的数据包
- ip[8] == 5:只接收ip的ttl=5的数据包(ip首部开始的第8个字节)
构造完过滤表达式后,我们需要编译它,使用的是pcap_compile函数,其函数原型如下:
int pcap_comile(pcap_t *p,struct bpf_program *fp,const char *str,int optimize, bpf_u_int32 netmask);
表达式被编译后就可以使用了,此时需要执行pcap_setfilter,函数原型如下:
int pcap_setfilter(pcap_t *p, struct bpf_program *fp);
下面的代码使嗅探器嗅探经由端口23的所有通信,使用混杂模式,设备是eth0:
#include<stdio.h>
#include<pcap.h>
int main(){
pcap_t *handle; //会话句柄
char dev[]="eth0"; //执行嗅探的设备
char errbuf[PCAP_ERRBUF_SIZE]; //存储错误信息的字符串
struct bpf_program filter; //已经编译好的过滤表达式
char filter_app[]="port 23"; //过滤表达式
bpf_u_int32 mask; //执行嗅探的设备的网络掩码
bpf_u_int32 net; //执行嗅探的设备的IP地址
pcap_lookupnet(dev,&net,&mask,errbuf);
handle=pcap_open_live(dev,DEV_SIZE,1,0,errbuf);
pcap_compile(handle,&filter,filter_app,0,net);
pcap_setfilter(handle,

本文深入解读libpcap的工作原理,介绍了其网络分接头和数据过滤器,展示了如何设置设备、创建过滤器及实际抓包的步骤,包括pcap_lookupdev、pcap_open_live等关键函数的使用。

5651

被折叠的 条评论
为什么被折叠?



