精华内容
下载资源
问答
  • libpcap

    2017-11-08 13:28:05
    libpcap使用 libpcap是一个网络数据包捕获函数库,功能非常强大,Linux下著名的tcpdump就是以它为基础的。今天我们利用它来完成一个我们自己的网络嗅探器(sniffer) 首先先介绍一下本次实验的环境: ...
    libpcap使用



    libpcap是一个网络数据包捕获函数库,功能非常强大,Linux下著名的tcpdump就是以它为基础的。今天我们利用它来完成一个我们自己的网络嗅探器(sniffer)




    首先先介绍一下本次实验的环境:
    Ubuntu 11.04,IP:192.168.1.1,广播地址:192.168.1.255,子网掩码:255.255.255.0
    可以使用下面的命令设置:
    sudo ifconfig eth0 192.168.1.1 broadcast 192.168.1.255 netmask 255.255.255.0





    1.安装
    http://www.tcpdump.org/下载libpcap(tcpdump的源码也可以从这个网站下载)
    解压
    ./configure
    make
    sudo make install





    2.使用
    安装好libpcap后,我们要使用它啦,先写一个简单的程序,并介绍如何使用libpcap库编译它:
    Makefile:
    [plain]  view plain  copy
    1. all: test.c  
    2.     gcc -g -Wall -o test test.c -lpcap  
    3.   
    4. clean:  
    5.     rm -rf *.o test  

    其后的程序的Makefile均类似,故不再重复


    test1.c
    [cpp]  view plain  copy
    1. #include <pcap.h>  
    2. #include <stdio.h>  
    3.   
    4. int main()  
    5. {  
    6.   char errBuf[PCAP_ERRBUF_SIZE], * device;  
    7.     
    8.   device = pcap_lookupdev(errBuf);  
    9.     
    10.   if(device)  
    11.   {  
    12.     printf("success: device: %s\n", device);  
    13.   }  
    14.   else  
    15.   {  
    16.     printf("error: %s\n", errBuf);  
    17.   }  
    18.     
    19.   return 0;  
    20. }  





    可以成功编译,不过运行的时候却提示找不到libpcap.so.1,因为libpcap.so.1默认安装到了/usr/local/lib下,我们做一个符号链接到/usr/lib/下即可






    运行test的时候输出"no suitable device found",原因是我们没有以root权限运行,root权限运行后就正常了







    下面开始正式讲解如何使用libpcap:
    首先要使用libpcap,我们必须包含pcap.h头文件,可以在/usr/local/include/pcap/pcap.h找到,其中包含了每个类型定义的详细说明




    1.获取网络接口
    首先我们需要获取监听的网络接口:
    我们可以手动指定或让libpcap自动选择,先介绍如何让libpcap自动选择:
    char * pcap_lookupdev(char * errbuf)
    上面这个函数返回第一个合适的网络接口的字符串指针,如果出错,则errbuf存放出错信息字符串,errbuf至少应该是PCAP_ERRBUF_SIZE个字节长度的。注意,很多libpcap函数都有这个参数。
    pcap_lookupdev()一般可以在跨平台的,且各个平台上的网络接口名称都不相同的情况下使用。
    如果我们手动指定要监听的网络接口,则这一步跳过,我们在第二步中将要监听的网络接口字符串硬编码在pcap_open_live里。




    2.释放网络接口
    在操作为网络接口后,我们应该要释放它:
    void pcap_close(pcap_t * p)
    该函数用于关闭pcap_open_live()获取的pcap_t的网络接口对象并释放相关资源。




    3.打开网络接口
    获取网络接口后,我们需要打开它:
    pcap_t * pcap_open_live(const char * device, int snaplen, int promisc, int to_ms, char * errbuf)
    上面这个函数会返回指定接口的pcap_t类型指针,后面的所有操作都要使用这个指针。
    第一个参数是第一步获取的网络接口字符串,可以直接使用硬编码。
    第二个参数是对于每个数据包,从开头要抓多少个字节,我们可以设置这个值来只抓每个数据包的头部,而不关心具体的内容。典型的以太网帧长度是1518字节,但其他的某些协议的数据包会更长一点,但任何一个协议的一个数据包长度都必然小于65535个字节。
    第三个参数指定是否打开混杂模式(Promiscuous Mode),0表示非混杂模式,任何其他值表示混合模式。如果要打开混杂模式,那么网卡必须也要打开混杂模式,可以使用如下的命令打开eth0混杂模式:
    ifconfig eth0 promisc
    第四个参数指定需要等待的毫秒数,超过这个数值后,第3步获取数据包的这几个函数就会立即返回。0表示一直等待直到有数据包到来。
    第五个参数是存放出错信息的数组。




    4.获取数据包
    打开网络接口后就已经开始监听了,那如何知道收到了数据包呢?有下面3种方法:
    a)
    u_char * pcap_next(pcap_t * p, struct pcap_pkthdr * h)
    如果返回值为NULL,表示没有抓到包
    第一个参数是第2步返回的pcap_t类型的指针
    第二个参数是保存收到的第一个数据包的pcap_pkthdr类型的指针
    pcap_pkthdr类型的定义如下:
    [cpp]  view plain  copy
    1. struct pcap_pkthdr  
    2. {  
    3.   struct timeval ts;    /* time stamp */  
    4.   bpf_u_int32 caplen;   /* length of portion present */  
    5.   bpf_u_int32 len;      /* length this packet (off wire) */  
    6. };  

    注意这个函数只要收到一个数据包后就会立即返回


    b)
    int pcap_loop(pcap_t * p, int cnt, pcap_handler callback, u_char * user)
    第一个参数是第2步返回的pcap_t类型的指针
    第二个参数是需要抓的数据包的个数,一旦抓到了cnt个数据包,pcap_loop立即返回。负数的cnt表示pcap_loop永远循环抓包,直到出现错误。
    第三个参数是一个回调函数指针,它必须是如下的形式:
    void callback(u_char * userarg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
    第一个参数是pcap_loop的最后一个参数,当收到足够数量的包后pcap_loop会调用callback回调函数,同时将pcap_loop()的user参数传递给它
    第二个参数是收到的数据包的pcap_pkthdr类型的指针
    第三个参数是收到的数据包数据




    c)
    int pcap_dispatch(pcap_t * p, int cnt, pcap_handler callback, u_char * user)

    这个函数和pcap_loop()非常类似,只是在超过to_ms毫秒后就会返回(to_ms是pcap_open_live()的第4个参数)


    例子:

    test2:

    [cpp]  view plain  copy
    1. #include <pcap.h>  
    2. #include <time.h>  
    3. #include <stdlib.h>  
    4. #include <stdio.h>  
    5.   
    6. int main()  
    7. {  
    8.   char errBuf[PCAP_ERRBUF_SIZE], * devStr;  
    9.     
    10.   /* get a device */  
    11.   devStr = pcap_lookupdev(errBuf);  
    12.     
    13.   if(devStr)  
    14.   {  
    15.     printf("success: device: %s\n", devStr);  
    16.   }  
    17.   else  
    18.   {  
    19.     printf("error: %s\n", errBuf);  
    20.     exit(1);  
    21.   }  
    22.     
    23.   /* open a device, wait until a packet arrives */  
    24.   pcap_t * device = pcap_open_live(devStr, 65535, 1, 0, errBuf);  
    25.     
    26.   if(!device)  
    27.   {  
    28.     printf("error: pcap_open_live(): %s\n", errBuf);  
    29.     exit(1);  
    30.   }  
    31.   
    32.   /* wait a packet to arrive */  
    33.   struct pcap_pkthdr packet;  
    34.   const u_char * pktStr = pcap_next(device, &packet);  
    35.   
    36.   if(!pktStr)  
    37.   {  
    38.     printf("did not capture a packet!\n");  
    39.     exit(1);  
    40.   }  
    41.     
    42.   printf("Packet length: %d\n", packet.len);  
    43.   printf("Number of bytes: %d\n", packet.caplen);  
    44.   printf("Recieved time: %s\n", ctime((const time_t *)&packet.ts.tv_sec));   
    45.     
    46.   pcap_close(device);  
    47.     
    48.   return 0;  
    49. }  






    打开两个终端,先ping 192.168.1.10,由于我们的ip是192.168.1.1,因此我们可以收到广播的数据包,另一个终端运行test,就会抓到这个包。







    下面的这个程序会把收到的数据包内容全部打印出来,运行方式和上一个程序一样:
    test3:
    [cpp]  view plain  copy
    1. #include <pcap.h>  
    2. #include <time.h>  
    3. #include <stdlib.h>  
    4. #include <stdio.h>  
    5.   
    6. void getPacket(u_char * arg, const struct pcap_pkthdr * pkthdr, const u_char * packet)  
    7. {  
    8.   int * id = (int *)arg;  
    9.     
    10.   printf("id: %d\n", ++(*id));  
    11.   printf("Packet length: %d\n", pkthdr->len);  
    12.   printf("Number of bytes: %d\n", pkthdr->caplen);  
    13.   printf("Recieved time: %s", ctime((const time_t *)&pkthdr->ts.tv_sec));   
    14.     
    15.   int i;  
    16.   for(i=0; i<pkthdr->len; ++i)  
    17.   {  
    18.     printf(" %02x", packet[i]);  
    19.     if( (i + 1) % 16 == 0 )  
    20.     {  
    21.       printf("\n");  
    22.     }  
    23.   }  
    24.     
    25.   printf("\n\n");  
    26. }  
    27.   
    28. int main()  
    29. {  
    30.   char errBuf[PCAP_ERRBUF_SIZE], * devStr;  
    31.     
    32.   /* get a device */  
    33.   devStr = pcap_lookupdev(errBuf);  
    34.     
    35.   if(devStr)  
    36.   {  
    37.     printf("success: device: %s\n", devStr);  
    38.   }  
    39.   else  
    40.   {  
    41.     printf("error: %s\n", errBuf);  
    42.     exit(1);  
    43.   }  
    44.     
    45.   /* open a device, wait until a packet arrives */  
    46.   pcap_t * device = pcap_open_live(devStr, 65535, 1, 0, errBuf);  
    47.     
    48.   if(!device)  
    49.   {  
    50.     printf("error: pcap_open_live(): %s\n", errBuf);  
    51.     exit(1);  
    52.   }  
    53.     
    54.   /* wait loop forever */  
    55.   int id = 0;  
    56.   pcap_loop(device, -1, getPacket, (u_char*)&id);  
    57.     
    58.   pcap_close(device);  
    59.   
    60.   return 0;  
    61. }  






    从上图可以看出,如果我们没有按Ctrl+c,test会一直抓到包,因为我们将pcap_loop()设置为永远循环




    由于ping属于icmp协议,并且发出icmp协议数据包之前必须先通过arp协议获取目的主机的mac地址,因此我们抓到的包是arp协议的,而arp协议的数据包长度正好是42字节(14字节的以太网帧头+28字节的arp数据)。具体内容请参考相关网络协议说明。






    5.分析数据包
    我们既然已经抓到数据包了,那么我们要开始分析了,这部分留给读者自己完成,具体内容可以参考相关的网络协议说明。在本文的最后,我会示范性的写一个分析arp协议的sniffer,仅供参考。要特别注意一点,网络上的数据是网络字节顺序的,因此分析前需要转换为主机字节顺序(ntohs()函数)




    6.过滤数据包
    我们抓到的数据包往往很多,如何过滤掉我们不感兴趣的数据包呢?
    几乎所有的操作系统(BSD, AIX, Mac OS, Linux等)都会在内核中提供过滤数据包的方法,主要都是基于BSD Packet Filter(BPF)结构的。libpcap利用BPF来过滤数据包。
    过滤数据包需要完成3件事:
    a) 构造一个过滤表达式
    b) 编译这个表达式
    c) 应用这个过滤器



    a)

    BPF使用一种类似于汇编语言的语法书写过滤表达式,不过libpcap和tcpdump都把它封装成更高级且更容易的语法了,具体可以man tcpdump,以下是一些例子:
    src host 192.168.1.177
    只接收源ip地址是192.168.1.177的数据包


    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个字节)


    b)
    构造完过滤表达式后,我们需要编译它,使用如下函数:
    int pcap_compile(pcap_t * p, struct bpf_program * fp, char * str, int optimize, bpf_u_int32 netmask)
    fp:这是一个传出参数,存放编译后的bpf
    str:过滤表达式
    optimize:是否需要优化过滤表达式
    metmask:简单设置为0即可


    c)
    最后我们需要应用这个过滤表达式:
    int pcap_setfilter(pcap_t * p,  struct bpf_program * fp)
    第二个参数fp就是前一步pcap_compile()的第二个参数




    应用完过滤表达式之后我们便可以使用pcap_loop()或pcap_next()等抓包函数来抓包了。


    下面的程序演示了如何过滤数据包,我们只接收目的端口是80的数据包:
    test4.c
    [cpp]  view plain  copy
    1. #include <pcap.h>  
    2. #include <time.h>  
    3. #include <stdlib.h>  
    4. #include <stdio.h>  
    5.   
    6. void getPacket(u_char * arg, const struct pcap_pkthdr * pkthdr, const u_char * packet)  
    7. {  
    8.   int * id = (int *)arg;  
    9.     
    10.   printf("id: %d\n", ++(*id));  
    11.   printf("Packet length: %d\n", pkthdr->len);  
    12.   printf("Number of bytes: %d\n", pkthdr->caplen);  
    13.   printf("Recieved time: %s", ctime((const time_t *)&pkthdr->ts.tv_sec));   
    14.     
    15.   int i;  
    16.   for(i=0; i<pkthdr->len; ++i)  
    17.   {  
    18.     printf(" %02x", packet[i]);  
    19.     if( (i + 1) % 16 == 0 )  
    20.     {  
    21.       printf("\n");  
    22.     }  
    23.   }  
    24.     
    25.   printf("\n\n");  
    26. }  
    27.   
    28. int main()  
    29. {  
    30.   char errBuf[PCAP_ERRBUF_SIZE], * devStr;  
    31.     
    32.   /* get a device */  
    33.   devStr = pcap_lookupdev(errBuf);  
    34.     
    35.   if(devStr)  
    36.   {  
    37.     printf("success: device: %s\n", devStr);  
    38.   }  
    39.   else  
    40.   {  
    41.     printf("error: %s\n", errBuf);  
    42.     exit(1);  
    43.   }  
    44.     
    45.   /* open a device, wait until a packet arrives */  
    46.   pcap_t * device = pcap_open_live(devStr, 65535, 1, 0, errBuf);  
    47.     
    48.   if(!device)  
    49.   {  
    50.     printf("error: pcap_open_live(): %s\n", errBuf);  
    51.     exit(1);  
    52.   }  
    53.     
    54.   /* construct a filter */  
    55.   struct bpf_program filter;  
    56.   pcap_compile(device, &filter, "dst port 80", 1, 0);  
    57.   pcap_setfilter(device, &filter);  
    58.     
    59.   /* wait loop forever */  
    60.   int id = 0;  
    61.   pcap_loop(device, -1, getPacket, (u_char*)&id);  
    62.     
    63.   pcap_close(device);  
    64.   
    65.   return 0;  
    66. }  




    在下面的这一个例子中,客户机通过tcp的9732端口连接服务器,发送字符'A',之后服务器将'A'+1即'B'返回给客户机,具体实现可以参考:http://blog.csdn.net/htttw/article/details/7519964


    服务器的ip是192.168.56.101,客户机的ip是192.168.56.1
    服务器:



    Makefile:

    [plain]  view plain  copy
    1. all: tcp_client.c tcp_server.c  
    2.     gcc -g -Wall -o tcp_client tcp_client.c  
    3.     gcc -g -Wall -o tcp_server tcp_server.c  
    4.   
    5. clean:  
    6.     rm -rf *.o tcp_client tcp_server  


    tcp_server:

    [cpp]  view plain  copy
    1. #include <sys/types.h>  
    2. #include <sys/socket.h>  
    3. #include <netinet/in.h>  
    4. #include <arpa/inet.h>  
    5. #include <unistd.h>  
    6. #include <stdlib.h>  
    7. #include <stdio.h>  
    8.   
    9. #define PORT 9832  
    10. #define SERVER_IP "192.168.56.101"  
    11.   
    12. int main()  
    13. {  
    14.   /* create a socket */  
    15.   int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);  
    16.     
    17.   struct sockaddr_in server_addr;  
    18.   server_addr.sin_family = AF_INET;  
    19.   server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);  
    20.   server_addr.sin_port = htons(PORT);  
    21.     
    22.   /* bind with the local file */  
    23.   bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));  
    24.     
    25.   /* listen */  
    26.   listen(server_sockfd, 5);  
    27.     
    28.   char ch;  
    29.   int client_sockfd;  
    30.   struct sockaddr_in client_addr;  
    31.   socklen_t len = sizeof(client_addr);  
    32.   while(1)  
    33.   {  
    34.     printf("server waiting:\n");  
    35.       
    36.     /* accept a connection */  
    37.     client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);  
    38.       
    39.     /* exchange data */  
    40.     read(client_sockfd, &ch, 1);  
    41.     printf("get char from client: %c\n", ch);  
    42.     ++ch;  
    43.     write(client_sockfd, &ch, 1);  
    44.       
    45.     /* close the socket */  
    46.     close(client_sockfd);  
    47.   }  
    48.     
    49.   return 0;  
    50. }  


    tcp_client:

    [cpp]  view plain  copy
    1. #include <sys/types.h>  
    2. #include <sys/socket.h>  
    3. #include <netinet/in.h>  
    4. #include <arpa/inet.h>  
    5. #include <unistd.h>  
    6. #include <stdlib.h>  
    7. #include <stdio.h>  
    8.   
    9. #define PORT 9832  
    10. #define SERVER_IP "192.168.56.101"  
    11.   
    12. int main()  
    13. {  
    14.   /* create a socket */  
    15.   int sockfd = socket(AF_INET, SOCK_STREAM, 0);  
    16.     
    17.   struct sockaddr_in address;  
    18.   address.sin_family = AF_INET;  
    19.   address.sin_addr.s_addr = inet_addr(SERVER_IP);  
    20.   address.sin_port = htons(PORT);  
    21.     
    22.   /* connect to the server */  
    23.   int result = connect(sockfd, (struct sockaddr *)&address, sizeof(address));  
    24.   if(result == -1)  
    25.   {  
    26.     perror("connect failed: ");  
    27.     exit(1);  
    28.   }  
    29.     
    30.   /* exchange data */  
    31.   char ch = 'A';  
    32.   write(sockfd, &ch, 1);  
    33.   read(sockfd, &ch, 1);  
    34.   printf("get char from server: %c\n", ch);  
    35.     
    36.   /* close the socket */  
    37.   close(sockfd);  
    38.     
    39.   return 0;  
    40. }  



    运行方法如下,首先在服务器上运行tcp_server,然后运行我们的监听器,然后在客户机上运行tcp_client,注意,我们可以先清空arp缓存,这样就可以看到整个通信过程(包括一开始的arp广播)
    在客户机上运行下列命令来清空记录服务器的arp缓存:
    sudo arp -d 192.168.56.101
    arp -a后发现已经删除了记录服务器的arp缓存




    抓包的结果如下所示,由于包太多了,无法全部截图,因此我把所有内容保存在下面的文本中了:



    全部的包如下:

    [plain]  view plain  copy
    1. hutao@hutao-VirtualBox:~/test3$ sudo ./test  
    2. success: device: eth0  
    3. id: 1  
    4. Packet length: 60  
    5. Number of bytes: 60  
    6. Recieved time: Sat Apr 28 19:57:50 2012  
    7.  ff ff ff ff ff ff 0a 00 27 00 00 00 08 06 00 01  
    8.  08 00 06 04 00 01 0a 00 27 00 00 00 c0 a8 38 01  
    9.  00 00 00 00 00 00 c0 a8 38 65 00 00 00 00 00 00  
    10.  00 00 00 00 00 00 00 00 00 00 00 00  
    11.   
    12. id: 2  
    13. Packet length: 42  
    14. Number of bytes: 42  
    15. Recieved time: Sat Apr 28 19:57:50 2012  
    16.  0a 00 27 00 00 00 08 00 27 9c ff b1 08 06 00 01  
    17.  08 00 06 04 00 02 08 00 27 9c ff b1 c0 a8 38 65  
    18.  0a 00 27 00 00 00 c0 a8 38 01  
    19.   
    20. id: 3  
    21. Packet length: 74  
    22. Number of bytes: 74  
    23. Recieved time: Sat Apr 28 19:57:50 2012  
    24.  08 00 27 9c ff b1 0a 00 27 00 00 00 08 00 45 00  
    25.  00 3c d4 af 40 00 40 06 74 55 c0 a8 38 01 c0 a8  
    26.  38 65 8e 20 26 68 79 e1 63 8c 00 00 00 00 a0 02  
    27.  39 08 d4 13 00 00 02 04 05 b4 04 02 08 0a 00 14  
    28.  b7 23 00 00 00 00 01 03 03 06  
    29.   
    30. id: 4  
    31. Packet length: 74  
    32. Number of bytes: 74  
    33. Recieved time: Sat Apr 28 19:57:50 2012  
    34.  0a 00 27 00 00 00 08 00 27 9c ff b1 08 00 45 00  
    35.  00 3c 00 00 40 00 40 06 49 05 c0 a8 38 65 c0 a8  
    36.  38 01 26 68 8e 20 b6 c4 e6 e5 79 e1 63 8d a0 12  
    37.  38 90 f1 e5 00 00 02 04 05 b4 04 02 08 0a 00 57  
    38.  a1 2c 00 14 b7 23 01 03 03 05  
    39.   
    40. id: 5  
    41. Packet length: 66  
    42. Number of bytes: 66  
    43. Recieved time: Sat Apr 28 19:57:50 2012  
    44.  08 00 27 9c ff b1 0a 00 27 00 00 00 08 00 45 00  
    45.  00 34 d4 b0 40 00 40 06 74 5c c0 a8 38 01 c0 a8  
    46.  38 65 8e 20 26 68 79 e1 63 8d b6 c4 e6 e6 80 10  
    47.  00 e5 fb c1 00 00 01 01 08 0a 00 14 b7 24 00 57  
    48.  a1 2c  
    49.   
    50. id: 6  
    51. Packet length: 67  
    52. Number of bytes: 67  
    53. Recieved time: Sat Apr 28 19:57:50 2012  
    54.  08 00 27 9c ff b1 0a 00 27 00 00 00 08 00 45 00  
    55.  00 35 d4 b1 40 00 40 06 74 5a c0 a8 38 01 c0 a8  
    56.  38 65 8e 20 26 68 79 e1 63 8d b6 c4 e6 e6 80 18  
    57.  00 e5 ba b7 00 00 01 01 08 0a 00 14 b7 25 00 57  
    58.  a1 2c 41  
    59.   
    60. id: 7  
    61. Packet length: 66  
    62. Number of bytes: 66  
    63. Recieved time: Sat Apr 28 19:57:50 2012  
    64.  0a 00 27 00 00 00 08 00 27 9c ff b1 08 00 45 00  
    65.  00 34 47 cb 40 00 40 06 01 42 c0 a8 38 65 c0 a8  
    66.  38 01 26 68 8e 20 b6 c4 e6 e6 79 e1 63 8e 80 10  
    67.  01 c5 f1 dd 00 00 01 01 08 0a 00 57 a1 2e 00 14  
    68.  b7 25  
    69.   
    70. id: 8  
    71. Packet length: 67  
    72. Number of bytes: 67  
    73. Recieved time: Sat Apr 28 19:57:50 2012  
    74.  0a 00 27 00 00 00 08 00 27 9c ff b1 08 00 45 00  
    75.  00 35 47 cc 40 00 40 06 01 40 c0 a8 38 65 c0 a8  
    76.  38 01 26 68 8e 20 b6 c4 e6 e6 79 e1 63 8e 80 18  
    77.  01 c5 f1 de 00 00 01 01 08 0a 00 57 a1 2e 00 14  
    78.  b7 25 42  
    79.   
    80. id: 9  
    81. Packet length: 66  
    82. Number of bytes: 66  
    83. Recieved time: Sat Apr 28 19:57:50 2012  
    84.  0a 00 27 00 00 00 08 00 27 9c ff b1 08 00 45 00  
    85.  00 34 47 cd 40 00 40 06 01 40 c0 a8 38 65 c0 a8  
    86.  38 01 26 68 8e 20 b6 c4 e6 e7 79 e1 63 8e 80 11  
    87.  01 c5 f1 dd 00 00 01 01 08 0a 00 57 a1 2e 00 14  
    88.  b7 25  
    89.   
    90. id: 10  
    91. Packet length: 66  
    92. Number of bytes: 66  
    93. Recieved time: Sat Apr 28 19:57:50 2012  
    94.  08 00 27 9c ff b1 0a 00 27 00 00 00 08 00 45 00  
    95.  00 34 d4 b2 40 00 40 06 74 5a c0 a8 38 01 c0 a8  
    96.  38 65 8e 20 26 68 79 e1 63 8e b6 c4 e6 e7 80 10  
    97.  00 e5 fb bc 00 00 01 01 08 0a 00 14 b7 25 00 57  
    98.  a1 2e  
    99.   
    100. id: 11  
    101. Packet length: 66  
    102. Number of bytes: 66  
    103. Recieved time: Sat Apr 28 19:57:50 2012  
    104.  08 00 27 9c ff b1 0a 00 27 00 00 00 08 00 45 00  
    105.  00 34 d4 b3 40 00 40 06 74 59 c0 a8 38 01 c0 a8  
    106.  38 65 8e 20 26 68 79 e1 63 8e b6 c4 e6 e7 80 11  
    107.  00 e5 fb bb 00 00 01 01 08 0a 00 14 b7 25 00 57  
    108.  a1 2e  
    109.   
    110. id: 12  
    111. Packet length: 66  
    112. Number of bytes: 66  
    113. Recieved time: Sat Apr 28 19:57:50 2012  
    114.  0a 00 27 00 00 00 08 00 27 9c ff b1 08 00 45 00  
    115.  00 34 47 ce 40 00 40 06 01 3f c0 a8 38 65 c0 a8  
    116.  38 01 26 68 8e 20 b6 c4 e6 e8 79 e1 63 8f 80 10  
    117.  01 c5 f1 dd 00 00 01 01 08 0a 00 57 a1 2e 00 14  
    118.  b7 25  
    119.   
    120. id: 13  
    121. Packet length: 66  
    122. Number of bytes: 66  
    123. Recieved time: Sat Apr 28 19:57:50 2012  
    124.  08 00 27 9c ff b1 0a 00 27 00 00 00 08 00 45 00  
    125.  00 34 d4 b4 40 00 40 06 74 58 c0 a8 38 01 c0 a8  
    126.  38 65 8e 20 26 68 79 e1 63 8f b6 c4 e6 e8 80 10  
    127.  00 e5 fb b9 00 00 01 01 08 0a 00 14 b7 26 00 57  
    128.  a1 2e  



    仔细研究即可发现服务器与客户机是如何通过tcp通信的。









    下面的这个程序可以获取eth0的ip和子网掩码等信息:

    test5:

    [cpp]  view plain  copy
    1. #include <stdio.h>  
    2. #include <stdlib.h>  
    3. #include <pcap.h>  
    4. #include <errno.h>  
    5. #include <netinet/in.h>  
    6. #include <arpa/inet.h>  
    7.   
    8. int main()  
    9. {  
    10.   /* ask pcap to find a valid device for use to sniff on */  
    11.   char * dev;   /* name of the device */   
    12.   char errbuf[PCAP_ERRBUF_SIZE];  
    13.   dev = pcap_lookupdev(errbuf);  
    14.   
    15.   /* error checking */  
    16.   if(!dev)  
    17.   {  
    18.     printf("pcap_lookupdev() error: %s\n", errbuf);  
    19.     exit(1);  
    20.   }  
    21.   
    22.   /* print out device name */  
    23.   printf("dev name: %s\n", dev);  
    24.   
    25.   /* ask pcap for the network address and mask of the device */  
    26.   bpf_u_int32 netp;   /* ip */  
    27.   bpf_u_int32 maskp;  /* subnet mask */  
    28.   int ret;            /* return code */  
    29.   ret = pcap_lookupnet(dev, &netp, &maskp, errbuf);  
    30.   
    31.   if(ret == -1)  
    32.   {  
    33.     printf("pcap_lookupnet() error: %s\n", errbuf);  
    34.     exit(1);  
    35.   }  
    36.   
    37.   /* get the network address in a human readable form */  
    38.   char * net;   /* dot notation of the network address */  
    39.   char * mask;  /* dot notation of the network mask */  
    40.   struct in_addr addr;  
    41.   
    42.   addr.s_addr = netp;  
    43.   net = inet_ntoa(addr);  
    44.   
    45.   if(!net)  
    46.   {  
    47.     perror("inet_ntoa() ip error: ");  
    48.     exit(1);  
    49.   }  
    50.   
    51.   printf("ip: %s\n", net);  
    52.   
    53.   /* do the same as above for the device's mask */  
    54.   addr.s_addr = maskp;  
    55.   mask = inet_ntoa(addr);  
    56.     
    57.   if(!mask)  
    58.   {  
    59.     perror("inet_ntoa() sub mask error: ");  
    60.     exit(1);  
    61.   }  
    62.     
    63.   printf("sub mask: %s\n", mask);  
    64.   
    65.   return 0;  
    66. }  


    结果如图:





    int pcap_lookupnet(const char * device, bpf_u_int32 * netp, bpf_u_int32 * maskp, char * errbuf)
    可以获取指定设备的ip地址,子网掩码等信息

    netp:传出参数,指定网络接口的ip地址

    maskp:传出参数,指定网络接口的子网掩码

    pcap_lookupnet()失败返回-1
    我们使用inet_ntoa()将其转换为可读的点分十进制形式的字符串






    本文的绝大部分来源于libpcap的官方文档:libpcapHakin9LuisMartinGarcia.pdf,可以在官网下载,文档只有9页,不过很详细,还包括了数据链路层,网络层,传输层,应用层等的分析。很好!


    更多参考可以man pcap


    最后为了方便大家,本文的所有代码和上述的pdf文档都一并上传上来了:

    http://download.csdn.net/detail/htttw/4264686


    展开全文
  • LIBPCAP

    2012-12-03 09:18:05
    libpcap使用 libpcap是一个网络数据包捕获函数库,功能非常强大,Linux下著名的tcpdump就是以它为基础的。今天我们利用它来完成一个我们自己的网络嗅探器(sniffer) 首先先介绍一下本次实验的环境: ...
    libpcap使用



    libpcap是一个网络数据包捕获函数库,功能非常强大,Linux下著名的tcpdump就是以它为基础的。今天我们利用它来完成一个我们自己的网络嗅探器(sniffer)




    首先先介绍一下本次实验的环境:
    Ubuntu 11.04,IP:192.168.1.1,广播地址:192.168.1.255,子网掩码:255.255.255.0
    可以使用下面的命令设置:
    sudo ifconfig eth0 192.168.1.1 broadcast 192.168.1.255 netmask 255.255.255.0





    1.安装
    http://www.tcpdump.org/下载libpcap(tcpdump的源码也可以从这个网站下载)
    解压
    ./configure
    make
    sudo make install





    2.使用
    安装好libpcap后,我们要使用它啦,先写一个简单的程序,并介绍如何使用libpcap库编译它:
    Makefile:
    [plain] view plain copy
    1. all: test.c  
    2.     gcc -g -Wall -o test test.c -lpcap  
    3.   
    4. clean:  
    5.     rm -rf *.o test  

    其后的程序的Makefile均类似,故不再重复


    test1.c
    1. #include <pcap.h>  
    2. #include <stdio.h>  
    3.   
    4. int main()  
    5. {  
    6.   char errBuf[PCAP_ERRBUF_SIZE], * device;  
    7.     
    8.   device = pcap_lookupdev(errBuf);  
    9.     
    10.   if(device)  
    11.   {  
    12.     printf("success: device: %s\n", device);  
    13.   }  
    14.   else  
    15.   {  
    16.     printf("error: %s\n", errBuf);  
    17.   }  
    18.     
    19.   return 0;  
    20. }  





    可以成功编译,不过运行的时候却提示找不到libpcap.so.1,因为libpcap.so.1默认安装到了/usr/local/lib下,我们做一个符号链接到/usr/lib/下即可






    运行test的时候输出"no suitable device found",原因是我们没有以root权限运行,root权限运行后就正常了







    下面开始正式讲解如何使用libpcap:
    首先要使用libpcap,我们必须包含pcap.h头文件,可以在/usr/local/include/pcap/pcap.h找到,其中包含了每个类型定义的详细说明




    1.获取网络接口
    首先我们需要获取监听的网络接口:
    我们可以手动指定或让libpcap自动选择,先介绍如何让libpcap自动选择:
    char * pcap_lookupdev(char * errbuf)
    上面这个函数返回第一个合适的网络接口的字符串指针,如果出错,则errbuf存放出错信息字符串,errbuf至少应该是PCAP_ERRBUF_SIZE个字节长度的。注意,很多libpcap函数都有这个参数。
    pcap_lookupdev()一般可以在跨平台的,且各个平台上的网络接口名称都不相同的情况下使用。
    如果我们手动指定要监听的网络接口,则这一步跳过,我们在第二步中将要监听的网络接口字符串硬编码在pcap_open_live里。




    2.释放网络接口
    在操作为网络接口后,我们应该要释放它:
    void pcap_close(pcap_t * p)
    该函数用于关闭pcap_open_live()获取的pcap_t的网络接口对象并释放相关资源。




    3.打开网络接口
    获取网络接口后,我们需要打开它:
    pcap_t * pcap_open_live(const char * device, int snaplen, int promisc, int to_ms, char * errbuf)
    上面这个函数会返回指定接口的pcap_t类型指针,后面的所有操作都要使用这个指针。
    第一个参数是第一步获取的网络接口字符串,可以直接使用硬编码。
    第二个参数是对于每个数据包,从开头要抓多少个字节,我们可以设置这个值来只抓每个数据包的头部,而不关心具体的内容。典型的以太网帧长度是1518字节,但其他的某些协议的数据包会更长一点,但任何一个协议的一个数据包长度都必然小于65535个字节。
    第三个参数指定是否打开混杂模式(Promiscuous Mode),0表示非混杂模式,任何其他值表示混合模式。如果要打开混杂模式,那么网卡必须也要打开混杂模式,可以使用如下的命令打开eth0混杂模式:
    ifconfig eth0 promisc
    第四个参数指定需要等待的毫秒数,超过这个数值后,第3步获取数据包的这几个函数就会立即返回。0表示一直等待直到有数据包到来。
    第五个参数是存放出错信息的数组。




    4.获取数据包
    打开网络接口后就已经开始监听了,那如何知道收到了数据包呢?有下面3种方法:
    a)
    u_char * pcap_next(pcap_t * p, struct pcap_pkthdr * h)
    如果返回值为NULL,表示没有抓到包
    第一个参数是第2步返回的pcap_t类型的指针
    第二个参数是保存收到的第一个数据包的pcap_pkthdr类型的指针
    pcap_pkthdr类型的定义如下:
    1. struct pcap_pkthdr  
    2. {  
    3.   struct timeval ts;    /* time stamp */  
    4.   bpf_u_int32 caplen;   /* length of portion present */  
    5.   bpf_u_int32 len;      /* length this packet (off wire) */  
    6. };  

    注意这个函数只要收到一个数据包后就会立即返回


    b)
    int pcap_loop(pcap_t * p, int cnt, pcap_handler callback, u_char * user)
    第一个参数是第2步返回的pcap_t类型的指针
    第二个参数是需要抓的数据包的个数,一旦抓到了cnt个数据包,pcap_loop立即返回。负数的cnt表示pcap_loop永远循环抓包,直到出现错误。
    第三个参数是一个回调函数指针,它必须是如下的形式:
    void callback(u_char * userarg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
    第一个参数是pcap_loop的最后一个参数,当收到足够数量的包后pcap_loop会调用callback回调函数,同时将pcap_loop()的user参数传递给它
    第二个参数是收到的数据包的pcap_pkthdr类型的指针
    第三个参数是收到的数据包数据




    c)
    int pcap_dispatch(pcap_t * p, int cnt, pcap_handler callback, u_char * user)

    这个函数和pcap_loop()非常类似,只是在超过to_ms毫秒后就会返回(to_ms是pcap_open_live()的第4个参数)


    例子:

    test2:

    1. #include <pcap.h>  
    2. #include <time.h>  
    3. #include <stdlib.h>  
    4. #include <stdio.h>  
    5.   
    6. int main()  
    7. {  
    8.   char errBuf[PCAP_ERRBUF_SIZE], * devStr;  
    9.     
    10.   /* get a device */  
    11.   devStr = pcap_lookupdev(errBuf);  
    12.     
    13.   if(devStr)  
    14.   {  
    15.     printf("success: device: %s\n", devStr);  
    16.   }  
    17.   else  
    18.   {  
    19.     printf("error: %s\n", errBuf);  
    20.     exit(1);  
    21.   }  
    22.     
    23.   /* open a device, wait until a packet arrives */  
    24.   pcap_t * device = pcap_open_live(devStr, 65535, 1, 0, errBuf);  
    25.     
    26.   if(!device)  
    27.   {  
    28.     printf("error: pcap_open_live(): %s\n", errBuf);  
    29.     exit(1);  
    30.   }  
    31.   
    32.   /* wait a packet to arrive */  
    33.   struct pcap_pkthdr packet;  
    34.   const u_char * pktStr = pcap_next(device, &packet);  
    35.   
    36.   if(!pktStr)  
    37.   {  
    38.     printf("did not capture a packet!\n");  
    39.     exit(1);  
    40.   }  
    41.     
    42.   printf("Packet length: %d\n", packet.len);  
    43.   printf("Number of bytes: %d\n", packet.caplen);  
    44.   printf("Recieved time: %s\n", ctime((const time_t *)&packet.ts.tv_sec));   
    45.     
    46.   pcap_close(device);  
    47.     
    48.   return 0;  
    49. }  






    打开两个终端,先ping 192.168.1.10,由于我们的ip是192.168.1.1,因此我们可以收到广播的数据包,另一个终端运行test,就会抓到这个包。







    下面的这个程序会把收到的数据包内容全部打印出来,运行方式和上一个程序一样:
    test3:
    1. #include <pcap.h>  
    2. #include <time.h>  
    3. #include <stdlib.h>  
    4. #include <stdio.h>  
    5.   
    6. void getPacket(u_char * arg, const struct pcap_pkthdr * pkthdr, const u_char * packet)  
    7. {  
    8.   int * id = (int *)arg;  
    9.     
    10.   printf("id: %d\n", ++(*id));  
    11.   printf("Packet length: %d\n", pkthdr->len);  
    12.   printf("Number of bytes: %d\n", pkthdr->caplen);  
    13.   printf("Recieved time: %s", ctime((const time_t *)&pkthdr->ts.tv_sec));   
    14.     
    15.   int i;  
    16.   for(i=0; i<pkthdr->len; ++i)  
    17.   {  
    18.     printf(" %02x", packet[i]);  
    19.     if( (i + 1) % 16 == 0 )  
    20.     {  
    21.       printf("\n");  
    22.     }  
    23.   }  
    24.     
    25.   printf("\n\n");  
    26. }  
    27.   
    28. int main()  
    29. {  
    30.   char errBuf[PCAP_ERRBUF_SIZE], * devStr;  
    31.     
    32.   /* get a device */  
    33.   devStr = pcap_lookupdev(errBuf);  
    34.     
    35.   if(devStr)  
    36.   {  
    37.     printf("success: device: %s\n", devStr);  
    38.   }  
    39.   else  
    40.   {  
    41.     printf("error: %s\n", errBuf);  
    42.     exit(1);  
    43.   }  
    44.     
    45.   /* open a device, wait until a packet arrives */  
    46.   pcap_t * device = pcap_open_live(devStr, 65535, 1, 0, errBuf);  
    47.     
    48.   if(!device)  
    49.   {  
    50.     printf("error: pcap_open_live(): %s\n", errBuf);  
    51.     exit(1);  
    52.   }  
    53.     
    54.   /* wait loop forever */  
    55.   int id = 0;  
    56.   pcap_loop(device, -1, getPacket, (u_char*)&id);  
    57.     
    58.   pcap_close(device);  
    59.   
    60.   return 0;  
    61. }  






    从上图可以看出,如果我们没有按Ctrl+c,test会一直抓到包,因为我们将pcap_loop()设置为永远循环




    由于ping属于icmp协议,并且发出icmp协议数据包之前必须先通过arp协议获取目的主机的mac地址,因此我们抓到的包是arp协议的,而arp协议的数据包长度正好是42字节(14字节的以太网帧头+28字节的arp数据)。具体内容请参考相关网络协议说明。






    5.分析数据包
    我们既然已经抓到数据包了,那么我们要开始分析了,这部分留给读者自己完成,具体内容可以参考相关的网络协议说明。在本文的最后,我会示范性的写一个分析arp协议的sniffer,仅供参考。要特别注意一点,网络上的数据是网络字节顺序的,因此分析前需要转换为主机字节顺序(ntohs()函数)




    6.过滤数据包
    我们抓到的数据包往往很多,如何过滤掉我们不感兴趣的数据包呢?
    几乎所有的操作系统(BSD, AIX, Mac OS, Linux等)都会在内核中提供过滤数据包的方法,主要都是基于BSD Packet Filter(BPF)结构的。libpcap利用BPF来过滤数据包。
    过滤数据包需要完成3件事:
    a) 构造一个过滤表达式
    b) 编译这个表达式
    c) 应用这个过滤器



    a)

    BPF使用一种类似于汇编语言的语法书写过滤表达式,不过libpcap和tcpdump都把它封装成更高级且更容易的语法了,具体可以man tcpdump,以下是一些例子:
    src host 192.168.1.177
    只接收源ip地址是192.168.1.177的数据包


    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个字节)


    b)
    构造完过滤表达式后,我们需要编译它,使用如下函数:
    int pcap_compile(pcap_t * p, struct bpf_program * fp, char * str, int optimize, bpf_u_int32 netmask)
    fp:这是一个传出参数,存放编译后的bpf
    str:过滤表达式
    optimize:是否需要优化过滤表达式
    metmask:简单设置为0即可


    c)
    最后我们需要应用这个过滤表达式:
    int pcap_setfilter(pcap_t * p,  struct bpf_program * fp)
    第二个参数fp就是前一步pcap_compile()的第二个参数




    应用完过滤表达式之后我们便可以使用pcap_loop()或pcap_next()等抓包函数来抓包了。


    下面的程序演示了如何过滤数据包,我们只接收目的端口是80的数据包:
    test4.c
    1. #include <pcap.h>  
    2. #include <time.h>  
    3. #include <stdlib.h>  
    4. #include <stdio.h>  
    5.   
    6. void getPacket(u_char * arg, const struct pcap_pkthdr * pkthdr, const u_char * packet)  
    7. {  
    8.   int * id = (int *)arg;  
    9.     
    10.   printf("id: %d\n", ++(*id));  
    11.   printf("Packet length: %d\n", pkthdr->len);  
    12.   printf("Number of bytes: %d\n", pkthdr->caplen);  
    13.   printf("Recieved time: %s", ctime((const time_t *)&pkthdr->ts.tv_sec));   
    14.     
    15.   int i;  
    16.   for(i=0; i<pkthdr->len; ++i)  
    17.   {  
    18.     printf(" %02x", packet[i]);  
    19.     if( (i + 1) % 16 == 0 )  
    20.     {  
    21.       printf("\n");  
    22.     }  
    23.   }  
    24.     
    25.   printf("\n\n");  
    26. }  
    27.   
    28. int main()  
    29. {  
    30.   char errBuf[PCAP_ERRBUF_SIZE], * devStr;  
    31.     
    32.   /* get a device */  
    33.   devStr = pcap_lookupdev(errBuf);  
    34.     
    35.   if(devStr)  
    36.   {  
    37.     printf("success: device: %s\n", devStr);  
    38.   }  
    39.   else  
    40.   {  
    41.     printf("error: %s\n", errBuf);  
    42.     exit(1);  
    43.   }  
    44.     
    45.   /* open a device, wait until a packet arrives */  
    46.   pcap_t * device = pcap_open_live(devStr, 65535, 1, 0, errBuf);  
    47.     
    48.   if(!device)  
    49.   {  
    50.     printf("error: pcap_open_live(): %s\n", errBuf);  
    51.     exit(1);  
    52.   }  
    53.     
    54.   /* construct a filter */  
    55.   struct bpf_program filter;  
    56.   pcap_compile(device, &filter, "dst port 80", 1, 0);  
    57.   pcap_setfilter(device, &filter);  
    58.     
    59.   /* wait loop forever */  
    60.   int id = 0;  
    61.   pcap_loop(device, -1, getPacket, (u_char*)&id);  
    62.     
    63.   pcap_close(device);  
    64.   
    65.   return 0;  
    66. }  




    在下面的这一个例子中,客户机通过tcp的9732端口连接服务器,发送字符'A',之后服务器将'A'+1即'B'返回给客户机,具体实现可以参考:http://blog.csdn.net/htttw/article/details/7519964


    服务器的ip是192.168.56.101,客户机的ip是192.168.56.1
    服务器:



    Makefile:

    [plain] view plain copy
    1. all: tcp_client.c tcp_server.c  
    2.     gcc -g -Wall -o tcp_client tcp_client.c  
    3.     gcc -g -Wall -o tcp_server tcp_server.c  
    4.   
    5. clean:  
    6.     rm -rf *.o tcp_client tcp_server  


    tcp_server:

    1. #include <sys/types.h>  
    2. #include <sys/socket.h>  
    3. #include <netinet/in.h>  
    4. #include <arpa/inet.h>  
    5. #include <unistd.h>  
    6. #include <stdlib.h>  
    7. #include <stdio.h>  
    8.   
    9. #define PORT 9832  
    10. #define SERVER_IP "192.168.56.101"  
    11.   
    12. int main()  
    13. {  
    14.   /* create a socket */  
    15.   int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);  
    16.     
    17.   struct sockaddr_in server_addr;  
    18.   server_addr.sin_family = AF_INET;  
    19.   server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);  
    20.   server_addr.sin_port = htons(PORT);  
    21.     
    22.   /* bind with the local file */  
    23.   bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));  
    24.     
    25.   /* listen */  
    26.   listen(server_sockfd, 5);  
    27.     
    28.   char ch;  
    29.   int client_sockfd;  
    30.   struct sockaddr_in client_addr;  
    31.   socklen_t len = sizeof(client_addr);  
    32.   while(1)  
    33.   {  
    34.     printf("server waiting:\n");  
    35.       
    36.     /* accept a connection */  
    37.     client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);  
    38.       
    39.     /* exchange data */  
    40.     read(client_sockfd, &ch, 1);  
    41.     printf("get char from client: %c\n", ch);  
    42.     ++ch;  
    43.     write(client_sockfd, &ch, 1);  
    44.       
    45.     /* close the socket */  
    46.     close(client_sockfd);  
    47.   }  
    48.     
    49.   return 0;  
    50. }  


    tcp_client:

    1. #include <sys/types.h>  
    2. #include <sys/socket.h>  
    3. #include <netinet/in.h>  
    4. #include <arpa/inet.h>  
    5. #include <unistd.h>  
    6. #include <stdlib.h>  
    7. #include <stdio.h>  
    8.   
    9. #define PORT 9832  
    10. #define SERVER_IP "192.168.56.101"  
    11.   
    12. int main()  
    13. {  
    14.   /* create a socket */  
    15.   int sockfd = socket(AF_INET, SOCK_STREAM, 0);  
    16.     
    17.   struct sockaddr_in address;  
    18.   address.sin_family = AF_INET;  
    19.   address.sin_addr.s_addr = inet_addr(SERVER_IP);  
    20.   address.sin_port = htons(PORT);  
    21.     
    22.   /* connect to the server */  
    23.   int result = connect(sockfd, (struct sockaddr *)&address, sizeof(address));  
    24.   if(result == -1)  
    25.   {  
    26.     perror("connect failed: ");  
    27.     exit(1);  
    28.   }  
    29.     
    30.   /* exchange data */  
    31.   char ch = 'A';  
    32.   write(sockfd, &ch, 1);  
    33.   read(sockfd, &ch, 1);  
    34.   printf("get char from server: %c\n", ch);  
    35.     
    36.   /* close the socket */  
    37.   close(sockfd);  
    38.     
    39.   return 0;  
    40. }  



    运行方法如下,首先在服务器上运行tcp_server,然后运行我们的监听器,然后在客户机上运行tcp_client,注意,我们可以先清空arp缓存,这样就可以看到整个通信过程(包括一开始的arp广播)
    在客户机上运行下列命令来清空记录服务器的arp缓存:
    sudo arp -d 192.168.56.101
    arp -a后发现已经删除了记录服务器的arp缓存




    抓包的结果如下所示,由于包太多了,无法全部截图,因此我把所有内容保存在下面的文本中了:



    全部的包如下:

    [plain] view plain copy
    1. hutao@hutao-VirtualBox:~/test3$ sudo ./test  
    2. success: device: eth0  
    3. id: 1  
    4. Packet length: 60  
    5. Number of bytes: 60  
    6. Recieved time: Sat Apr 28 19:57:50 2012  
    7.  ff ff ff ff ff ff 0a 00 27 00 00 00 08 06 00 01  
    8.  08 00 06 04 00 01 0a 00 27 00 00 00 c0 a8 38 01  
    9.  00 00 00 00 00 00 c0 a8 38 65 00 00 00 00 00 00  
    10.  00 00 00 00 00 00 00 00 00 00 00 00  
    11.   
    12. id: 2  
    13. Packet length: 42  
    14. Number of bytes: 42  
    15. Recieved time: Sat Apr 28 19:57:50 2012  
    16.  0a 00 27 00 00 00 08 00 27 9c ff b1 08 06 00 01  
    17.  08 00 06 04 00 02 08 00 27 9c ff b1 c0 a8 38 65  
    18.  0a 00 27 00 00 00 c0 a8 38 01  
    19.   
    20. id: 3  
    21. Packet length: 74  
    22. Number of bytes: 74  
    23. Recieved time: Sat Apr 28 19:57:50 2012  
    24.  08 00 27 9c ff b1 0a 00 27 00 00 00 08 00 45 00  
    25.  00 3c d4 af 40 00 40 06 74 55 c0 a8 38 01 c0 a8  
    26.  38 65 8e 20 26 68 79 e1 63 8c 00 00 00 00 a0 02  
    27.  39 08 d4 13 00 00 02 04 05 b4 04 02 08 0a 00 14  
    28.  b7 23 00 00 00 00 01 03 03 06  
    29.   
    30. id: 4  
    31. Packet length: 74  
    32. Number of bytes: 74  
    33. Recieved time: Sat Apr 28 19:57:50 2012  
    34.  0a 00 27 00 00 00 08 00 27 9c ff b1 08 00 45 00  
    35.  00 3c 00 00 40 00 40 06 49 05 c0 a8 38 65 c0 a8  
    36.  38 01 26 68 8e 20 b6 c4 e6 e5 79 e1 63 8d a0 12  
    37.  38 90 f1 e5 00 00 02 04 05 b4 04 02 08 0a 00 57  
    38.  a1 2c 00 14 b7 23 01 03 03 05  
    39.   
    40. id: 5  
    41. Packet length: 66  
    42. Number of bytes: 66  
    43. Recieved time: Sat Apr 28 19:57:50 2012  
    44.  08 00 27 9c ff b1 0a 00 27 00 00 00 08 00 45 00  
    45.  00 34 d4 b0 40 00 40 06 74 5c c0 a8 38 01 c0 a8  
    46.  38 65 8e 20 26 68 79 e1 63 8d b6 c4 e6 e6 80 10  
    47.  00 e5 fb c1 00 00 01 01 08 0a 00 14 b7 24 00 57  
    48.  a1 2c  
    49.   
    50. id: 6  
    51. Packet length: 67  
    52. Number of bytes: 67  
    53. Recieved time: Sat Apr 28 19:57:50 2012  
    54.  08 00 27 9c ff b1 0a 00 27 00 00 00 08 00 45 00  
    55.  00 35 d4 b1 40 00 40 06 74 5a c0 a8 38 01 c0 a8  
    56.  38 65 8e 20 26 68 79 e1 63 8d b6 c4 e6 e6 80 18  
    57.  00 e5 ba b7 00 00 01 01 08 0a 00 14 b7 25 00 57  
    58.  a1 2c 41  
    59.   
    60. id: 7  
    61. Packet length: 66  
    62. Number of bytes: 66  
    63. Recieved time: Sat Apr 28 19:57:50 2012  
    64.  0a 00 27 00 00 00 08 00 27 9c ff b1 08 00 45 00  
    65.  00 34 47 cb 40 00 40 06 01 42 c0 a8 38 65 c0 a8  
    66.  38 01 26 68 8e 20 b6 c4 e6 e6 79 e1 63 8e 80 10  
    67.  01 c5 f1 dd 00 00 01 01 08 0a 00 57 a1 2e 00 14  
    68.  b7 25  
    69.   
    70. id: 8  
    71. Packet length: 67  
    72. Number of bytes: 67  
    73. Recieved time: Sat Apr 28 19:57:50 2012  
    74.  0a 00 27 00 00 00 08 00 27 9c ff b1 08 00 45 00  
    75.  00 35 47 cc 40 00 40 06 01 40 c0 a8 38 65 c0 a8  
    76.  38 01 26 68 8e 20 b6 c4 e6 e6 79 e1 63 8e 80 18  
    77.  01 c5 f1 de 00 00 01 01 08 0a 00 57 a1 2e 00 14  
    78.  b7 25 42  
    79.   
    80. id: 9  
    81. Packet length: 66  
    82. Number of bytes: 66  
    83. Recieved time: Sat Apr 28 19:57:50 2012  
    84.  0a 00 27 00 00 00 08 00 27 9c ff b1 08 00 45 00  
    85.  00 34 47 cd 40 00 40 06 01 40 c0 a8 38 65 c0 a8  
    86.  38 01 26 68 8e 20 b6 c4 e6 e7 79 e1 63 8e 80 11  
    87.  01 c5 f1 dd 00 00 01 01 08 0a 00 57 a1 2e 00 14  
    88.  b7 25  
    89.   
    90. id: 10  
    91. Packet length: 66  
    92. Number of bytes: 66  
    93. Recieved time: Sat Apr 28 19:57:50 2012  
    94.  08 00 27 9c ff b1 0a 00 27 00 00 00 08 00 45 00  
    95.  00 34 d4 b2 40 00 40 06 74 5a c0 a8 38 01 c0 a8  
    96.  38 65 8e 20 26 68 79 e1 63 8e b6 c4 e6 e7 80 10  
    97.  00 e5 fb bc 00 00 01 01 08 0a 00 14 b7 25 00 57  
    98.  a1 2e  
    99.   
    100. id: 11  
    101. Packet length: 66  
    102. Number of bytes: 66  
    103. Recieved time: Sat Apr 28 19:57:50 2012  
    104.  08 00 27 9c ff b1 0a 00 27 00 00 00 08 00 45 00  
    105.  00 34 d4 b3 40 00 40 06 74 59 c0 a8 38 01 c0 a8  
    106.  38 65 8e 20 26 68 79 e1 63 8e b6 c4 e6 e7 80 11  
    107.  00 e5 fb bb 00 00 01 01 08 0a 00 14 b7 25 00 57  
    108.  a1 2e  
    109.   
    110. id: 12  
    111. Packet length: 66  
    112. Number of bytes: 66  
    113. Recieved time: Sat Apr 28 19:57:50 2012  
    114.  0a 00 27 00 00 00 08 00 27 9c ff b1 08 00 45 00  
    115.  00 34 47 ce 40 00 40 06 01 3f c0 a8 38 65 c0 a8  
    116.  38 01 26 68 8e 20 b6 c4 e6 e8 79 e1 63 8f 80 10  
    117.  01 c5 f1 dd 00 00 01 01 08 0a 00 57 a1 2e 00 14  
    118.  b7 25  
    119.   
    120. id: 13  
    121. Packet length: 66  
    122. Number of bytes: 66  
    123. Recieved time: Sat Apr 28 19:57:50 2012  
    124.  08 00 27 9c ff b1 0a 00 27 00 00 00 08 00 45 00  
    125.  00 34 d4 b4 40 00 40 06 74 58 c0 a8 38 01 c0 a8  
    126.  38 65 8e 20 26 68 79 e1 63 8f b6 c4 e6 e8 80 10  
    127.  00 e5 fb b9 00 00 01 01 08 0a 00 14 b7 26 00 57  
    128.  a1 2e  



    仔细研究即可发现服务器与客户机是如何通过tcp通信的。









    下面的这个程序可以获取eth0的ip和子网掩码等信息:

    test5:

    1. #include <stdio.h>  
    2. #include <stdlib.h>  
    3. #include <pcap.h>  
    4. #include <errno.h>  
    5. #include <netinet/in.h>  
    6. #include <arpa/inet.h>  
    7.   
    8. int main()  
    9. {  
    10.   /* ask pcap to find a valid device for use to sniff on */  
    11.   char * dev;   /* name of the device */   
    12.   char errbuf[PCAP_ERRBUF_SIZE];  
    13.   dev = pcap_lookupdev(errbuf);  
    14.   
    15.   /* error checking */  
    16.   if(!dev)  
    17.   {  
    18.     printf("pcap_lookupdev() error: %s\n", errbuf);  
    19.     exit(1);  
    20.   }  
    21.   
    22.   /* print out device name */  
    23.   printf("dev name: %s\n", dev);  
    24.   
    25.   /* ask pcap for the network address and mask of the device */  
    26.   bpf_u_int32 netp;   /* ip */  
    27.   bpf_u_int32 maskp;  /* subnet mask */  
    28.   int ret;            /* return code */  
    29.   ret = pcap_lookupnet(dev, &netp, &maskp, errbuf);  
    30.   
    31.   if(ret == -1)  
    32.   {  
    33.     printf("pcap_lookupnet() error: %s\n", errbuf);  
    34.     exit(1);  
    35.   }  
    36.   
    37.   /* get the network address in a human readable form */  
    38.   char * net;   /* dot notation of the network address */  
    39.   char * mask;  /* dot notation of the network mask */  
    40.   struct in_addr addr;  
    41.   
    42.   addr.s_addr = netp;  
    43.   net = inet_ntoa(addr);  
    44.   
    45.   if(!net)  
    46.   {  
    47.     perror("inet_ntoa() ip error: ");  
    48.     exit(1);  
    49.   }  
    50.   
    51.   printf("ip: %s\n", net);  
    52.   
    53.   /* do the same as above for the device's mask */  
    54.   addr.s_addr = maskp;  
    55.   mask = inet_ntoa(addr);  
    56.     
    57.   if(!mask)  
    58.   {  
    59.     perror("inet_ntoa() sub mask error: ");  
    60.     exit(1);  
    61.   }  
    62.     
    63.   printf("sub mask: %s\n", mask);  
    64.   
    65.   return 0;  
    66. }  


    结果如图:





    int pcap_lookupnet(const char * device, bpf_u_int32 * netp, bpf_u_int32 * maskp, char * errbuf)
    可以获取指定设备的ip地址,子网掩码等信息

    netp:传出参数,指定网络接口的ip地址

    maskp:传出参数,指定网络接口的子网掩码

    pcap_lookupnet()失败返回-1
    我们使用inet_ntoa()将其转换为可读的点分十进制形式的字符串






    本文的绝大部分来源于libpcap的官方文档:libpcapHakin9LuisMartinGarcia.pdf,可以在官网下载,文档只有9页,不过很详细,还包括了数据链路层,网络层,传输层,应用层等的分析。很好!


    更多参考可以man pcap


    最后为了方便大家,本文的所有代码和上述的pdf文档都一并上传上来了:

    http://download.csdn.net/detail/htttw/4264686



    完成!

    展开全文
  • Libpcap

    2014-06-18 13:33:53
    libpcap提供的接口函数主要实现和封装了与数据包截获有关的过程。   在Linux系统中要从链路层(MAC)直接收发数据,比较普遍的做法就是用libpcap和libnet两个动态库来实现。 libpcap apt-get install flex ...
    libnet提供的接口函数主要实现和封装了数据包的构造和发送过程。
    libpcap提供的接口函数主要实现和封装了与数据包截获有关的过程。
        
    在Linux系统中要从 链路层(MAC)直接收发数据,比较普遍的做法就是用libpcap和libnet两个动态库来实现。
    libpcap
    apt-get install flex  apt-get install bison
    1 ./configure
    2 make
    3 make install

    执行:
    test.c
    #include<stdio.h>
    #include<pcap.h>
    int main()
    {
      char errBuf[PCAP_ERRBUF_SIZE], * device;
      
      device = pcap_lookupdev(errBuf);
      
      if(device)
      {
        printf("success: device: %s\n", device);
      }
      else
      {
        printf("error: %s\n", errBuf);
      }
      return 0;
    }
    编译:gcc test.c -o test  -lpcap
    执行:./test  ( 运行的时候却提示找不到libpcap.so.1,因为libpcap.so.1默认安装到了/usr/local/lib下,                我们做一个符号链接到/usr/lib/下即可)
    符号链接:ln -s /usr/local/lib/libpcap.so.1 /usr/lib/libpcap.so.1

    如何使用libpcap
    1.获取网络接口
    可以手动指定或让libpcap自动选择,先介绍如何让libpcap自动选择:
    char * pcap_lookupdev(char * errbuf)
    上面这个函数返回第一个合适的网络接口的字符串指针,如果出错,则errbuf存放出错信息字符串,errbuf至少应该是PCAP_ERRBUF_SIZE个字节长度的。注意,很多libpcap函数都有这个参数。
    pcap_lookupdev()一般可以在跨平台的,且各个平台上的网络接口名称都不相同的情况下使用。
    如果我们手动指定要监听的网络接口,则这一步跳过,我们在第二步中将要监听的网络接口字符串硬编码在pcap_open_live里。

    2.释放网络接口
    在操作为网络接口后,我们应该要释放它:
    void pcap_close(pcap_t * p)
    该函数用于关闭pcap_open_live()获取的pcap_t的网络接口对象并释放相关资源。


    3.打开网络接口
    获取网络接口后,我们需要打开它:
    pcap_t * pcap_open_live(const char * device, int snaplen, int promisc, int to_ms, char * errbuf)
    上面这个函数会返回指定接口的pcap_t类型指针,后面的所有操作都要使用这个指针。
    第一个参数是第一步获取的网络接口字符串,可以直接使用硬编码。
    第二个参数是对于每个数据包,从开头要抓多少个字节,我们可以设置这个值来只抓每个数据包的头部,而不关心具体的内容。典型的以太网帧长度是1518字节,但其他的某些协议的数据包会更长一点,但任何一个协议的一个数据包长度都必然小于65535个字节。
    第三个参数指定是否打开混杂模式(Promiscuous Mode),0表示非混杂模式,任何其他值表示混合模式。如果要打开混杂模式,那么网卡必须也要打开混杂模式,可以使用如下的命令打开eth0混杂模式:ifconfig eth0 promisc
    第四个参数指定需要等待的毫秒数,超过这个数值后,第3步获取数据包的这几个函数就会立即返回。0表示一直等待直到有数据包到来。
    第五个参数是存放出错信息的数组。

    4.获取数据包
    打开网络接口后就已经开始监听了,那如何知道收到了数据包呢?有下面3种方法:
    a)
    u_char * pcap_next(pcap_t * p, struct pcap_pkthdr * h)
    如果返回值为NULL,表示没有抓到包
    第一个参数 是第2步返回的pcap_t类型的指针
    第二个参数 是保存收到的第一个数据包的pcap_pkthdr类型的指针
    pcap_pkthdr类型的定义如下:
    1. struct pcap_pkthdr
    2. {
    3. struct timeval ts;
    4. bpf_u_int32 caplen;
    5. bpf_u_int32 len;
    6. };
    注意这个函数只要收到一个数据包后就会立即返回
    b)
    int pcap_loop(pcap_t * p, int cnt, pcap_handler callback, u_char * user)
    第一个参数是第2步返回的pcap_t类型的指针
    第二个参数是需要抓的数据包的个数,一旦抓到了cnt个数据包,pcap_loop立即返回。负数的cnt表示pcap_loop永远循环抓包,直到出现错误。
    第三个参数是一个回调函数指针,它必须是如下的形式:
    void callback(u_char * userarg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
    第一个参数是
    pcap_loop的最后一个参数,当收到足够数量的包后pcap_loop会调用callback回调函数,同时将pcap_loop()的user参数传递给它
    第二个参数是收到的数据包的pcap_pkthdr类型的指针
    第三个参数是收到的数据包数据


    c)
    int pcap_dispatch(pcap_t * p, int cnt, pcap_handler callback, u_char * user)

    这个函数和pcap_loop()非常类似,只是在超过to_ms毫秒后就会返回(to_ms是pcap_open_live()的第4个参数)

    5.分析数据包
    我们既然已经抓到数据包了,那么我们要开始分析了,这部分留给读者自己完成,具体内容可以参考相关的网络协议说明。在本文的最后,我会示范性的写一个分析arp协议的sniffer,仅供参考。要特别注意一点,网络上的数据是网络字节顺序的,因此分析前需要转换为主机字节顺序(ntohs()函数)

    6.过滤数据包
    我们抓到的数据包往往很多,如何过滤掉我们不感兴趣的数据包呢?
    几乎所有的操作系统(BSD, AIX, Mac OS, Linux等)都会在内核中提供过滤数据包的方法,主要都是基于BSD Packet Filter(BPF)结构的。libpcap利用BPF来过滤数据包。
    过滤数据包需要完成3件事:
    a) 构造一个过滤表达式
    b) 编译这个表达式
    c) 应用这个过滤器

    a)

    BPF使用一种类似于汇编语言的语法书写过滤表达式,不过libpcap和tcpdump都把它封装成更高级且更容易的语法了,具体可以man tcpdump,以下是一些例子:
    src host 192.168.1.177
    只接收源ip地址是192.168.1.177的数据包

    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个字节)
    b)
    构造完过滤表达式后,我们需要编译它,使用如下函数:
    int pcap_compile(pcap_t * p, struct bpf_program * fp, char * str, int optimize, bpf_u_int32 netmask)
    fp:这是一个传出参数,存放编译后的bpf
    str:过滤表达式
    optimize:是否需要优化过滤表达式
    metmask:简单设置为0即可
    c)
    最后我们需要应用这个过滤表达式:
    int pcap_setfilter(pcap_t * p, struct bpf_program * fp)
    第二个参数fp就是前一步pcap_compile()的第二个参数
    应用完过滤表达式之后我们便可以使用pcap_loop()或pcap_next()等抓包函数来抓包了。

    pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
           p:表示pcap会话句柄;fp:存放编译以后的规则;str:规则表达式格式的过滤规则(filter),
           同tcpdump中的filter;optimize:制定优化选项:0 false, 1 true;
           netmask:监听接口的网络掩码;返回值:-1表示操作失败,其他值表成功。

    7.获取eth0的ip和子网掩码等信息:

    int pcap_lookupnet(const char * device, bpf_u_int32 * netp, bpf_u_int32 * maskp, char * errbuf)
    可以获取指定设备的ip地址,子网掩码等信息

    netp:传出参数,指定网络接口的ip地址

    maskp:传出参数,指定网络接口的子网掩码

    pcap_lookupnet()失败返回-1


    参考:http://www.tcpdump.org/

         http://blog.csdn.net/htttw/article/details/7521053

    ===================================================================================================================

    libpcap 发送数据

    int pcap_sendpacket(pcap_t *, const u_char *, int);

    说明:该函数可以发送一个原始数据包到网络上。Buf包含要发送到网络上的数据包的数据(包括协议头)。注意,CRC不用包含,因为它是网卡驱动计算然后添加的。

    注意到缓冲区的数据是不经过处理就被发送到网络上的,这就意味着应用程序必须要创建正确的协议头以保证发送一些有用的信息。

    参考:http://linux.chinaunix.net/techdoc/net/2007/08/14/965314.shtml

    展开全文
  • [](https://www.cnblogs.com/10087622blog/p/8320234.html "") 就以上链接里面提到的redhat对tpacketv3支持有问题,需要关闭tpacketv3,想请教一下各位怎么关闭?是在libpcap源码中屏蔽相关代码吗?
  • libpcap详解

    千次阅读 2016-10-25 16:22:20
    libpcap(Packet Capture Library),即数据包捕获函数库,是Unix/Linux平台下的网络数据包捕获函数库。它是一个独立于系统的用户层包捕获的API接口,为底层网络监测提供了一个可移植的框架。 一、libpcap工作原理 ...
    libpcap(Packet Capture Library),即数据包捕获函数库,是Unix/Linux平台下的网络数据包捕获函数库。它是一个独立于系统的用户层包捕获的API接口,为底层网络监测提供了一个可移植的框架。 
    

    一、libpcap工作原理
    libpcap主要由两部份组成:网络分接头(Network Tap)和数据过滤器(Packet Filter)。网络分接头从网络设备驱动程序中收集数据拷贝,过滤器决定是否接收该数据包。Libpcap利用BSD Packet Filter(BPF)算法对网卡接收到的链路层数据包进行过滤。BPF算法的基本思想是在有BPF监听的网络中,网卡驱动将接收到的数据包复制一份交给BPF过滤器,过滤器根据用户定义的规则决定是否接收此数据包以及需要拷贝该数据包的那些内容,然后将过滤后的数据给与过滤器相关联的上层应用程序。
    libpcap的包捕获机制就是在数据链路层加一个旁路处理。当一个数据包到达网络接口时,libpcap首先利用已经创建的Socket从链路层驱动程序中获得该数据包的拷贝,再通过Tap函数将数据包发给BPF过滤器。BPF过滤器根据用户已经定义好的过滤规则对数据包进行逐一匹配,匹配成功则放入内核缓冲区,并传递给用户缓冲区,匹配失败则直接丢弃。如果没有设置过滤规则,所有数据包都将放入内核缓冲区,并传递给用户层缓冲区。

    二、libpcap的抓包框架
    pcap_lookupdev()函数用于查找网络设备,返回可被pcap_open_live()函数调用的网络设备名指针。
    pcap_open_live()函数用于打开网络设备,并且返回用于捕获网络数据包的数据包捕获描述字。对于此网络设备的操作都要基于此网络设备描述字。
    pcap_lookupnet()函数获得指定网络设备的网络号和掩码。
    pcap_compile()函数用于将用户制定的过滤策略编译到过滤程序中。
    pcap_setfilter()函数用于设置过滤器。
    pcap_loop()函数pcap_dispatch()函数用于捕获数据包,捕获后还可以进行处理,此外pcap_next()和pcap_next_ex()两个函数也可以用来捕获数据包。
    pcap_close()函数用于关闭网络设备,释放资源。

    其实pcap的应用程序格式很简单,总的来说可以可以分为以下5部分,相信看了以下的5部分,大概能对pcap的总体布局有个大概的了解了吧:
    1.我们从决定用哪一个接口进行嗅探开始。在Linux中,这可能是eth0,而在BSD系统中则可能是xl1等等。我们也可以用一个字符串来定义这个设备,或者采用pcap提供的接口名来工作。
    2.初始化pcap。在这里我们要告诉pcap对什么设备进行嗅探。假如愿意的话,我们还可以嗅探多个设备。怎样区分它们呢?使用 文件句柄。就像打开一个文件进行读写一样,必须命名我们的嗅探“会话”,以此使它们各自区别开来。
    3.假如我们只想嗅探特定的传输(如TCP/IP包,发往端口23的包等等),我们必须创建一个规则集合,编译并且使用它。这个过程分为三个相互紧密关联的阶段。规则集合被置于一个字符串内,并且被转换成能被pcap读的格式(因此编译它)。编译实际上就是在我们的程序里调用一个不被外部程序使用的函数。接下来我们要告诉 pcap使用它来过滤出我们想要的那一个会话。
    4.最后,我们告诉pcap进入它的主体执行循环。在这个阶段内pcap一直工作到它接收了所有我们想要的包为止。每当它收到一个包就调用另一个已经定义好的函数,这个函数可以做我们想要的任何工作,它可以剖析所部获的包并给用户打印出结果,它可以将结果保存为一个文件,或者什么也不作。
    5.在嗅探到所需的数据后,我们要关闭会话并结束。

    三、实现libpcap的每一个步骤
    (1)设置设备
    这是很简单的。有两种方法设置想要嗅探的设备。
    第一种,我们可以简单的让用户告诉我们。考察下面的程序:
       #include
       #include
       int main(int argc, char *argv[])
       {
       char *dev = argv[1];
       printf("Device: %s", dev);
       return(0);
       }
    用户通过传递给程序的第一个参数来指定设备。字符串“dev”以pcap能“理解”的格式保存了我们要嗅探的接口的名字(当然,用户必须给了我们一个真正存在的接口)。
    另一种也是同样的简单。来看这段程序:
       #include
       #include
       int main()
       {
       char *dev, errbuf[PCAP_ERRBUF_SIZE];
       dev = pcap_lookupdev(errbuf);
       printf("Device: %s", dev);
       return(0);
       }
    (2)打开设备进行嗅探
    创建一个嗅探会话的任务真的非常简单。为此,我们使用pcap_open_live()函数。此函数的原型(根据pcap的手册页)如下:
       pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
    其第一个参数是我们在上一节中指定的设备,snaplen是整形的,它定义了将被pcap捕捉的最大字节数。当promisc设为true时将置指定接口为混杂模式(然而,当它置为false时接口仍处于混杂模式的非凡情况也是有可能的)。to_ms是读取时的超时值,单位是毫秒(假如为0则一直嗅探直到错误发生,为-1则不确定)。最后,ebuf是一个我们可以存入任何错误信息的字符串(就像上面的errbuf)。此函数返回其会话句柄。
    混杂模式与非混杂模式的区别:这两种方式区别很大。一般来说,非混杂模式的嗅探器中,主机仅嗅探那些跟它直接有关的通信,如发向它的,从它发出的,或经它路由的等都会被嗅探器捕捉。而在混杂模式中则嗅探传输线路上的所有通信。在非交换式网络中,这将是整个网络的通信。这样做最明显的优点就是使更多的包被嗅探到,它们因你嗅探网络的原因或者对你有帮助,或者没有。但是,混杂模式是可被探测到的。一个主机可以通过高强度的测试判定另一台主机是否正在进行混杂模式的嗅探。其次,它仅在非交换式的网络环境中有效工作(如集线器,或者交换中的ARP层面)。再次,在高负荷的网络中,主机的系统资源将消耗的非常严重。
    (3)过滤通信
    实现这一过程由pcap_compile()与pcap_setfilter()这两个函数完成。
    在使用我们自己的过滤器前必须编译它。过滤表达式被保存在一个字符串中(字符数组)。其句法在tcpdump的手册页中被证实非常好。我建议你亲自阅读它。但是我们将使用简单的测试表达式,这样你可能很轻易理解我的例子。
    我们调用pcap_compile()来编译它,其原型是这样定义的:
       int pcap_compile(pcap_t *p, strUCt bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
    第一个参数是会话句柄。接下来的是我们存储被编译的过滤器版本的地址的引用。再接下来的则是表达式本身,存储在规定的字符串格式里。再下边是一个定义表达式是否被优化的整形量(0为false,1为true,标准规定)。最后,我们必须指定应用此过滤器的网络掩码。函数返回-1为失败,其他的任何值都表明是成功的。
    表达式被编译之后就可以使用了。现在进入pcap_setfilter()。仿照我们介绍pcap的格式,先来看一看pcap_setfilter()的原型:
       int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
    这非常直观,第一个参数是会话句柄,第二个参数是被编译表达式版本的引用(可推测出它与pcap_compile()的第二个参数相同)。
    下面的代码示例可能能使你更好的理解:
       #include
       pcap_t *handle; /* 会话的句柄 */
       char dev[] = "eth0"; /* 执行嗅探的设备 */
       char errbuf[PCAP_ERRBUF_SIZE]; /* 存储错误 信息的字符串 */
       struct bpf_program filter; /*已经编译好的过滤表达式*/
       char filter_app[] = "port 23"; /* 过滤表达式*/
       bpf_u_int32 mask; /* 执行嗅探的设备的网络掩码 */
       bpf_u_int32 net; /* 执行嗅探的设备的IP地址 */
       pcap_lookupnet(dev, &net, &mask, errbuf);
       handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);
       pcap_compile(handle, &filter, filter_app, 0, net);
       pcap_setfilter(handle, &filter);
    这个程序使嗅探器嗅探经由端口23的所有通信,使用混杂模式,设备是eth0。
    (4)实际的嗅探
    有两种手段捕捉包。我们可以一次只捕捉一个包,也可以进入一个循环,等捕捉到多个包再进行处理。我们将先看看怎样去捕捉单个包,然后再看看使用循环的方法。为此,我们使用函数pcap_next()。
    pcap_next()的原型及其简单:
       u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
    第一个参数是会话句柄,第二个参数是指向一个包括了当前数据包总体信息(被捕捉时的时间,包的长度,其被指定的部分长度)的结构体的指针(在这里只有一个片断,只作为一个示例)。pcap_next()返回一个u_char指针给被这个结构体描述的包。我们将稍后讨论这种实际读取包本身的手段。
       这里有一个演示怎样使用pcap_next()来嗅探一个包的例子:
       #include
       #include
       int main()
       {
       pcap_t *handle; /* 会话句柄 */
       char *dev; /* 执行嗅探的设备 */
       char errbuf[PCAP_ERRBUF_SIZE]; /* 存储错误信息的字符串 */
      
       struct bpf_program filter; /* 已经编译好的过滤器 */
       char filter_app[] = "port 23"; /* 过滤表达式 */
       bpf_u_int32 mask; /* 所在网络的掩码 */
       bpf_u_int32 net; /* 主机的IP地址 */
       struct pcap_pkthdr header; /* 由pcap.h定义 */
       const u_char *packet; /* 实际的包 */
       /* Define the device */
       dev = pcap_lookupdev(errbuf);
       /* 探查设备属性 */
       pcap_lookupnet(dev, &net, &mask, errbuf);
       /* 以混杂模式打开会话 */
       handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);
       /* 编译并应用过滤器 */
       pcap_compile(handle, &filter, filter_app, 0, net);
       pcap_setfilter(handle, &filter);
       /* 截获一个包 */
       packet = pcap_next(handle, &header);
       /* 打印它的长度 */
       printf("Jacked a packet with length of [%d]
       ", header.len);
       /* 关闭会话 */
       pcap_close(handle);
       return(0);
       }
    这个程序嗅探被pcap_lookupdev()返回的设备并将它置为混杂模式。它发现第一个包经过端口23(telnet)并且告诉用户此包的大小(以字 节为单位)。这个程序又包含了一个新的调用pcap_close(),我们将在后面讨论(尽管它的名字就足够证实它自己的作用)。
    实际上很少有嗅探程序会真正的使用pcap_next()。通常,它们使用pcap_loop()或者 pcap_dispatch()(它就是用了pcap_loop())。
    pcap_loop()的原型如下:
       int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
    第一个参数是会话句柄,接下来是一个整型,它告诉pcap_loop()在返回前应捕捉多少个数据包(若为负值则表示应该一直工作直至错误发生)。第三个参数是回调函数的名称(正像其标识符所指,无括号)。最后一个参数在有些应用里有用,但更多时候则置为NULL。假设我们有我们自己的想送往回调函数的参数,另外还有pcap_loop()发送的参数,这就需要用到它。很明显,必须是一个u_char类型的指针以确保结果正确;正像我们稍后见到的,pcap使用了很有意思的方法以u_char指针的形势传递信息。pcap_dispatch()的用法几乎相同。唯一不同的是它们如何处理超时(还记得在调用pcap_open_live()时怎样设置超时吗?这就是它起作用的地方)。Pcap_loop()忽略超时而pcap_dispatch()则不。关于它们之间区别的更深入的讨论请参见pcap的手册页。
    回调函数的原型:
       void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);
    让我们更细致的考察它。首先,你会注重到该函数返回void类型,这是符合逻辑的,因为pcap_loop()不知道如何去处理一个回调返回值。第一个参数相应于pcap_loop()的最后一个参数。每当回调函数被老婆 调用时,无论最后一个参数传给pcap_loop()什么值,这个值都会传给我们回调函数的第一个参数。第二个参数是pcap头文件定义的,它包括数据包被嗅探的时间、大小等信息。结构体pcap_pkhdr在pcap.h中定义如下:
       struct pcap_pkthdr {
       struct timeval ts; /* 时间戳 */
       bpf_u_int32 caplen; /* 已捕捉部分的长度 */
       bpf_u_int32 len; /* 该包的脱机长度 */
       };
    这些量都相当明了。最后一个参数在它们中是最有意思的,也最让pcap程序新手感到迷惑。这又是一个u_char指针,它包含了被pcap_loop()嗅探到的所有包。
    但是你怎样使用这个我们在原型里称为packet的变量呢?一个数据包包含许多属性,因此你可以想象它不只是一个字符串,而实质上是一个结构体的集合(比如,一个TCP/IP包会有一个以太网的头部,一个IP头部,一个TCP头部,还有此包的有效载荷)。这个u_char就是这些结构体的串联版本。为了使用它,我们必须作一些有趣的匹配工作。
    下面这些是一些数据包的结构体:
       /* 以太网帧头部 */
       struct sniff_ethernet {
       u_char ether_dhost[ETHER_ADDR_LEN]; /* 目的主机的地址 */
       u_char ether_shost[ETHER_ADDR_LEN]; /* 源主机的地址 */
       u_short ether_type; /* IP? ARP? RARP? etc */
       };
       /* IP数据包的头部 */
       struct sniff_ip {
       #if BYTE_ORDER == LITTLE_ENDIAN
       u_int ip_hl:4, /* 头部长度 */
       ip_v:4; /* 版本号 */
       #if BYTE_ORDER == BIG_ENDIAN
       u_int ip_v:4, /* 版本号 */
       ip_hl:4; /* 头部长度 */
       #endif
       #endif /* not _IP_VHL */
       u_char ip_tos; /* 服务的类型 */
       u_short ip_len; /* 总长度 */
       u_short ip_id; /*包标志号 */
       u_short ip_off; /* 碎片偏移 */
       #define IP_RF 0x8000 /* 保留的碎片标志 */
       #define IP_DF 0x4000 /* dont fragment flag */
       #define IP_MF 0x2000 /* 多碎片标志*/
       #define IP_OFFMASK 0x1fff /*分段位 */
       u_char ip_ttl; /* 数据包的生存时间 */
       u_char ip_p; /* 所使用的协议 */
       u_short ip_sum; /* 校验和 */
       struct in_addr ip_src,ip_dst; /* 源地址、目的地址*/
       };
       /* TCP 数据包的头部 */
       struct sniff_tcp {
       u_short th_sport; /* 源端口 */
       u_short th_dport; /* 目的端口 */
       tcp_seq th_seq; /* 包序号 */
       tcp_seq th_ack; /* 确认序号 */
       #if BYTE_ORDER == LITTLE_ENDIAN
       u_int th_x2:4, /* 还没有用到 */
       th_off:4; /* 数据偏移 */
       #endif
       #if BYTE_ORDER == BIG_ENDIAN
       u_int th_off:4, /* 数据偏移*/
       th_x2:4; /*还没有用到 */
       #endif
       u_char th_flags;
       #define TH_FIN 0x01
       #define TH_SYN 0x02
       #define TH_RST 0x04
       #define TH_PUSH 0x08
       #define TH_ACK 0x10
       #define TH_URG 0x20
       #define TH_ECE 0x40
       #define TH_CWR 0x80
       #define TH_FLAGS (TH_FINTH_SYNTH_RSTTH_ACKTH_URGTH_ECETH_CWR)
       u_short th_win; /* TCP滑动窗口 */
       u_short th_sum; /* 头部校验和 */
       u_short th_urp; /* 紧急服务位 */
       };
    pcap嗅探数据包时正是使用的这些结构。接下来,它简单的创建一个u_char字符串并且将这些结构体填入。那么我们怎样才能区分它们呢?预备好见证指针最实用的好处之一吧。
    我们再一次假定要对以太网上的TCP/IP包进行处理。同样的手段可以应用于任何数据包,唯一的区别是你实际所使用的结构体的类型。让我们从声明分解u_char包的变量开始:
       const struct sniff_ethernet *ethernet; /* 以太网帧头部*/
       const struct sniff_ip *ip; /* IP包头部 */
       const struct sniff_tcp *tcp; /* TCP包头部 */
       const char *payload; /* 数据包的有效载荷*/
       /*为了让它的可读性好,我们计算每个结构体中的变量大小*/
       int size_ethernet = sizeof(struct sniff_ethernet);
       int size_ip = sizeof(struct sniff_ip);
       int size_tcp = sizeof(struct sniff_tcp);
       现在我们开始让人感到有些神秘的匹配:
       ethernet = (struct sniff_ethernet*)(packet);
       ip = (struct sniff_ip*)(packet + size_ethernet);
       tcp = (struct sniff_tcp*)(packet + size_ethernet + size_ip);
       payload = (u_char *)(packet + size_ethernet + size_ip + size_tcp);
      
    此处如何工作?考虑u_char在内存中的层次。基本的,当pcap将这些结构体填入u_char的时候是将这些数据存入一个字符串中,那个字符串将被送入我们的回调函数中。反向转换是这样的,不考虑这些结构体制中的值,它们的大小将是一致的。例如在我的平台上,一个sniff_ethernet结构体的大小是14字节。一个sniff_ip结构体是20字节,一个sniff_tcp结构体也是20字节。 u_char指针正是包含了内存地址的一个变量,这也是指针的实质,它指向内存的一个区域。简单而言,我们说指针指向的地址为x,假如三个结构体恰好线性排列,第一个(sniff_ethernet)被装载到内存地址的x处则我们很轻易的发现其他结构体的地址,让我们以表格显示之:
       Variable Location (in bytes)
       sniff_ethernet X
       sniff_ip X + 14
       sniff_tcp X + 14 + 20
       payload X + 14 + 20 + 20
    结构体sniff_ethernet正好在x处,紧接着它的sniff_ip则位于x加上它本身占用的空间(此例为14字节),依此类推可得全部地址。
    注重:你没有假定你的变量也是同样大小是很重要的。你应该总是使用sizeof()来确保尺寸的正确。这是因为这些结构体中的每个成员在不同平台下可以有不同的尺寸。

    下面是主要函数接口:
    pcap_t *pcap_open_live(char *device, int snaplen,
       int promisc, int to_ms, char *ebuf)
       获得用于捕获网络数据包的数据包捕获描述字。device参数为指定打开
       的网络设备名。snaplen参数定义捕获数据的最大字节数。promisc指定
       是否将网络接口置于混杂模式。to_ms参数指定超时时间(毫秒)。
       ebuf参数则仅在pcap_open_live()函数出错返回NULL时用于传递错误消
       息。
    pcap_t *pcap_open_offline(char *fname, char *ebuf)
       打开以前保存捕获数据包的文件,用于读取。fname参数指定打开的文
       件名。该文件中的数据格式与tcpdump和tcpslice兼容。"-"为标准输
       入。ebuf参数则仅在pcap_open_offline()函数出错返回NULL时用于传
       递错误消息。
    pcap_dumper_t *pcap_dump_open(pcap_t *p, char *fname)
       打开用于保存捕获数据包的文件,用于写入。fname参数为"-"时表示
       标准输出。出错时返回NULL。p参数为调用pcap_open_offline()或
       pcap_open_live()函数后返回的pcap结构指针。fname参数指定打开
       的文件名。如果返回NULL,则可调用pcap_geterr()函数获取错误消
       息。

    char *pcap_lookupdev(char *errbuf)
       用于返回可被pcap_open_live()或pcap_lookupnet()函数调用的网络
       设备名指针。如果函数出错,则返回NULL,同时errbuf中存放相关的
       错误消息。
    int pcap_lookupnet(char *device, bpf_u_int32 *netp,
       bpf_u_int32 *maskp, char *errbuf)
       获得指定网络设备的网络号和掩码。netp参数和maskp参数都是
       bpf_u_int32指针。如果函数出错,则返回-1,同时errbuf中存放相
       关的错误消息。
    int pcap_dispatch(pcap_t *p, int cnt,
       pcap_handler callback, u_char *user)
       捕获并处理数据包。cnt参数指定函数返回前所处理数据包的最大值。
       cnt=-1表示在一个缓冲区中处理所有的数据包。cnt=0表示处理所有
       数据包,直到产生以下错误之一:读取到EOF;超时读取。callback
       参数指定一个带有三个参数的回调函数,这三个参数为:一个从
       pcap_dispatch()函数传递过来的u_char指针,一个pcap_pkthdr结构
       的指针,和一个数据包大小的u_char指针。如果成功则返回读取到的
       字节数。读取到EOF时则返回零值。出错时则返回-1,此时可调用
       pcap_perror()或pcap_geterr()函数获取错误消息。
    int pcap_loop(pcap_t *p, int cnt,
       pcap_handler callback, u_char *user)
       功能基本与pcap_dispatch()函数相同,只不过此函数在cnt个数据包
       被处理或出现错误时才返回,但读取超时不会返回。而如果为
       pcap_open_live()函数指定了一个非零值的超时设置,然后调用
       pcap_dispatch()函数,则当超时发生时pcap_dispatch()函数会返回。
       cnt参数为负值时pcap_loop()函数将始终循环运行,除非出现错误。
    void pcap_dump(u_char *user, struct pcap_pkthdr *h,
       u_char *sp)
       向调用pcap_dump_open()函数打开的文件输出一个数据包。该函数可
       作为pcap_dispatch()函数的回调函数。
    int pcap_compile(pcap_t *p, struct bpf_program *fp,
       char *str, int optimize, bpf_u_int32 netmask)
       将str参数指定的字符串编译到过滤程序中。fp是一个bpf_program结
       构的指针,在pcap_compile()函数中被赋值。optimize参数控制结果
       代码的优化。netmask参数指定本地网络的网络掩码。
    int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
       指定一个过滤程序。fp参数是bpf_program结构指针,通常取自
       pcap_compile()函数调用。出错时返回-1;成功时返回0。
    u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
       返回指向下一个数据包的u_char指针。
    int pcap_datalink(pcap_t *p)
       返回数据链路层类型,例如DLT_EN10MB。
    int pcap_snapshot(pcap_t *p)
       返回pcap_open_live被调用后的snapshot参数值。
    int pcap_is_swapped(pcap_t *p)
       返回当前系统主机字节与被打开文件的字节顺序是否不同。
    int pcap_major_version(pcap_t *p)
       返回写入被打开文件所使用的pcap函数的主版本号。
    int pcap_minor_version(pcap_t *p)
       返回写入被打开文件所使用的pcap函数的辅版本号。
    int pcap_stats(pcap_t *p, struct pcap_stat *ps)
       向pcap_stat结构赋值。成功时返回0。这些数值包括了从开始
       捕获数据以来至今共捕获到的数据包统计。如果出错或不支持
       数据包统计,则返回-1,且可调用pcap_perror()或
       pcap_geterr()函数来获取错误消息。
    FILE *pcap_file(pcap_t *p)
       返回被打开文件的文件名。
    int pcap_fileno(pcap_t *p)
       返回被打开文件的文件描述字号码。
    void pcap_perror(pcap_t *p, char *prefix)
       在标准输出设备上显示最后一个pcap库错误消息。以prefix参
       数指定的字符串为消息头。
    char *pcap_geterr(pcap_t *p)
       返回最后一个pcap库错误消息。
    char *pcap_strerror(int error)
       如果strerror()函数不可用,则可调用pcap_strerror函数替代。
    void pcap_close(pcap_t *p)
       关闭p参数相应的文件,并释放资源。
    展开全文
  • libpcap使用

    2015-02-12 15:57:31
    libpcap使用   分类:  C/C++2012-04-28 18:3434984人阅读评论(61)收藏举报 structserver网络tcpcmakefile libpcap使用 libpcap是一个网络数据包捕获函数库,功能非常强大,Linux下著名的...
  • Libpcap简介

    2018-12-07 13:40:42
    1.Libpcap简介   Libpcap是Packet Capture Libray的英文缩写,即数据包捕获函数库。该库提供的C函数接口用于捕捉经过指定网络接口的数据包,该接口应该是被设为混杂模式。这个在原始套接子中有提到。  著名的...
  • libpcap介绍

    2020-05-16 14:19:39
    libpcap(Packet Capture Library),即数据包捕获函数库,是Unix/Linux平台下的网络数据包捕获函数库。它是一个独立于系统的用户层包捕获的API接口,为底层网络监测提供了一个可移植的框架。 一、libpcap...
  • Libpcap详解

    2017-11-09 10:50:57
    libpcap详解 2010-12-01 22:07 libpcap(Packet Capture Library),即数据包捕获函数库,是Unix/Linux平台下的网络数据包捕获函数库。 它是一个独立于系统的用户层包捕获的API接口,为底层网络监测...
  • Libpcap 总结

    2015-06-03 13:38:24
    1. Libpcap 简介 Libpcap是Packet Capture Libray的英文缩写, 即C语言库。该库提供的C函数接口,提供了用户级别的网络数据包捕获接口。在安装了 libpcap 的平台上,以 libpcap 为接口写的程序、应用,能够自由地跨...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,235
精华内容 1,294
关键字:

libpcap关闭