基于libcapwinpcap的网络编程.docx
《基于libcapwinpcap的网络编程.docx》由会员分享,可在线阅读,更多相关《基于libcapwinpcap的网络编程.docx(16页珍藏版)》请在冰豆网上搜索。
基于libcapwinpcap的网络编程
第五章基于libpcap的网络编程技术
5.1常见的包捕获机制简介
包捕获就是利用以太网的介质共享的特性,通过将网络适配器设置为混杂模式的方法,接收到所有网络上的以太网帧。
包捕获的机制大致可以分为两类:
一类是由操作系统内核提供的捕获机制。
另一类是由应用软件或系统开发包捕获驱动程序提供的捕获机制。
常见的包捕获机制如表5-1所示。
其中最主要的是下列4种:
✓BPF(BerkeleyPacketFilter)
✓DLPI(DataLinkProviderInterface)
✓NIT(NetworkInterfaceTap)
✓SOCK-PACKET类型套接口。
BPF由基于BSD的Unix系统内核所实现。
DLPI是Solaris(和其他SystemVUNIX)系统的内嵌子系统。
NIT是SUNOS4系统的一部分,但在Solaris/SUNOS5中被DLPI所取代。
Linux核心则实现了SOCK-PACKET的包捕获机制。
从性能上看,BPF比DLPI和NIT好得多,SOCK-PACKET最弱。
表5-1常用的包捕获机制
由于现在很多局域网为NT网,其网络传输方式大多采用以太网标准,所以涉及的编程也是在Windows环境下实现的。
Windows操作系统没有提供包捕获机制,只提供了数量很少并且功能有限的API调用。
在Windows环境下由于其自身的封装性,很难对其底层进行编程。
本章将对BSD系列的libpcap进行深入地介绍。
5.2Libpcap与BPF
(1)libpcap概述
libpcap(PacketCapturelibrary),即数据包捕获函数库。
该库提供的C函数接口可用于捕获经过网络接口(只要经过该接口,目标地址不一定为本机)的数据包。
它是由洛仑兹伯克利试验室的研究人员StevenMcCanne和VanJacobson于1993年在Usenix'93会议上正式提出的一种用于Unix内核数据包过滤体制。
该函数库支持Linux、Solaris和BSD系统平台。
采用libpcap可以捕获本地网络数据链路层上的数据。
libpcap库是基于BPF(BerkeleyPacketFilter:
BSD包过滤器)系统的。
BPF是BSD系统在的TCP/IP软件在实现的时候所提供的一个接口,通过这个接口,外部程序可以得到到达本机的数据链路层网络数据,同时也可以设置过滤器,嵌入到网络软件中,获得过滤后的数据包。
(2)BPF结构及工作原理
BPF或者BSDPacketFilter是一种由stevenMcCanne和VanJacobson提出的内核包捕获的体系结构,它使得UNIX下的应用程序通过一个高度优化的方法读取流经网
图5-1BPF工作原理
络适配器的数据包。
winpcap的核心网络驱动程序也是采用这种结构的。
它主要由两部分组成:
Networktap和PacketFilter。
Networktap是一个回调函数(callbackfunction),
并不是直接由BPF执行,当一个新的数据包到达时,由网络适配器的设备驱动程序激活,它从网络拷贝每个数据包,并且将它们分发到对应的应用程序。
Packetfilter来决定一个数据包是否需要进行接收和拷贝到相应的应用程序,同时还提供一种很复杂的功能,可以确定需要进行拷贝的数据的长度。
BPF的工作原理如图5-1所示。
由Networktap不断地从网络适配器获得数据包,通过packetfilter的判断后,拷贝数据到内核storebuffer,当storebuffer填满后,将和holdbuffer进行数据交换,再由holdbuffer将数据拷贝到处于用户层的userbuffer中。
由于采用这种内核过滤、数据包双缓冲、数据包批拷贝的方式,减少了用户态进程调用内核的次数,极大地提高了数据包的处理能力。
在系统实现的过程中可以充分利用BPF过滤模型的这种特性,通过根据不同的网络侦听的需求,如针对特定的协议、特定的机器等,设置内核过滤器,减少捕获的数据的大小,同时增加内核缓冲区的大小,使系统能够获得一个很好的性能。
BPF的结构如图5-2所示。
libpcap在BPF的基础上提供了实用的接口函数供其它程序调用,既可以捕获网络上的数据,也可以打开tcpdump和tcpslice格式的数据,进行分析。
同时也可以根据需要,设置过滤器,获得感兴趣的数据包。
可以编辑简单的BPF过滤器对数据进行过滤,获得指定的IP包、TCP包等。
如下所示:
可以用tcpdump的过滤表达式进行过滤:
tcpdumptcp21
表示获得FTP端口(21)的所有的TCP数据,即通过21端口进出的数据,其它的数据全部丢弃。
图5-2BPF的结构
Libpcap软件包可从http:
//www.tcpdump.org/下载,然后依此执行下列三条命令即可安装,但如果希望libpcap能在linux上正常工作,则必须使内核支持"packet"协议,也即在编译内核时打开配置选项CONFIG_PACKET(选项缺省为打开)。
./configure
./make
./makeinstall
5.3libpcap库函数与数据结构
(1)libpcap库函数
libpcap所提供的主要函数如下:
1)pcap_t*pcap_open_live();用于获取一个包捕获描述符
2)char*pcap_lookupdev();返回一个适于pcap_open_live()和pcap_lookupnet()函数使用的指向网络设备的指针
3)intpcap_lookupnet();用于判断与网络设备相关的网络号和掩码
4)intpcap_dispatch()或intpcap_loop();收集和处理数据包
5)voidpcap_dump();将一个包输出到由pcap_dump_open()打开的保存文件中
6)intpcap_compile();用于将过滤规则字符串编译成一个内核过滤程序
7)intpcap_setfilter();设定一个过滤程序
8)intpcap_datalink();返回数据链路层类型,如10M以太网,SLIP,PPP,FDDI,ATM,IEEE802.3等
9)voidpcap_close();关闭关联文件并回收资源
10)intpcap_stats(pcap_t*,structpcap_stat*);
11)intpcap_read(pcap_t*,intcnt,pcap_handler,u_char*);
除了上面列出的主要函数外,libpcap还提供一些其他的函数,将在后面介绍。
dump文件格式:
文件头:
structpcap_file_header{
bpf_u_int32magic;
//0xa1b2c3d4
u_shortversion_major;
u_shortversion_minor;
bpf_int32thiszone;
bpf_u_int32sigfigs;
bpf_u_int32snaplen;
bpf_u_int32linktype;
};
每一个包的包头和数据
structpcap_pkthdr{
structtimevalts;bpf_u_int32caplen;bpf_u_int32len;
};
其中数据部分的长度为caplen
(2)libpcap数据结构
相关的数据结构:
typedefvoid(*pcap_handler)(u_char*,conststructpcap_pkthdr*,constu_char*);
typedefstructpcappcap_t;
structbpf_program{u_intbf_len;structbpf_insn*bf_insns;};
(3)利用libpcap函数库开发应用程序的基本步骤
图5-3说明了libpcap的应用步骤。
图5-3libpcap的调用流程
下面列出libpcap应用过程中的几个关键函数。
获取设备名
char*pcap_lookupdev(char*errbuf)
该函数用于返回可被pcap_open_live()或pcap_lookupnet()函数调用的网络设备名(一个字符串指针)。
如果函数出错,则返回NULL,同时errbuf中存放相关的错误消息。
获取网络号和掩码
intpcap_lookupnet(char*device,bpf_u_int32*netp,bpf_u_int32*maskp,char*errbuf)
获得指定网络设备的网络号和掩码。
netp参数和maskp参数都是bpf_u_int32指针。
如果函数出错,则返回-1,同时errbuf中存放相关的错误消息。
打开设备
pcap_t*pcap_open_live(char*device,intsnaplen,intpromisc,
intto_ms,char*errbuf);
获得用于捕获网络数据包的数据包捕获句柄,后续很多libpcap函数将使用该句柄,类似于文件操作函数频繁使用文件句柄。
device参数为指定打开的网络设备名,比如"eth0。
snaplen参数定义捕获数据的最大字节数。
promisc指定是否将网络接口置于混杂模式。
to_ms参数指定超时时间(毫秒),测试下来的结论,0可能代表永不超时。
errbuf参数则仅在pcap_open_live()函数出错返回NULL时用于传递错误消息。
/usr/include/pcap.h
typedefstructpcappcap_t;
pcap-int.h里定义了structpcap{}
structpcap
{
int fd;
int snapshot;
int linktype;
int tzoff; /*timezoneoffset */
int offset; /*offsetforproperalignment */
structpcap_sf sf;
structpcap_md md;
int bufsize; /*Readbuffer */
u_char* buffer;
u_char* bp;
int cc;
u_char* pkt; /*Placeholderforpcap_next() */
structbpf_programfcode;
/*Placeholderforfiltercodeifbpfnotinkernel.*/
char errbuf[PCAP_ERRBUF_SIZE];
};
编译、优化、调试过滤规则
intpcap_compile(pcap_t*p,structbpf_program*fp,char*str,
intoptimize,bpf_u_int32netmask);
该函数用于解析过滤规则串,填写bpf_program结构。
str指向过滤规则串,格式参看tcpdump的man手册,比如:
tcpdump-x-vv-n-tipproto\\tcpanddst192.168.8.90andtcp[13]\&2=2
这条过滤规则将捕捉所有携带SYN标志的到192.168.8.90的TCP报文。
过滤规则串可以是空串(""),表示抓取所有过路的报文。
optimize为1表示对过滤规则进行优化处理。
netmask指定子网掩码,一般从pcap_lookupnet()调用中获取。
返回值小于零表示调用失败。
这个函数可能比较难于理解,涉及的概念源自BPF,Linux系统没有这种概念,但是libpcap采用pcap_compile()和pcap_setfilter()结合的办法屏蔽了各种链路层支持的不同,无论是SOCK_PACKET,还是DLPI。
intpcap_setfilter(pcap_t*p,structbpf_program*fp);
该函数用于设置pcap_compile()解析完毕的过滤规则。
fp参数是bpf_program结构指针,通常取自pcap_compile()函数调用。
出错时返回-1;成功时返回0。
抓取下一个数据包
抓取数据包
intpcap_dispatch(pcap_t*p,intcnt,pcap_handlercallback,u_char*user);
捕获并处理数据包,即该函数用于捕捉报文、分发报文到预先指定好的处理函数(回调函数)。
其中,cnt参数指定函数返回前所处理数据包的最大值,pcap_dispatch()接收够cnt个报文便返回。
Cnt为-1表示在一个缓冲区中处理所有的数据包。
如果cnt为0,仅当发生错误、读取到EOF或者读超时到了(pcap_open_live中指定)才停止捕捉报文并返回。
callback参数指定一个带有三个参数的回调函数用于处理pcap_dispatch()所捕获的报文。
回调函数的三个参数为:
typedefvoid(*pcap_handler)(u_char*,conststructpcap_pkthdr*,constu_char*);
一个从pcap_dispatch()函数传递过来的u_char指针,一个pcap_pkthdr结构的指针,和一个数据包大小的u_char指针。
如果成功则pcap_dispatch()返回捕捉到的报文个数,如果在读取静态文件(以前包捕捉过程中存储下来的)时碰到EOF则返回0。
返回-1表示发生错误,此时可以用pcap_perror()、pcap_geterr()显示错误信息。
--------------------------------------------------------------------------
/usr/include/pcap.h
/*
*Eachpacketinthedumpfileisprependedwiththisgenericheader.
*Thisgetsaroundtheproblemofdifferentheadersfordifferent
*packetinterfaces.
*/
structpcap_pkthdr
{
structtimevalts; /*timestamp */
bpf_u_int32 caplen; /*lengthofportionpresent */
bpf_u_int32 len; /*lengththispacket(offwire)*/
};
/usr/include/net/bpf.h
/*
*Structureprependedtoeachpacket.
*/
structbpf_hdr
{
structtimevalbh_tstamp; /*timestamp */
bpf_u_int32 bh_caplen; /*lengthofcapturedportion*/
bpf_u_int32 bh_datalen; /*originallengthofpacket */
u_short bh_hdrlen; /*lengthofbpfheader(thisstruct
plusalignmentpadding) */
};
/*
*Becausethestructureaboveisnotamultipleof4bytes,somecompilers
*willinsistoninsertingpadding;hence,sizeof(structbpf_hdr)won'twork.
*Onlythekernelneedstoknowaboutit;applicationsusebh_hdrlen.
*/
#ifdefKERNEL
#defineSIZEOF_BPF_HDR18
#endif
--------------------------------------------------------------------------
intpcap_loop(pcap_t*p,intcnt,pcap_handlercallback,u_char*user)
功能基本与pcap_dispatch()函数相同,只不过此函数在cnt个数据包被处理或出现错误时才返回,但读取超时不会返回。
而如果为pcap_open_live()函数指定了一个非零值的超时设置,然后调用pcap_dispatch()函数,则当超时发生时pcap_dispatch()函数会返回。
cnt参数为负值时pcap_loop()函数将始终循环运行,除非出现错误。
例如:
pcap_loop(pd,10,eth_printer,NULL);主循环,开始抓包,共抓10个(由第二个参数指定),抓到包后就进入函数eth_printeru_char*pcap_next(pcap_t*p,structpcap_pkthdr*h)返回指向下一个数据包的u_char指针。
关闭
voidpcap_close(pcap_t*p)关闭p参数相应的文件,并释放资源。
出错处理
象其它库一样,libpcap也有自己的错误处理机制。
基本上每个函数都有返回值,出错时返回值小于零。
voidpcap_perror(pcap_t*,char*);
char*pcap_strerror(int);
char*pcap_geterr(pcap_t*);
在pcap_t中有一个成员存了错误字串
structpcap{
...
charerrbuf[PCAP_ERRBUF_SIZE];
};
其他的辅助函数
pcap_read()打开设备
pcap_next()循环包捕获
脱机方式监听部分:
Libpcap支持脱机监听。
即先将网络上的数据截获下来,存到磁盘上,再以后方便时从磁盘上获取数据来做分析。
主要函数为
pcap_open_offline()
pcap_offline_read()
FILE*pcap_file(pcap_t*p)返回被打开文件的文件名。
intpcap_fileno(pcap_t*p)返回被打开文件的文件描述字号码。
intpcap_snapshot(pcap_t*);
返回最长抓多少字节,就是在pcap_open_live中第二个参数设置
intpcap_stats(pcap_t*,structpcap_stat*);
计数,共抓了多少过滤掉了多少
structpcap_stat{
u_intps_recv;/*numberofpacketsreceived*/
u_intps_drop;/*numberofpacketsdropped*/
u_intps_ifdrop;/*dropsbyinterfaceXXXnot
yetsupported*/
};
intpcap_datalink(pcap_t*);返回网络类型,如DLT_EN10MB就是10M以太网
intpcap_major_version(pcap_t*);返回libpcap主版本号
intpcap_minor_version(pcap_t*);返回libpcap次版本号
5.4Libpcap的应用实例
上面详细说明了libpcap的调用过程和步骤,下面通过两个具体的例子来说明libpcap库的应用。
例5-1一个简单的Libpcap测试程序
(1)test.CXX
#ifdef__cplusplus
extern"C"{
#endif
#include
#ifdef__cplusplus
}
#endif
voidprinter(u_char*user,conststructpcap_pkthdr*h,constu_char*p)
{
printf("Igetonepacket!
");
}
#defineDEFAULT_SNAPLEN68
intmain()
{
charebuf[PCAP_ERRBUF_SIZE];
char*device=pcap_lookupdev(ebuf);
bpf_u_int32localnet,netmask;
pcap_lookupnet(device,&localnet,&netmask,ebuf);
printf("%u.%u.%u.%u",localnet&0xff,localnet>>8&0xff,
localnet>>16&0xff,localnet>>24&0xff);
printf(":
%d.%d.%d.%d",netmask&0xff,netmask>>8&0xff,
netmask>>16&0xff,netmask>>24&0xff);
structpcap_t*pd=pcap_open_live(device,DEFAULT_SNAPLEN,0,1000,ebuf);
if(pcap_datalink(pd)==DLT_EN10MB)
printf("10Mb以太网");
structbpf_programfcode;
pcap_compile(pd,&fcode,NULL,1,0);
pcap_setfilter(pd,&fcode);
pcap_loop(pd,10,printer,NULL);
structpcap_statstat;
p