文章出處

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\n");
149             continue;
150         }
151 
152         printf("%d bytes recieved\n", 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]\n", (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]\n", (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.\n");
    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]\n", (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


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()