最近老师让写一个流量监控程序,用到了libpcap编程。虽然很简单,但是前期也走了一些弯路。最初是直接从别人博客里面copy的代码,然后运行时就是结果就是不正确。本以为是系统问题,我又装了个双系统。。。
现在我把自己的代码分享出来吧,这些是我自己运行成功的,代码很简单,只是希望能给新人一些借鉴。
首先:我们先得到我们的设备名称,因为之后我们需要根据名称指针来打开我们的设备,得到名称指针的方式如下:
/*get our device name*/
char *dev,errbuf[PCAP_ERRBUF_SIZE];
dev = pcap_lookupdev(errbuf);
printf("Device: %s\n",dev); /*print our device name*/
如果我们实现已经知道设备名称了,就不用在通过这个方式得到了。
得到名称之后,我们根据名称指针打开我们的设备,这时候我们需要用到的函数是:
/*pcap_t *pcap_open_dev(char*device,int snaplen,int promisc,int to_ms,char*errbuf);*/
device是设备名称,snaplen是pcap将捕获的最大字节数,promisc是混合模式,to_ms是读取时的超时值,单位是毫秒,为0则一直嗅探直到错误发生,errbuf是出错之后信息的储存。
函数的返回值是一个句柄,在下面我们将会使用的到。
/*open device and sniff
*pcap_t *pcap_open_dev(char*device,int snaplen,int promisc,int to_ms,char*errbuf);
*
*/
pcap_t * open_dev = pcap_open_live(dev,65535,1,0,errbuf);
if(!open_dev){
printf("open device failed: %s",errbuf);
}
打开设备之后,我们设置过滤条件,过滤掉我们不想要的包。设置过滤涉及到两个函数:
int pcap_compile(pcap_t *p,struct bpf_program *fp,char *str,int optimize,bpf_u_int32 netmask)
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
pcap_compile的第一个参数就是刚刚我们打开设备的返回值,后面是我们存储被编译的过滤器版本的地址的引用。
str是我们的过滤条件。比如"tcp port 21"是指只抓取来自tcp协议,并且端口是21的包。optimize是一个定义表达式是否被优化的整形量,netmask是表示网络掩码。
pcap_setfilter则是用的都是pcap_compile里面已经出现过的函数。
好,现在我们开始设置过滤:
struct bpf_program filter;
char filter_exp[] = "port 80";/*filter expressiong*/
bpf_u_int32 mask; /*net mask*/
pcap_compile(open_dev,&filter,filter_exp,0,mask);
pcap_setfilter(open_dev,&filter);
接下来我们就开始循环捕获了,在这里我用到的函数是pcap_loop,下面是它的函数原型:
int pcap_loop(pcap_t *p,int cnt,pcap_hander callback,u_char *user)
其中:pcap_t还是我们的句柄,cnt是表示我们想要捕获多少个数据包,如果值为-1,则表示一直捕获,直到出错位置。pcap_hander则是我们回调函数的名称,user是我们想发送给回调函数的参数,如果没有,那么设置成NULL;
pthread_attr_init(&attr);
pthread_create(&tid,&attr,runner,NULL);//这两句是创建线程用的,为了每秒都显示流量情况。
pcap_loop(open_dev,-1,got_packet,NULL);
pcap_close(open_dev);
got_packet()是用户自定义的函数,可以根据自己的需要定义。下面是我定义的:
void got_packet(u_char *argv,const struct pcap_pkthdr *header,const u_char *packet){
int len2 = (int)header->caplen;
Ethernet *ethernet = (Ethernet *)(packet);
Ip_header *ip = (Ip_header *)(packet + sizeof(Ethernet));
if(ip->proto==(u_char)6){ //6是tcp协议
tcp_count += len2;
}else if(ip->proto == (u_char)17){ //17是代表使用的udp协议
udp_count += len2;
}
count +=len2; //count是为了统计总流量写的,在函数外面声明的,同理udp_count,tcp_count
}
Ethernet ,Ip_header是定义的结构体,这是为了进一步解析抓到的数据包用的。下面是结构体原型。(其中有些是直接网上copy的)
/* *以太网帧的首部 */
typedef struct ethernet{
u_char host1[6];
u_char host2[6];
u_short type;
}Ethernet;
/* IPv4 首部 */
typedef struct ip_header{
u_char ver_ihl; // 版本 (4 bits) + 首部长度 (4 bits)
u_char tos; // 服务类型(Type of service)
u_short tlen; // 总长(Total length)
u_short identification; // 标识(Identification)
u_short flags_fo; // 标志位(Flags) (3 bits) + 段偏移量(Fragment offset) (13 bits)
u_char ttl; // 存活时间(Time to live)
u_char proto; // 协议(Protocol)
u_short crc; // 首部校验和(Header checksum)
int saddr; // 源地址(Source address)
int daddr; // 目的地址(Destination address)
}Ip_header;
当然,如果你想解析到更多的数据,那么你可能还需要另tcp_heaer,和udp_header。如下:
/* UDP 首部*/
typedef struct udp_header{
u_short sport; // 源端口(Source port)
u_short dport; // 目的端口(Destination port)
u_short len; // UDP数据包长度(Datagram length),the minimum is 8
u_short crc; // 校验和(Checksum)
}Udp_header;
/*TCP首部*/
typedef struct tcp_header{
u_short sport;//源端口
u_short dPort;//目的端口
unsigned int SequNum;//序号
unsigned int AckNum;//确认号
u_short sHeaderLenAndFlag;//数据偏移+保留+URG+ACK+RST+SYN+FIN
u_short WinSize;//窗口大小
u_short CheckSum;//检验和
u_short urgentPointer;//紧急指针
}Tcp_header;
这里面的代码是流量监控的大部分,完整的代码附件可以在这里下载,不需要积分。
http://download.youkuaiyun.com/detail/qq_27856623/9713803
编译的时候需要
gcc my.c -lpcap -lpthread
运行的时候需要sudo ./a.out