Linux内核网络数据包捕获

libpcap实现机制及接口函数

2016-12-09  本文已影响0人  shaarawy18

1.Libpcap 的工作原理

Libpcap的工作原理可以描述为,当一个数据包到达网卡时,通过网络分接口(即旁路机制)将数据包发给BPF过滤器,匹配通过的数据包可以被libpcap利用创建的套接字PF_PACKET从链路层驱动程序中获得。进而在用户空间提供独立于系统的用户级API接口。

流程图示意图

一个数据包的捕捉分为三个主要部分

  • 面向底层包捕获、

这与Linux操作系统对数据包的处理流程是相同的

网卡->网卡驱动->数据链路层->IP层->传输层->应用程序

2、Libpcap的实现机制

这里实现的包捕获机制是在数据链路层增加一个旁路处理,并不干扰系统自身的网路协议栈的处理,对发送和接收的数据包通过Linux内核做过滤和缓冲处理,最后直接传递给上层应用程序。因此libpcap在捕获到达网卡的数据包后绕开了传统linux协议栈处理,直接使用链路层PF_PACKET协议族原始套接字方式向用户空间传递报文。

libpcap捕获报文机制示意图

接下来,对照上图分层解释报文从网卡最终到达用户空间的处理流程:

---------------------------物理层

  • 分配一个缓冲区sk_buff,把接收的数据拷贝进去;(第一次拷贝)
  • 对缓冲区结构内的一些参数做初始化以告知较高层协议数据是什么类型skb->protocol;
  • 非NAPI:调用netif_rx( )函数通知内核,将帧放入CPU的softnet_data->input_pkt_queue。netif_rx会调用网络接口函数netif_rx_schedule(使用softdate_net结构中内嵌的backlog_dev作为dev参数)
  • NAPI:帧存放在每个设备自己的队列之中。调用netif_rx_schedule函数(直接以对应设备的dev结构为参数)
  • 浏览poll_list设备列表,这些设备的入口队列都有数据;
  • 非NAPI设备:执行process_backlog函数(backlog_dev->poll)

若时间片用完或者配额用尽,将该设备放置列表尾部等待下一次中断到来时继续被调用;若处理完input_pkt_queue列表中的全部报文,则将设备退出poll_list同时打开设备中断服务继续监听下一个报文到来。

  • NAPI设备:执行poll函数

dev->poll可以做一些轮询的工作,如果网络设备已经接收了多个报文,可以一次性处理。就算设备此刻所接收到的报文都已经处理完了,驱动程序也可以根据某种方式预判设备在很短的一段时间内还将收到报文,于是依然将自己对应的dev结构留在poll_list中,处于轮询状态。增大了报文接收的平均延时,但避免了大量中断带来的开销。dev设备退出poll_list同时打开设备中断服务。

  • 如果有抓包程序,由网络分接口进入BPF过滤器,将规则匹配的报文拷贝到系统内核缓存 (第二次拷贝)否则直接丢弃数据包;*注 : linux 在 PF_PACKET 类型的 socket 上支持内核过滤。Linux 内核允许我们把一个名为 LPF(Linux Packet Filter) 的过滤器直接放到 PF_PACKET 类型 socket 的处理过程中,过滤器在网卡接收中断执行后立即执行 *
  • 处理数据链路层的桥接功能;
  • 根据skb->protocol字段确定上层协议并提交给网络层处理-->进入网络协议栈

|
|
| 内核空间
|
/
---------------------------数据链路层
/
|
| 用户空间
|
|

fd=socket(PF_PACKET,sock_RAW,htons(ETH_P_ALL))
libpcap 函数库注册的报文接收类型为 ETH_P_ALL,即接收所有的网络数据帧,其处理函数为 packet_rcv()。该函数工作在数据链路层。

进而调用recvfrom函数获得捕获的报文 (需要进行系统调用):
packet_rcv() 函数将直接调用 skb_queue_tail() 将数据报文存放在代表相应网络连接控制结构(struct sock)的接收队列 receive_queue 中。这样数据报文在接收过程中就绕过了 TCP 层和 IP 层繁琐的协议处理过程。最后,睡眠在 sk 等待队列上的函数 packet_recvmsg() 会接收链路层数据帧并将该数据帧直接拷贝到应用程序缓冲区中。


3、BPF过滤器

BPF结构图

4、关键函数

