libpcap是数据包捕获函数库。该库提供的C函数接口可用于需要捕获经过网络接口数据包的系统开发上。libpcap提供的接口函数主要实现和封装了与数据包截获有关的过程。这个库为不同的平台提供了一致的编程接口,在安装了libpcap的平台上,以libpcap为接口写的程序,能够自由的跨平台使用。
linux下libpcap的安装:sudo apt-get install libpcap-dev
linux下gcc编译程序:gcc my_pcap.c -lpcap
执行程序的时候如果报错:no suitable device found,以管理员权限运行程序即可,sudo ./my_pcap
libpcap的抓包框架:
头文件: #include <pcap.h> 在/usr/local/include/pcap目录下
1.查找网络设备
char *pcap_lookupdev(char *errbuf)
该函数用于返回可被pcap_open_live()或pcap_lookupnet()函数调用的网络设备名(一个字符串指针)。如果函数出错,则返回NULL,同时errbuf中存放相关的错误消息。
2.获得指定网络设备的网络号和掩码
int pcap_lookupnet(char *device, bpf_u_int32 *netp, bpf_u_int32 *maskp, char *errbuf)
netp参数和maskp参数都是bpf_u_int32指针。如果函数出错,则返回-1,同时errbuf中存放相关的错误消息。
Bpf_u_int32:32位无符号数
Struct in_addr
{
unsigned long s_addr;
}
inet_ntoa();以a.b.c.d的形式显示地址。
3.打开网络设备
pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
获得用于捕获网络数据包的数据包捕获描述字。device参数为指定打开的网络设备名。snaplen参数定义捕获数据的最大字节数,65535是最大值。promisc指定 是否将网络接口置于混杂模式,设置为1表示混杂模式。to_ms参数指定超时时间(毫秒),设置为0表示超时时间无限大。ebuf参数则仅在pcap_open_live()函数出错返回NULL时用于传递 错误消息。
typedef struct pcap pcap_t;
pcap结构在libpcap源码的pcap-int.h定义,使用时一般都是使用其指针类型)。
4.打开已有的网络数据包 //如果是抓取数据包,这个过程不需要
pcap_t *pcap_open_offline(char *fname, char *errbuf)
fname参数指定打开的文件名。该文件中的数据格式与tcpdump兼容。errbuf参数则仅在pcap_open_offline()函数出错返回NULL时用于传递错误消息。
pcap_t *pcap_fopen_offline(FILE *fp, char *errbuf)
打开文件指针。
5.编译和设置过滤条件
int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
设置过滤条件,举一些例子:
-
src host 192.168.1.1:只接收源ip地址是192.168.1.1的数据包
-
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] == icmpecho:只接收 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个字节)
将str参数指定的字符串编译到过滤程序中。fp是一个bpf_program结构的指针,在pcap_compile()函数中被赋值。optimize参数控制结果代码的优化。netmask参数指定本地网络的网络掩码,当不知道的时候可以设为0。出错时返回-1.
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
指定一个过滤程序。fp参数是bpf_program结构指针,通常取自pcap_compile()函数调用。出错时返回-1。
6.抓取和读取数据包
int pcap_dispatch(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
捕获并处理数据包。cnt参数指定函数返回前所处理数据包的最大值。cnt=-1表示在一个缓冲区中处理所有的数据包。callback参数指定一个带有三个参数的回调函数,这三个参数为:一个从 pcap_dispatch()函数传递过来的u_char指针,一个pcap_pkthdr结构的指针,和指向caplen大小的数据包的u_char指针。
struct pcap_pkthdr {
struct tim ts; // ts是一个结构struct timeval,它有两个部分,第一部分是1900开始以来的秒数,第二部分是当前秒之后的毫秒数
bpf_u_int32 caplen; //表示抓到的数据长度
bpf_u_int32 len; //表示数据包的实际长度
};
user参数是留给用户使用的,当callback被调用的时候这个值会传递给callback的第一个参数(也叫user)。
成功 则返回读到的数据包数。返回0没有抓到数据包。出错时则返回-1,此时可调用pcap_perror()或pcap_geterr()函数获取错误消息。返回-2表示调用了pcap_breakloop().
int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
功能基本与pcap_dispatch()函数类似,只不过此函数在cnt个数据包被处理或出现错误时才返回,但读取超时不会返回。
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
读取下一个数据包,类似于pcap_dispatch()中cnt参数设为1,返回指向读到的数据包的指针,但是不返回这个包的pcap_pkthdr结构的参数。
7.关闭文件释放资源
void pcap_close(pcap_t *p)
关闭P指针。
实例
#include <stdio.h>
#include <stdlib.h>
#include <pcap.h>
#define PCAP_DATABUF_MAX 65535
#define ETHERTYPE_IPV4 0x0800
#define ETHERTYPE_IPV6 0x86DD
typedef unsigned char u_int8;
typedef unsigned short u_int16;
typedef unsigned int u_int32;
typedef unsigned long u_int64;
/*MAC头,总长度14字节 */
typedef struct _eth_hdr{
u_int8 dst_mac[6];
u_int8 src_mac[6];
u_int16 eth_type;
}eth_hdr;
eth_hdr *ethernet;
/*IP头*/
typedef struct _ip_hdr{
u_int8 ver_hl; //版本和头长
u_int8 serv_type; //服务类型
u_int16 pkt_len; //包总长
u_int16 re_mark; //重组标志
u_int16 flag_seg; //标志位和段偏移量
u_int8 surv_tm; //生存时间
u_int8 protocol; //协议码(判断传输层是哪一个协议)
u_int16 h_check; //头检验和
u_int32 src_ip; //源ip
u_int32 dst_ip; //目的ip
u_int32 option; //可选选项
}ip_hdr;
ip_hdr *ip;
/*TCP头,总长度20字节,不包括可选选项*/
typedef struct _tcp_hdr{
u_int16 sport; //源端口
u_int16 dport; //目的端口
u_int32 seq; //序列号
u_int32 ack; //确认序号
u_int8 head_len; //头长度
u_int8 flags; //保留和标记位
u_int16 wind_size; //窗口大小
u_int16 check_sum; //校验和
u_int16 urgent_p; //紧急指针
}tcp_hdr;
tcp_hdr *tcp;
/*UDP头,总长度8个字节*/
typedef struct _udp_hdr{
u_int16 sport; //源端口
u_int16 dport; //目的端口
u_int16 pktlen; //UDP头和数据的总长度
u_int16 check_sum; //校验和
}udp_hdr;
udp_hdr *udp;
//ip整型转换点分十进制
char *InttoIpv4str(u_int32 num){
char* ipstr = (char*)calloc(128, sizeof(char*));
if (ipstr)
sprintf(ipstr, "%d.%d.%d.%d", num >> 24 & 255, num >> 16 & 255, num >> 8 & 255, num & 255);
else
printf("failed to Allocate memory...");
return ipstr;
}
void pcap_callback(u_char *useless,const struct pcap_pkthdr *pkthdr, const u_char *packet)
{
printf("data len:%u\n", pkthdr->caplen); //抓到时的数据长度
printf("packet size:%u\n", pkthdr->len); //数据包实际的长度
/*解析数据链路层 以太网头*/
ethernet = (struct _eth_hdr*)packet;
u_int64 src_mac = ntohs( ethernet->src_mac );
u_int64 dst_mac = ntohs( ethernet->dst_mac );
printf("src_mac:%lu\n",src_mac);
printf("dst_mac:%lu\n",dst_mac);
printf("eth_type:%u\n",ethernet->eth_type);
u_int32 eth_len = sizeof(struct _eth_hdr); //以太网头的长度
u_int32 ip_len; //ip头的长度
u_int32 tcp_len = sizeof(struct _tcp_hdr); //tcp头的长度
u_int32 udp_len = sizeof(struct _udp_hdr); //udp头的长度
/*解析网络层 IP头*/
if(ntohs(ethernet->eth_type) == ETHERTYPE_IPV4){ //IPV4
printf("It's IPv4!\n");
ip = (struct _ip_hdr*)(packet + eth_len);
ip_len = (ip->ver_hl & 0x0f)*4; //ip头的长度
u_int32 saddr = (u_int32)ntohl(ip->src_ip); //网络字节序转换成主机字节序
u_int32 daddr = (u_int32)ntohl(ip->dst_ip);
printf("eth_len:%u ip_len:%u tcp_len:%u udp_len:%u\n", eth_len, ip_len, tcp_len, udp_len);
printf("src_ip:%s\n", InttoIpv4str(saddr)); //源IP地址
printf("dst_ip:%s\n", InttoIpv4str(daddr)); //目的IP地址
printf("ip->proto:%u\n", ip->protocol); //传输层用的哪一个协议
/*解析传输层 TCP、UDP、ICMP*/
if(ip->protocol == 6){ //TCP
tcp = (struct _tcp_hdr*)(packet + eth_len + ip_len);
printf("tcp_sport = %u\n", tcp->sport);
printf("tcp_dport = %u\n", tcp->dport);
/**********(pcaket + eth_len + ip_len + tcp_len)就是TCP协议传输的正文数据了***********/
}else if(ip->protocol == 17){ //UDP
udp = (struct _udp_hdr*)(packet + eth_len + ip_len);
printf("udp_sport = %u\n", udp->sport);
printf("udp_dport = %u\n", udp->dport);
/**********(pcaket + eth_len + ip_len + udp_len)就是UDP协议传输的正文数据了***********/
}else if(ip->protocol == 1){ //ICMP
}
}else if(ntohs(ethernet->eth_type) == ETHERTYPE_IPV6){ //IPV6
printf("It's IPv6!\n");
}
printf("============================================\n");
}
int main()
{
char *dev; //设备名
char errbuf[PCAP_ERRBUF_SIZE] = {}; //PCAP_ERRBUF_SIZE在pcap.h中已经定义
bpf_u_int32 netp, maskp; //网络号和掩码
pcap_t *handler; //数据包捕获描述字
struct bpf_program *fp;
char *filter_str = "port 9000"; //过滤条件
/*Find network devices*/
if((dev = pcap_lookupdev(errbuf)) == NULL){
printf("lookupdev failed:%s\n", errbuf);
exit(1);
}else{
printf("Device:%s\n", dev);
}
/*Get the network number and mask of the network device*/
if(pcap_lookupnet(dev, &netp, &maskp, errbuf) == -1){
printf("%s\n", errbuf);
exit(1);
}
/*Open network device*/
if((handler = pcap_open_live(dev, PCAP_DATABUF_MAX, 1, 0, errbuf)) == NULL){
printf("%s\n", errbuf);
exit(1);
}
/*Compiling and setting filtering conditions*/
if(pcap_compile(handler, fp, filter_str, 0, maskp) == -1){
printf("pcap_compile error...\n");
exit(1);
}
if(pcap_setfilter(handler, fp) == -1){
printf("pcap_setfilter error...\n");
exit(1);
}
/*Capturing and processing data packets*/
if(pcap_loop(handler, -1, pcap_callback, NULL) == -1){
printf("pcap_loop error...\n");
pcap_close(handler);
}
return 0;
}