在Ubuntu 14.04 64位上使用libpcap进行抓包和解包

为了开发需要,我决定使用最新libpcap源码包安装。在Unix环境下安装libpcap库,需要c编译器,flex,bison等,安装Ubuntu系统时,没有这些包。安装flex需要m4编译环境,否则会提示“GNU M4 is required”错误。

1.安装系统依赖包sudo apt-get install gcc libc6-devsudo apt-get install m4sudo apt-get install flex bison

2.下载libpcap源码并安装从官网http://www.tcpdump.org/下载最新的libpcap版本cd /usr/local/srcwget http://www.tcpdump.org/release/libpcap-1.5.3.tar.gztar zxvf libpcap-1.5.3.tar.gzcd libpcap-1.5.3./configuremakesudo make install

3.安装开发需要用到的依赖库sudo apt-get install libpcap-dev

4.测试libpcap的小程序,命名为pcap_demo.c,以检验环境是否配置正确#include <pcap.h>#include <stdio.h>

int main(int argc, char *argv[]){pcap_t *handle; /* Session handle */char *dev; /* The device to sniff on */char errbuf[PCAP_ERRBUF_SIZE]; /* Error string */struct bpf_program fp; /* The compiled filter */char filter_exp[] = “port 80”; /* The filter expression */bpf_u_int32 mask; /* Our netmask */bpf_u_int32 net; /* Our IP */struct pcap_pkthdr header; /* The header that pcap gives us */const u_char *packet; /* The actual packet */

/* Define the device */dev = pcap_lookupdev(errbuf);if (dev == NULL) {fprintf(stderr, “Couldn’t find default device: %s\n”, errbuf);return(2);}/* Find the properties for the device */if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {fprintf(stderr, “Couldn’t get netmask for device %s: %s\n”, dev, errbuf);net = 0;mask = 0;}/* Open the session in promiscuous mode */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);}/* Compile and apply the filter */if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {fprintf(stderr, “Couldn’t parse filter %s: %s\n”, filter_exp, pcap_geterr(handle));return(2);}if (pcap_setfilter(handle, &fp) == -1) {fprintf(stderr, “Couldn’t install filter %s: %s\n”, filter_exp, pcap_geterr(handle));return(2);}/* Grab a packet */packet = pcap_next(handle, &header);/* Print its length */printf(“Jacked a packet with length of [%d]\n”, header.len);/* And close the session */pcap_close(handle);return(0);}开始编译:gcc -g pcap_demo.c -o pcap_demo -lpcap 开始执行./pcap_demo

5.注意的问题5.1.注意使用root用户来执行,或者对普通用户使用sudo来提升权限sudo pcap_demo 5.2.对一些PCAP API函数要有全面地理解,并时刻更新文档,比如pcap_loop这个函数,下面是官网的man page地址 http://www.tcpdump.org/manpages/pcap.3pcap.html5.3,对一些函数的理解:pcap_loop和pcap_dispatch的区别,前者不会超时返回,而是会尽量去读取更多的包,直至发生一个错误才返回。正常返回的值是0,否则就是负值。我目前需要连续不断地抓包,所以应该使用pcap_loop。pcap_dispatch内部会调用pcap_loop。pcap_loop中的第四个参数在某些应用中很有用,但是在通常情况下被设为NULL。这种定义很有用,比如假如你想给pcap_loop的回调函数再额外传递一些参数,你可以使用u_char类型的指针(字符串),传人到回调函数中再做具体地处理。pcap_dispatch只会处理它收到的第一批包。从pcap_loop返回后,我们应该显式调用pcap_close来关闭pcap以便释放资源。为了表示连续不间断的抓包,在pcap_loop中要尽量使用-1而不是0.关于其中回调函数的说明,里面有3个参数。第一个参数user是pcap_loop中最后一个参数,它表示某个不太重要的信息,可以忽略,第二个参数是包头信息,含有包的时间戳和长度,第三个参数是包的数据,从链路层头开始,它在该回调函数中并不会被释放,但是当该回调返回时不包装这些数据再合法,所以要再次使用它们,请事先复制出来。

下面再深入研究一下pcap_loop这个回调函数的定义:首先注意函数的返回值是void类型,这完全符合逻辑,因为pcap_loop根本不知道如何处理回调函数的返回值,就是给他传出返回值也没有意义。然后就是回调函数的第一个参数对应pcap_loop中的最后一个参数,不管你给pcap_loop的最后一个参数传递什么值,当回调函数被调用时,它都会作为第一个参数传递给回调函数,第二个参数是pcap头,它包含一些信息:抓包时间,包多大,等等,这个结构在pcap.h中定义,如下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) */};

抓包方式有两种,一种是每次抓一个包,另一种是循环抓包,分为循环抓有限个数的包,或者是无限循环抓包。其中每次抓一个包使用pcap_next函数。pcap_next()表示只抓取一个数据包,它的第一个参数是session handle,第二个参数是包头,返回值是包数据。函数原型如下u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)另一种循环抓包的方法,就是使用pcap_loop和回调函数,代码样例参见我整理出的两个例子。

5.4.网卡设备的设置有两种方法,第一种方法是用户直接指定网卡名称(必须要真实可用),从命令行参数传递进去,比如#include <stdio.h>#include <pcap.h>

int main(int argc, char *argv[]){char *dev = argv[1];

printf(“Device: %s\n”, dev);return(0);}另一种方法,是让pcap自己去探测网卡设备,如果出错,会给出出错信息,但是这种方法很多情况下不靠谱,对此的改进是,让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\n”, dev);return(0);}

5.5.对我们抓到的数据包,它的结构,大体分为Ethernet header,IP header,TCP header等等,分别对应14,20,20个字节,IP header至少20个字节,TCP头也至少20个字节。data=recv_data[42:]这样做的原因是,recv_data 抓到的都是raw packet, tcp/ip是分了5层,一个udp包,会带有 14Bytes的Ethenet_II Frame的头,然后是20个字节的ip包头,而udp包头有8个字节,所以偏移量42之后的才是udp的实际内容tcp包的包头大小为20Bytes,所以其实际内容在偏移量54之后

5.6.对数据包的解包和组包,需要用到Ethernet header,IP header,TCP header,UDP header等常用数据结构的定义,我们并不需要重新定义这些头,直接引用相关的头文件定义就可以了。#include <netinet/in.h>#include <netinet/if_ether.h>#include <netinet/ip.h>#include <netinet/tcp.h>#include <netinet/udp.h>#include <netinet/ip_icmp.h>

5.6.对我们目前的程序,通常需要从命令行传人如下参数信息:wlan0 待抓包的网卡名称tcp port 80 过滤表达式10000 or -1 抓包模式,是抓指定个数的包(这里是10000个包),还是无限抓包下去当然,对这些参数,你也可以写死在程序里。

参考文献[1].http://www.tcpdump.org/pcap.html

更多Ubuntu相关信息见Ubuntu 专题页面 http://www.68idc.cn/topicnews.aspx?tid=2

一直有记日记的习惯,可是,旅行回来,都懒得写日记来记录,

在Ubuntu 14.04 64位上使用libpcap进行抓包和解包

相关文章:

你感兴趣的文章:

标签云: