历史经验之libpcap抓取数据包


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指针。

  #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;    
 }   

猜你喜欢

转载自blog.csdn.net/weixin_41486034/article/details/107225617