• suricata抓包方式之一 AF_PACKET


    1、前言

        linux提供了原始套接字RAW_SOCKET,可以抓取数据链路层的报文。这样可以对报文进行深入分析。今天介绍一下AF_PACKET的用法,分为两种方式。第一种方法是通过套接字,打开指定的网卡,然后使用recvmsg读取,实际过程需要需要将报文从内核区拷贝到用户区。第二种方法是使用packet_mmap,使用共享内存方式,在内核空间中分配一块内核缓冲区,然后用户空间程序调用mmap映射到用户空间。将接收到的skb拷贝到那块内核缓冲区中,这样用户空间的程序就可以直接读到捕获的数据包了。PACKET_MMAP减少了系统调用,不用recvmsg就可以读取到捕获的报文,相比原始套接字+recvfrom的方式,减少了一次拷贝和一次系统调用。libpcap就是采用第二种方式。suricata默认方式也是使用packet mmap抓包。

    2、测试例子

      为了方便测试,可以使用linux提供的sock_filter过滤ip地址。使用tcpdump可以反汇编出来过滤的条件。以www.qq.com为例进行说明:

    ping www.qq.com得到ip地址:101.226.103.106

    tcpdump ip -s 2048 -d host 101.226.103.106
    (000) ldh      [12]
    (001) jeq      #0x800           jt 2    jf 7
    (002) ld       [26]
    (003) jeq      #0x65e2676a      jt 6    jf 4
    (004) ld       [30]
    (005) jeq      #0x65e2676a      jt 6    jf 7
    (006) ret      #2048
    (007) ret      #0
     tcpdump -dd host 101.226.103.106
    { 0x28, 0, 0, 0x0000000c },
    { 0x15, 0, 4, 0x00000800 },
    { 0x20, 0, 0, 0x0000001a },
    { 0x15, 8, 0, 0x65e2676a },
    { 0x20, 0, 0, 0x0000001e },
    { 0x15, 6, 7, 0x65e2676a },
    { 0x15, 1, 0, 0x00000806 },
    { 0x15, 0, 5, 0x00008035 },
    { 0x20, 0, 0, 0x0000001c },
    { 0x15, 2, 0, 0x65e2676a },
    { 0x20, 0, 0, 0x00000026 },
    { 0x15, 0, 1, 0x65e2676a },
    { 0x6, 0, 0, 0x0000ffff },
    { 0x6, 0, 0, 0x00000000 }

    更新详细过滤细节可以参考: http://blog.csdn.net/eqiang8271/article/details/8489769

    (1)第一种方法:

      1 #include <sys/types.h>
      2 #include <sys/time.h>
      3 #include <sys/ioctl.h>
      4 #include <sys/socket.h>
      5 #include <linux/types.h>
      6 #include <netinet/in.h>
      7 #include <netinet/udp.h>
      8 #include <netinet/ip.h>
      9 #include <netpacket/packet.h>
     10 #include <net/ethernet.h>
     11 #include <arpa/inet.h>
     12 #include <string.h>
     13 #include <signal.h>
     14 #include <net/if.h>
     15 #include <stdio.h>
     16 #include <sys/uio.h>
     17 #include <fcntl.h>
     18 #include <unistd.h>
     19 #include <linux/filter.h>
     20 #include <stdlib.h>
     21 
     22 #define ETH_HDR_LEN 14
     23 #define IP_HDR_LEN 20
     24 #define UDP_HDR_LEN 8
     25 #define TCP_HDR_LEN 20
     26 
     27 static int sock;
     28 
     29 void sig_handler(int sig) 
     30 {
     31     struct ifreq ethreq;
     32     if(sig == SIGTERM)
     33         printf("SIGTERM recieved, exiting.../n");
     34     else if(sig == SIGINT)
     35         printf("SIGINT recieved, exiting.../n");
     36     else if(sig == SIGQUIT)
     37         printf("SIGQUIT recieved, exiting.../n");
     38     // turn off the PROMISCOUS mode 
     39     strncpy(ethreq.ifr_name, "eth1", IFNAMSIZ);
     40     if(ioctl(sock, SIOCGIFFLAGS, &ethreq) != -1) {
     41         ethreq.ifr_flags &= ~IFF_PROMISC;
     42         ioctl(sock, SIOCSIFFLAGS, &ethreq);
     43     }
     44     close(sock);
     45     exit(0);
     46 }
     47 
     48 int main(int argc, char ** argv) {
     49     int n;
     50     char buf[2048];
     51     unsigned char *ethhead;
     52     unsigned char *iphead;
     53     struct ifreq ethreq;
     54     struct sigaction sighandle;
     55 
     56 #if 0
     57     $tcpdump ip -s 2048 -d host 192.168.1.2
     58         (000) ldh      [12]
     59         (001) jeq      #0x800           jt 2 jf 7
     60         (002) ld       [26]
     61         (003) jeq      #0xc0a80102      jt 6 jf 4
     62         (004) ld       [30]
     63         (005) jeq      #0xc0a80102      jt 6 jf 7
     64         (006) ret      #2048
     65         (007) ret      #0
     66 #endif
     67 
     68 #if 0
     69         测试访问www.qq.com 
     70         ping www.qq.com 得到ip地址为:101.226.103.106
     71         tcpdump -dd host 101.226.103.106
     72          { 0x28, 0, 0, 0x0000000c },
     73          { 0x15, 0, 4, 0x00000800 },
     74          { 0x20, 0, 0, 0x0000001a },
     75          { 0x15, 8, 0, 0x65e2676a },
     76          { 0x20, 0, 0, 0x0000001e },
     77          { 0x15, 6, 7, 0x65e2676a },
     78          { 0x15, 1, 0, 0x00000806 },
     79          { 0x15, 0, 5, 0x00008035 },
     80          { 0x20, 0, 0, 0x0000001c },
     81          { 0x15, 2, 0, 0x65e2676a },
     82          { 0x20, 0, 0, 0x00000026 },
     83          { 0x15, 0, 1, 0x65e2676a },
     84          { 0x6, 0, 0, 0x0000ffff },
     85          { 0x6, 0, 0, 0x00000000 },
     86 #endif
     87         struct sock_filter bpf_code[] = {
     88             { 0x28, 0, 0, 0x0000000c },
     89             { 0x15, 0, 5, 0x00000800 },
     90             { 0x20, 0, 0, 0x0000001a },
     91             { 0x15, 2, 0, 0x65e2676a },
     92             { 0x20, 0, 0, 0x00000026 },
     93             { 0x15, 0, 1, 0x65e2676a },
     94             { 0x6, 0, 0, 0x0000ffff },
     95             { 0x6, 0, 0, 0x00000000 }
     96         };
     97 
     98     struct sock_fprog filter;
     99     filter.len = sizeof(bpf_code)/sizeof(bpf_code[0]);
    100     filter.filter = bpf_code;
    101 
    102     sighandle.sa_flags = 0;
    103     sighandle.sa_handler = sig_handler;
    104     sigemptyset(&sighandle.sa_mask);
    105     //sigaddset(&sighandle.sa_mask, SIGTERM);
    106     //sigaddset(&sighandle.sa_mask, SIGINT);
    107     //sigaddset(&sighandle.sa_mask, SIGQUIT);
    108     sigaction(SIGTERM, &sighandle, NULL);
    109     sigaction(SIGINT, &sighandle, NULL);
    110     sigaction(SIGQUIT, &sighandle, NULL);
    111 
    112     // AF_PACKET allows application to read pecket from and write packet to network device
    113     // SOCK_DGRAM the packet exclude ethernet header
    114     // SOCK_RAW raw data from the device including ethernet header
    115     // ETH_P_IP all IP packets 
    116     if((sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP))) == -1) {
    117         perror("socket");
    118         exit(1);
    119     }
    120 
    121     // set NIC to promiscous mode, so we can recieve all packets of the network
    122     strncpy(ethreq.ifr_name, "eth1", IFNAMSIZ);
    123     if(ioctl(sock, SIOCGIFFLAGS, &ethreq) == -1) {
    124         perror("ioctl");
    125         close(sock);
    126         exit(1);
    127     }
    128 
    129     ethreq.ifr_flags |= IFF_PROMISC;
    130     if(ioctl(sock, SIOCSIFFLAGS, &ethreq) == -1) {
    131         perror("ioctl");
    132         close(sock);
    133         exit(1);
    134     }
    135 
    136 #if 1
    137     // attach the bpf filter
    138     if(setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) == -1) {
    139         perror("setsockopt");
    140         close(sock);
    141         exit(1);
    142     }
    143 #endif
    144 
    145     while(1) {
    146         n = recvfrom(sock, buf, sizeof(buf), 0, NULL, NULL);
    147         if(n < (ETH_HDR_LEN+IP_HDR_LEN+UDP_HDR_LEN)) {
    148             printf("invalid packet
    ");
    149             continue;
    150         }
    151 
    152         printf("%d bytes recieved
    ", n);
    153 
    154         ethhead = buf;
    155         printf("Ethernet: MAC[%02X:%02X:%02X:%02X:%02X:%02X]", ethhead[0], ethhead[1], ethhead[2],
    156                 ethhead[3], ethhead[4], ethhead[5]);
    157         printf("->[%02X:%02X:%02X:%02X:%02X:%02X]", ethhead[6], ethhead[7], ethhead[8],
    158                 ethhead[9], ethhead[10], ethhead[11]);
    159         printf(" type[%04x]
    ", (ntohs(ethhead[12]|ethhead[13]<<8)));
    160 
    161         iphead = ethhead + ETH_HDR_LEN;
    162         // header length as 32-bit
    163         printf("IP: Version: %d HeaderLen: %d[%d]", (*iphead>>4), (*iphead & 0x0f), (*iphead & 0x0f)*4);
    164         printf(" TotalLen %d", (iphead[2]<<8|iphead[3]));
    165         printf(" IP [%d.%d.%d.%d]", iphead[12], iphead[13], iphead[14], iphead[15]);
    166         printf("->[%d.%d.%d.%d]", iphead[16], iphead[17], iphead[18], iphead[19]);
    167         printf(" %d", iphead[9]);
    168 
    169         if(iphead[9] == IPPROTO_TCP)
    170             printf("[TCP]");
    171         else if(iphead[9] == IPPROTO_UDP)
    172             printf("[UDP]");
    173         else if(iphead[9] == IPPROTO_ICMP)
    174             printf("[ICMP]");
    175         else if(iphead[9] == IPPROTO_IGMP)
    176             printf("[IGMP]");
    177         else if(iphead[9] == IPPROTO_IGMP)
    178             printf("[IGMP]");
    179         else
    180             printf("[OTHERS]");
    181 
    182         printf(" PORT [%d]->[%d]
    ", (iphead[20]<<8|iphead[21]), (iphead[22]<<8|iphead[23]));
    183     }
    184     close(sock);
    185     exit(0);
    186 }

    (2)第二种方法:

    #include <stdio.h>
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    #include <sys/mman.h>
    #include <poll.h>
    #include <linux/types.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <linux/if_packet.h>
    #include <linux/if_ether.h>
    #include <linux/filter.h>
    #include <net/ethernet.h>
    
    #define ETH_HDR_LEN 14
    void CallBackPacket(char *data)
    {
        unsigned char *ethhead;
        unsigned char *iphead;
        printf("Recv A Packet.
    ");
        ethhead = data;
        printf("Ethernet: MAC[%02X:%02X:%02X:%02X:%02X:%02X]", ethhead[0], ethhead[1], ethhead[2],
                ethhead[3], ethhead[4], ethhead[5]);
        printf("->[%02X:%02X:%02X:%02X:%02X:%02X]", ethhead[6], ethhead[7], ethhead[8],
                ethhead[9], ethhead[10], ethhead[11]);
        printf(" type[%04x]
    ", (ntohs(ethhead[12]|ethhead[13]<<8)));
    
        iphead = ethhead + ETH_HDR_LEN;
        // header length as 32-bit
        printf("IP: Version: %d HeaderLen: %d[%d]", (*iphead>>4), (*iphead & 0x0f), (*iphead & 0x0f)*4);
        printf(" TotalLen %d", (iphead[2]<<8|iphead[3]));
        printf(" IP [%d.%d.%d.%d]", iphead[12], iphead[13], iphead[14], iphead[15]);
        printf("->[%d.%d.%d.%d]", iphead[16], iphead[17], iphead[18], iphead[19]);
        printf(" %d", iphead[9]);
    }
    
    int main()
    {
        int fd = 0, ret = 0;
        char *buff = NULL;
    
        fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
        //可以使用ARP进行一下测试
        //fd = socket(PF_PACKET, SOCK_DGRAM, htons (ETH_P_ARP));
        if(fd<0)
        {
            perror("socket");
            goto failed_2;
        }
    
        //PACKET_VERSION和SO_BINDTODEVICE可以省略
    #if 1
        const int tpacket_version = TPACKET_V1;
        /* set tpacket hdr version. */
        ret = setsockopt(fd, SOL_PACKET, PACKET_VERSION, &tpacket_version, sizeof (int));
        if(ret<0)
        {
            perror("setsockopt");
            goto failed_2;
        }
    
        //#define NETDEV_NAME "wlan0"
    #define NETDEV_NAME "eth1"
        /* bind to device. */
        ret = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, NETDEV_NAME, sizeof (NETDEV_NAME));
        if(ret<0)
        {
            perror("setsockopt");
            goto failed_2;
        }
    #endif
    
        struct tpacket_req req;
    #define PER_PACKET_SIZE 2048
        const int BUFFER_SIZE = 1024*1024*16; //16MB的缓冲区
        req.tp_block_size = 4096;
        req.tp_block_nr = BUFFER_SIZE/req.tp_block_size;
        req.tp_frame_size = PER_PACKET_SIZE;
        req.tp_frame_nr = BUFFER_SIZE/req.tp_frame_size;
    
        ret = setsockopt(fd, SOL_PACKET, PACKET_RX_RING, (void *)&req, sizeof(req));
        if(ret<0)
        {
            perror("setsockopt");
            goto failed_2;
        }
    
    #if 1
        struct sock_filter bpf_code[] = {
            { 0x28, 0, 0, 0x0000000c },
            { 0x15, 0, 5, 0x00000800 },
            { 0x20, 0, 0, 0x0000001a },
            { 0x15, 2, 0, 0x65e2676a },
            { 0x20, 0, 0, 0x00000026 },
            { 0x15, 0, 1, 0x65e2676a },
            { 0x6, 0, 0, 0x0000ffff },
            { 0x6, 0, 0, 0x00000000 }
        };
        struct sock_fprog filter;
        filter.len = sizeof(bpf_code)/sizeof(bpf_code[0]);
        filter.filter = bpf_code;
        // attach the bpf filter
        if(setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) == -1) {
            perror("setsockopt");
            close(fd);
            goto failed_2;
        }
    #endif
    
        buff = (char *)mmap(0, BUFFER_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
        if(buff == MAP_FAILED)
        {
            perror("mmap");
            goto failed_2;
        }
    
        int nIndex=0, i=0;
        while(1)
        {
            //这里在poll前先检查是否已经有报文被捕获了
            struct tpacket_hdr* pHead = (struct tpacket_hdr*)(buff+ nIndex*PER_PACKET_SIZE);
            //如果frame的状态已经为TP_STATUS_USER了,说明已经在poll前已经有一个数据包被捕获了,如果poll后不再有数据包被捕获,那么这个报文不会被处理,这就是所谓的竞争情况。
            if(pHead->tp_status == TP_STATUS_USER)
                goto process_packet;
    
            //poll检测报文捕获
            struct pollfd pfd;
            pfd.fd = fd;
            //pfd.events = POLLIN|POLLRDNORM|POLLERR;
            pfd.events = POLLIN;
            pfd.revents = 0;
            ret = poll(&pfd, 1, -1);
            if(ret<0)
            {
                perror("poll");
                goto failed_1;
            }
    
    process_packet:
            //尽力的去处理环形缓冲区中的数据frame,直到没有数据frame了
            for(i=0; i < req.tp_frame_nr; i++)
            {
                struct tpacket_hdr* pHead = (struct tpacket_hdr*)(buff+ nIndex*PER_PACKET_SIZE);
    
                //XXX: 由于frame都在一个环形缓冲区中,因此如果下一个frame中没有数据了,后面的frame也就没有frame了
                if(pHead->tp_status == TP_STATUS_KERNEL)
                    break;
    
                //处理数据frame
                CallBackPacket((char*)pHead+pHead->tp_net);
    
                //重新设置frame的状态为TP_STATUS_KERNEL
                pHead->tp_len = 0;
                pHead->tp_status = TP_STATUS_KERNEL;
    
                //更新环形缓冲区的索引,指向下一个frame
                nIndex++;
                nIndex %= req.tp_frame_nr;
            }
    
        }
    success:
        close(fd);
        munmap(buff, BUFFER_SIZE);
        return 0;
    
    failed_1:
        munmap(buff, BUFFER_SIZE);
    
    failed_2:
        close(fd);
        return -1;
    }

    3、测试结果

    执行main程序,使用curl http://www.qq.com

  • 相关阅读:
    vim使用技巧
    排序
    2020-3-27 学习面向对象的笔记
    小圆圈第三章答案
    内置函数部分用法
    Pycharm快捷键
    小猿圈第二章答案
    Typora学习笔记
    AI的真实感
    Unity 横版2D移动跳跃问题——关于一段跳与二段跳
  • 原文地址:https://www.cnblogs.com/Anker/p/6040873.html
Copyright © 2020-2023  润新知