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] == 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;    
}

转载自https://www.cnblogs.com/itsad/p/7885113.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值