skb = dev_alloc_skb(pkt_len + 5) ;
...  ...  ...
if (skb!=NULL) {
    skb->dev = dev;
    skb_reserve(skb,2);    //把IP对齐在16字节边界上
...  ...  ...
/*把DATA数据拷贝到sk_buff结构*/
...  ...  ...
skb->protocol = eth_type_trans(skb,dev) ;
netif_rx(skb) ;
dev->last_rx=jiffies;
...  ...  ...

}

5、用户级API

1)获取数据包捕获描述字
函数名称: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时用于传递错误消息。

2)打开保存捕获数据包文件
函数名称:pcap_t *pcap_open_offline(char *fname, char *ebuf)
函数功能:打开以前保存捕获数据包的文件,用于读取。
参数说明:fname参数指定打开的文件名。该文件中的数据格式与tcpdump和tcpslice兼容。”-“为标准输入。ebuf参数则仅在pcap_open_offline()函数出错返回NULL时用于传递错误消息。

3)转储数据包
函数名称: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()函数获取错误消息。

4)查找网络设备
函数名称:char *pcap_lookupdev(char *errbuf)
函数功能:用于返回可被pcap_open_live()或pcap_lookupnet()函数调用的网络设备名指针。
返回值:如果函数出错,则返回NULL,同时errbuf中存放相关的错误消息。

5)获取网络号和掩码

函数名称:int pcap_lookupnet(char *device, bpf_u_int32 *netp,bpf_u_int32 *maskp, char *errbuf)
函数功能:获得指定网络设备的网络号和掩码。
参数说明:netp参数和maskp参数都是bpf_u_int32指针。
返回值:如果函数出错,则返回-1,同时errbuf中存放相关的错误消息。

6)捕获并处理数据包
** 函数名称**: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()函数获取错误消息。

** 7)捕获和处理数据包**
** 函数名称: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()函数将始终循环运行,除非出现错误。

** 8)输出数据包**
函数名称:void pcap_dump(u_char *user, struct pcap_pkthdr *h,u_char *sp)
** 函数功能**:向调用pcap_dump_open()函数打开的文件输出一个数据包。该函数可作为pcap_dispatch()函数的回调函数。

参数说明: 参数1: 所建立的文件pcap_dump_open()的返回值,要进行强制转换.;参数2: 数据包特有的内容.;参数 3: 数据包内容指针

9)编译字串至过滤程序
函数名称: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参数指定本地网络的网络掩码。

10)指定过滤程序
函数名称:int pcap_setfilter(pcap_t p, struct bpf_program fp)
函数功能:指定一个过滤程序。
参数说明:fp参数是bpf_program结构指针,通常取自pcap_compile()函数调用。
** 返回值
:出错时返回-1;成功时返回0

11)获取下一个数据包
函数名称:u_char pcap_next(pcap_t p, struct pcap_pkthdr *h)
** 函数功能
:返回指向下一个数据包的u_char指针

12)获取数据链路层类型
函数名称:int pcap_datalink(pcap_t *p)
** 函数功能**:返回数据链路层类型,例如DLT_EN10MB

13)获取快照参数值
函数名称:int pcap_snapshot(pcap_t *p)
** 函数功能**:返回pcap_open_live被调用后的snapshot参数值

14)检测字节顺序
函数名称:int pcap_is_swapped(pcap_t *p)
函数功能:返回当前系统主机字节与被打开文件的字节顺序是否不同

15)获取主版本号
** 函数名称**:int pcap_major_version(pcap_t *p)
函数功能:返回写入被打开文件所使用的pcap函数的主版本号

16)获取辅版本号
函数名称:int pcap_minor_version(pcap_t *p)
** 函数功能**:返回写入被打开文件所使用的pcap函数的辅版本号

17)结构赋值
函数名称:int pcap_stats(pcap_t *p, struct pcap_stat *ps)
函数功能:向pcap_stat结构赋值。成功时返回0。这些数值包括了从开始捕获数据以来至今共捕获到的数据包统计。如果出错或不支持数据包统计,则返回-1,且可调用pcap_perror()或pcap_geterr()函数来获取错误消息。

18)获取打开文件名
函数名称:FILE *pcap_file(pcap_t *p)
函数功能:返回被打开文件的文件名。

19)获取描述字号码
函数名称:int pcap_fileno(pcap_t *p)
函数功能:返回被打开文件的文件描述字号码

** 20)显示错误消息**
函数名称:void pcap_perror(pcap_t *p, char *prefix)
函数功能:在标准输出设备上显示最后一个pcap库错误消息。以prefix参数指定的字符串为消息头。

6、参考文档

上一篇下一篇

猜你喜欢

热点阅读