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] == icmp-echo:只接收 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指针。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <pcap.h> 4 5 #define PCAP_DATABUF_MAX 65535 6 7 #define ETHERTYPE_IPV4 0x0800 8 #define ETHERTYPE_IPV6 0x86DD 9 10 typedef unsigned char u_int8; 11 typedef unsigned short u_int16; 12 typedef unsigned int u_int32; 13 typedef unsigned long u_int64; 14 15 /*MAC头,总长度14字节 */ 16 typedef struct _eth_hdr{ 17 u_int8 dst_mac[6]; 18 u_int8 src_mac[6]; 19 u_int16 eth_type; 20 }eth_hdr; 21 eth_hdr *ethernet; 22 23 /*IP头*/ 24 typedef struct _ip_hdr{ 25 u_int8 ver_hl; //版本和头长 26 u_int8 serv_type; //服务类型 27 u_int16 pkt_len; //包总长 28 u_int16 re_mark; //重组标志 29 u_int16 flag_seg; //标志位和段偏移量 30 u_int8 surv_tm; //生存时间 31 u_int8 protocol; //协议码(判断传输层是哪一个协议) 32 u_int16 h_check; //头检验和 33 u_int32 src_ip; //源ip 34 u_int32 dst_ip; //目的ip 35 u_int32 option; //可选选项 36 }ip_hdr; 37 ip_hdr *ip; 38 39 /*TCP头,总长度20字节,不包括可选选项*/ 40 typedef struct _tcp_hdr{ 41 u_int16 sport; //源端口 42 u_int16 dport; //目的端口 43 u_int32 seq; //序列号 44 u_int32 ack; //确认序号 45 u_int8 head_len; //头长度 46 u_int8 flags; //保留和标记位 47 u_int16 wind_size; //窗口大小 48 u_int16 check_sum; //校验和 49 u_int16 urgent_p; //紧急指针 50 }tcp_hdr; 51 tcp_hdr *tcp; 52 53 /*UDP头,总长度8个字节*/ 54 typedef struct _udp_hdr{ 55 u_int16 sport; //源端口 56 u_int16 dport; //目的端口 57 u_int16 pktlen; //UDP头和数据的总长度 58 u_int16 check_sum; //校验和 59 }udp_hdr; 60 udp_hdr *udp; 61 62 //ip整型转换点分十进制 63 char *InttoIpv4str(u_int32 num){ 64 char* ipstr = (char*)calloc(128, sizeof(char*)); 65 66 if (ipstr) 67 sprintf(ipstr, "%d.%d.%d.%d", num >> 24 & 255, num >> 16 & 255, num >> 8 & 255, num & 255); 68 else 69 printf("failed to Allocate memory..."); 70 71 return ipstr; 72 } 73 74 void pcap_callback(u_char *useless,const struct pcap_pkthdr *pkthdr, const u_char *packet) 75 { 76 printf("data len:%u\n", pkthdr->caplen); //抓到时的数据长度 77 printf("packet size:%u\n", pkthdr->len); //数据包实际的长度 78 79 /*解析数据链路层 以太网头*/ 80 ethernet = (struct _eth_hdr*)packet; 81 u_int64 src_mac = ntohs( ethernet->src_mac ); 82 u_int64 dst_mac = ntohs( ethernet->dst_mac ); 83 84 printf("src_mac:%lu\n",src_mac); 85 printf("dst_mac:%lu\n",dst_mac); 86 printf("eth_type:%u\n",ethernet->eth_type); 87 88 u_int32 eth_len = sizeof(struct _eth_hdr); //以太网头的长度 89 u_int32 ip_len; //ip头的长度 90 u_int32 tcp_len = sizeof(struct _tcp_hdr); //tcp头的长度 91 u_int32 udp_len = sizeof(struct _udp_hdr); //udp头的长度 92 93 /*解析网络层 IP头*/ 94 if(ntohs(ethernet->eth_type) == ETHERTYPE_IPV4){ //IPV4 95 printf("It's IPv4!\n"); 96 97 ip = (struct _ip_hdr*)(packet + eth_len); 98 ip_len = (ip->ver_hl & 0x0f)*4; //ip头的长度 99 u_int32 saddr = (u_int32)ntohl(ip->src_ip); //网络字节序转换成主机字节序 100 u_int32 daddr = (u_int32)ntohl(ip->dst_ip); 101 102 printf("eth_len:%u ip_len:%u tcp_len:%u udp_len:%u\n", eth_len, ip_len, tcp_len, udp_len); 103 104 printf("src_ip:%s\n", InttoIpv4str(saddr)); //源IP地址 105 printf("dst_ip:%s\n", InttoIpv4str(daddr)); //目的IP地址 106 107 printf("ip->proto:%u\n", ip->protocol); //传输层用的哪一个协议 108 109 /*解析传输层 TCP、UDP、ICMP*/ 110 if(ip->protocol == 6){ //TCP 111 tcp = (struct _tcp_hdr*)(packet + eth_len + ip_len); 112 printf("tcp_sport = %u\n", tcp->sport); 113 printf("tcp_dport = %u\n", tcp->dport); 114 115 /**********(pcaket + eth_len + ip_len + tcp_len)就是TCP协议传输的正文数据了***********/ 116 117 }else if(ip->protocol == 17){ //UDP 118 udp = (struct _udp_hdr*)(packet + eth_len + ip_len); 119 printf("udp_sport = %u\n", udp->sport); 120 printf("udp_dport = %u\n", udp->dport); 121 122 /**********(pcaket + eth_len + ip_len + udp_len)就是UDP协议传输的正文数据了***********/ 123 124 }else if(ip->protocol == 1){ //ICMP 125 126 } 127 128 }else if(ntohs(ethernet->eth_type) == ETHERTYPE_IPV6){ //IPV6 129 printf("It's IPv6!\n"); 130 } 131 132 printf("============================================\n"); 133 } 134 135 int main() 136 { 137 char *dev; //设备名 138 char errbuf[PCAP_ERRBUF_SIZE] = {}; //PCAP_ERRBUF_SIZE在pcap.h中已经定义 139 bpf_u_int32 netp, maskp; //网络号和掩码 140 pcap_t *handler; //数据包捕获描述字 141 struct bpf_program *fp; 142 char *filter_str = "port 9000"; //过滤条件 143 144 /*Find network devices*/ 145 if((dev = pcap_lookupdev(errbuf)) == NULL){ 146 printf("lookupdev failed:%s\n", errbuf); 147 exit(1); 148 }else{ 149 printf("Device:%s\n", dev); 150 } 151 152 /*Get the network number and mask of the network device*/ 153 if(pcap_lookupnet(dev, &netp, &maskp, errbuf) == -1){ 154 printf("%s\n", errbuf); 155 exit(1); 156 } 157 158 /*Open network device*/ 159 if((handler = pcap_open_live(dev, PCAP_DATABUF_MAX, 1, 0, errbuf)) == NULL){ 160 printf("%s\n", errbuf); 161 exit(1); 162 } 163 164 /*Compiling and setting filtering conditions*/ 165 if(pcap_compile(handler, fp, filter_str, 0, maskp) == -1){ 166 printf("pcap_compile error...\n"); 167 exit(1); 168 } 169 if(pcap_setfilter(handler, fp) == -1){ 170 printf("pcap_setfilter error...\n"); 171 exit(1); 172 } 173 174 /*Capturing and processing data packets*/ 175 if(pcap_loop(handler, -1, pcap_callback, NULL) == -1){ 176 printf("pcap_loop error...\n"); 177 pcap_close(handler); 178 } 179 180 return 0; 181 }
来源:https://www.cnblogs.com/itsad/p/7885113.html