当前位置:首页 > 嵌入式培训 > 嵌入式学习 > 讲师博文 > libpcap使用

libpcap使用 时间:2018-09-29      来源:未知

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.安装

在//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'返回给客户机,具体实现可以参考://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.

上一篇:【C语言】21-结构体

下一篇:libpcap使用

热点文章推荐
华清学员就业榜单
高薪学员经验分享
热点新闻推荐
前台专线:010-82525158 企业培训洽谈专线:010-82525379 院校合作洽谈专线:010-82525379 Copyright © 2004-2022 北京华清远见科技集团有限公司 版权所有 ,京ICP备16055225号-5京公海网安备11010802025203号

回到顶部