原地址:http://wwtvanessa.blogbus.com/logs/74573944.html
libpcap是一个与实现无关的访问操作系统所提供的分组捕获机制的分组捕获函数库,用于访问数据链路层。这个库为不同的平台提供了一致的编程接口,在安装了 libpcap 的平台上,以 libpcap 为接口写的程序、应用,能够自由的跨平台使用。操作系统所提供的分组捕获机制主要有三种:BPF(Berkeley Packet Filter),DLPI(Data Link Provider Interface),及Linux下的SOCK_PACKET类型套接口。基于 BSD 的系统使用 BPF,基于 SVR4 的系统一般使用DLPI。从文献上看BPF比DLPI性能好很多,而SOCK_PACKET更弱。SCO OpenServer 虽然本身没有内核过滤模块 BPF,但有作为可压入内核的 STREAMS 模块 BPF,采用与伯克利 BPF 相一致的概念。但在 ioctl 操作上,SCO 的 BPF 并不完全提供伯克利 BPF 的所有功能。
为了提供跨平台兼容性,源码中并没有 Makefile ,而是要运行一个 configure 脚本来自动生成 Makefile,congfigure 脚本一方面检测系统特征,以确定当前系统及一些相关配置;另一方面读取已写好的 Makefile.in 文件,以此为蓝本,生成一个适用于当前平台的 Makefile 。
libpcap 共有二十多个 C 程序文件。我们将其按功能分组,来看看他们是如何紧密合作的。
1、打开、读取设备,设置过滤器部分。
这一部分集中了所有与具体系统监听方式密切相关的函数,也就是最底层直接与具体设备打交道的部分。设计者将其独立出来,因此要在加入对新的系统的支持,仅需要修改这一部分即可,这是非常漂亮的想法。文件形式为 pcap-*.c ,比如 pcap-bpf.c, pcap-dlpi.c, pcap-linux.c, pcap-nit.c等等 。
1.1 pcap_read()
1.2 pcap_t * pcap_open_live ( char * device, int snaplen, int promisc,
int to_ms, char * errbuf );
该函数用于获取一个抽象的包捕捉句柄,后续很多libpcap函数将使用该句柄,类似
文件操作函数频繁使用文件句柄。device指定网络接口设备名,比如"eth0。snaplen
指定单包最大捕捉字节数,为了保证包捕捉不至于太低效率,snaplen尽量适中,以
恰能获得所需协议层数据为准。promisc指定网络接口是否进入混杂模式,注意即使
该参数为false(0),网络接口仍然有可能因为其他原因处在混杂模式。to_ms指定毫
秒级读超时,man手册上并没有指明什么值意味着永不超时,测试下来的结论,0可能
代表永不超时。如果调用失败返回NULL,errbuf包含失败原因。
/usr/include/pcap.h
typedef struct pcap pcap_t;
pcap-int.h里定义了struct pcap {}
struct pcap
{
int fd;
int snapshot;
int linktype;
int tzoff; /* timezone offset */
int offset; /* offset for proper alignment */
struct pcap_sf sf;
struct pcap_md md;
int bufsize; /* Read buffer */
u_char * buffer;
u_char * bp;
int cc;
u_char * pkt; /* Place holder for pcap_next() */
struct bpf_program fcode; /* Placeholder for filter code if bpf not in kernel. */
char errbuf[PCAP_ERRBUF_SIZE];
};
2、编译、优化、调试过滤规则表达式部分
过滤机制采用了伪机器的方式,参看前面对 BPF 过滤器的基本介绍。正如前面介绍的,BPF 过滤器程序和实际机器语言有很多相似之处。一条指令(即结构 bpf_insn) 具有操作码(code),操作数(jt,jf),等等。设计者还提供了一系列的宏来书写代码,这正是 BPF 编码的“汇编语言”。正因如此,BPF 程序的编写也具有所有低级语言的弱点:编程太复杂。甚至跳转语句都要让你去一条一条的数需要跳过多少条语句。让使用者学习使用这些代码的编写方法会分散他们的精力,使用者当然需要更为简单易用,好理解的接口。因此,正如高级语言的产生一样,有了过滤规则表达式,使用这种表达式就直观、易理解多了。完成编译及优化的程序文件为:gencode.c, grammar.c, scanner.c, optimize.c ,这一部分将输入的过滤规则表达式编译为 BPF 代码,存入 bpf_program 结构,再用 pcap_setfilter() 来加载程序。
2.1 int pcap_compile ( pcap_t * p, struct bpf_program * fp, char * str, int optimize, bpf_u_int32 netmask );
该函数用于解析过滤规则串,填写bpf_program结构。str指向过滤规则串,格式参看tcpdump的man手册,比如:
tcpdump -x -vv -n -t ip proto \\tcp and dst 192.168.8.90 and tcp[13] \& 2 = 2
这条过滤规则将捕捉所有携带SYN标志的到192.168.8.90的TCP报文。过滤规则串可以是空串(""),表示抓取所有过路的报文。
optimize为1表示对过滤规则进行优化处理。netmask指定子网掩码,一般从pcap_lookupnet()调用中获取。返回值小于零表示调用失败。
这个函数可能比较难于理解,涉及的概念源自BPF,Linux系统没有这种概念,但是
libpcap采用pcap_compile()和pcap_setfilter()结合的办法屏蔽了各种链路层支持
的不同,无论是SOCK_PACKET、DLPI。
3、脱机方式监听部分。
Libpcap 支持脱机监听。即先将网络上的数据截获下来,存到磁盘上,再以后方便时从磁盘上获取数据来做分析。
3.1 pcap_open_offline()
3.2 pcap_offline_read ()
4、本地网络设置检测部分。
4.1 char * pcap_lookupdev ( char * errbuf );
该函数返回一个网络设备接口名,类似libnet_select_device(),对于Linux就是
"eth0"一类的名字.失败时返回NULL,errbuf包含了失败原因。errbuf一般定义如下:
/usr/include/pcap.h
#define PCAP_ERRBUF_SIZE 256
char errbuf[ PCAP_ERRBUF_SIZE ];
4.2 int pcap_lookupnet ( char * device, bpf_u_int32 * netp,
bpf_u_int32 * maskp, char * errbuf );
该函数用于获取指定网络接口的IP地址、子网掩码。不要被netp的名字所迷惑,它对
应的就是IP地址,maskp对应子网掩码。
/usr/include/pcap.h
typedef u_int bpf_u_int32;
显然简单理解成32-bit即可。如果调用失败则返回-1,errbuf包含失败原因。
5、主控程序及版本部分。
主控程序所在文件为 pcap.c 。这一文件定义了读数据的对外统一接口 pcap_next(),获取当前错误消息的 pcap_geterr() 等函数。用户无论是用实时还是脱机方式打开设备,都调用 pcap_next() 获取下一个包的数据。
5.1 pcap_next()
6、
6.1 int pcap_dispatch ( pcap_t * p, int cnt, pcap_handler callback, u_char * user );
该函数用于捕捉报文、分发报文到预先指定好的处理函数(回调函数)。
pcap_dispatch()接收够cnt个报文便返回,如果cnt为-1意味着所有报文集中在一个
缓冲区中。如果cnt为0,仅当发生错误、读取到EOF或者读超时到了(pcap_open_live
中指定)才停止捕捉报文并返回。callback指定如下类型的回调函数,用于处理
pcap_dispatch()所捕获的报文:
typedef void ( *pcap_handler ) ( u_char *, const struct pcap_pkthdr *, const u_char * );
pcap_dispatch()返回捕捉到的报文个数,如果在读取静态文件(以前包捕捉过程中存
储下来的)时碰到EOF则返回0。返回-1表示发生错误,此时可以用pcap_perror()、
pcap_geterr()显示错误信息。
下面来看看那个回调函数,总共有三个参数,第一个形参来自pcap_dispatch()的第
三个形参,一般我们自己的包捕捉程序不需要提供它,总是为NULL。第二个形参指向
pcap_pkthdr结构,该结构位于真正的物理帧前面,用于消除不同链路层支持的差异。
最后的形参指向所捕获报文的物理帧。
--------------------------------------------------------------------------
/usr/include/pcap.h
/*
* Each packet in the dump file is prepended with this generic header.
* This gets around the problem of different headers for different
* packet interfaces.
*/
struct pcap_pkthdr
{
struct timeval ts; /* time stamp */
bpf_u_int32 caplen; /* length of portion present */
bpf_u_int32 len; /* length this packet (off wire) */
};
/usr/include/net/bpf.h
/*
* Structure prepended to each packet.
*/
struct bpf_hdr
{
struct timeval bh_tstamp; /* time stamp */
bpf_u_int32 bh_caplen; /* length of captured portion */
bpf_u_int32 bh_datalen; /* original length of packet */
u_short bh_hdrlen; /* length of bpf header (this struct
plus alignment padding) */
};
/*
* Because the structure above is not a multiple of 4 bytes, some compilers
* will insist on inserting padding; hence, sizeof(struct bpf_hdr) won't work.
* Only the kernel needs to know about it; applications use bh_hdrlen.
*/
#ifdef KERNEL
#define SIZEOF_BPF_HDR 18
#endif
6.2 void pcap_close ( pcap_t * p );
该函数用于关闭pcap_open_live()获取的包捕捉句柄,释放相关资源。
void pcap_perror ( pcap_t * p, char * prefix );
第一形参来自pcap_open_live(),第二行参的作用类似perror()的形参,指定错误信
息的前缀,与perror()一样,结尾自动输出一个换行。
pcap_perror( p, "pcap_compile" )的输出类似这个效果:
pcap_compile: unknown ip proto ...
pcap_perror并不自动exit(),与perror()一样,如果需要,应该显式调用exit()。