[ZLG-ARM] 经典**!

[复制链接]
2994|1
 楼主| reeper 发表于 2009-4-9 15:20 | 显示全部楼层 |阅读模式
本文的大纲如下:<br /><br />一、基础知识<br /><br />1.&nbsp;Netfilter<br /><br />2.&nbsp;Netlink机制<br /><br />二、IP&nbsp;Queue编程接口<br /><br />三、一个实现接收内核态发送的IP&nbsp;Queue数据包的用户态例程<br /><br />1.&nbsp;libipq.h<br /><br />2.&nbsp;libipq.c<br /><br />3.&nbsp;ipq_user.c<br /><br />四、应用程序的测试<br /><br />1.&nbsp;测试环境的建立<br /><br />2.&nbsp;程序的测试<br /><br /><br /><br />一、基础知识<br /><br />基础知识部分的很多部分内容都重点参考或者直接引用了《如何用IP&nbsp;Queue机制编写用户态防火墙》,原文的链接为:http://www.syue.com/Firewall/HTML/3357.html。在此,向该文的作者表示感谢。<br /><br /><br /><br />1.&nbsp;Netfilter<br /><br />Linux内核在Netfilter(下文简称NF)框架的基础上提供了IP&nbsp;Queue机制,使得基于用户态(User&nbsp;Mode)的防火墙开发成为可能。<br /><br /><br /><br />内核中NF对网络报文的处理这里不做详细描述。假设读者已经熟悉NF的工作原理和工作流程。但这里还是要简单介绍一下NF中各个钩子(hook)函数对数据包处理的返回值,即该函数告诉内核对该数据包的处理意见。所有的返回值如下:<br /><br /><br /><br />NF_DROP:&nbsp;丢弃该报文,释放所有与该报文相关的资源;<br /><br />NF_ACCEPT:&nbsp;接受该报文,并继续处理;<br /><br />NF_STOLEN:&nbsp;该报文已经被HOOK函数接管,协议栈无须继续处理;<br /><br />NF_QUEUE:&nbsp;将该报文传递到用户态去做进一步的处理;<br /><br />NF_REPEAT:&nbsp;再次调用本HOOK函数。<br /><br /><br /><br />当HOOK处理函数返回值为NF_QUEUE时,内核协议栈将通过IP&nbsp;Queue机制把当前报文传递到用户态,由用户态的应用程序进行处理。这样,只要能够在相应的HOOK点上返回NF_QUEUE值,就可以将符合要求的报文传送到用户态去做进一步对报文行处理。随后,用户态程序会将处理后的报文以及对报文的处理意见(ACCEPT,DROP等)传递给内核协议栈。内核协议栈就会按照用户态对报文的处理意见将报文做接受、丢弃等处理。整个处理的过程就相当于一个用户态的防火墙,所有数据包的实质性处理都放在用户态进行。这样,即使是不具有深入内核知识的开发人员,也可以开发出适应一定应用场合的用户态防火墙。<br /><br /><br /><br />2.&nbsp;Netlink机制<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;前面讲到,所谓IP&nbsp;Queue机制,只是当NF上Hook函数对数据包处理的返回值为NF_QUEUE时,协议栈会将数据包交给内核中的ip_queue模块。而ip_queue又是怎么将数据包传递给用户态的呢?这里就涉及到在内核开发中常见的问题,如何将内核态的数据传递到用户态,实现内核空间和用户空间的通信。具体实现的方法有多种。本人的博客中也总结了若干种,并配有测试的例程:http://blog.chinaunix.net/u/33048/article.html.对于IP&nbsp;Queue,则是使用Netlink机制实现内核态和用户态的交互。<br /><br />NetLink是Linux系统特有的、基于socket编程接口的通信机制。它是一个面向数据报文的服务,并提供NETLINK_ROUTE(更新和修改路由操作)、NETLINK_FIREWALL&nbsp;(接受和发送IPv4协议NF传输的包,基于内核的ip_queue模块),NETLINK_ARPD(用户态ARP表操作)等多种通信协议。在创建基于IP&nbsp;Queue的NetLink&nbsp;Socket时,将采用如下系统调用:<br /><br />fd&nbsp;=&nbsp;socket(PF_NETLINK,&nbsp;SOCK_RAW,&nbsp;NETLINK_FIREWALL);<br />  这里,PF_NETLINK指明要创建NetLink&nbsp;Socket;SOCK_RAW指明采用原始套接字,也可以采用SOCK_DGRAM,因为NetLink机制的实现并不区分SOCK_RAW和SOCK_DGRAM;参数NETLINK_FIREWALL则指明通信协议采用IP&nbsp;Queue。<br /><br />既然IP&nbsp;Queue是基于NetLink的,其消息格式自然也遵从NetLink的规范。NetLink消息由两部分组成:消息头(struct&nbsp;nlmsghdr)和数据负载(data&nbsp;payload)。<br /><br />消息头的定义如下(include/linux/netlink.h):<br /><br /><blockquote>struct&nbsp;nlmsghdr<br /><br />{<br /><br />__u32&nbsp;nlmsg_len;&nbsp;/*消息长度*/<br /><br />__u16&nbsp;nlmsg_type;/*消息类型*/<br /><br />__u16&nbsp;nlmsg_flags;/*额外的标志*/<br /><br />__u32&nbsp;nlmsg_seq;&nbsp;/*序列号*/<br /><br />__u32&nbsp;nlmsg_pid;&nbsp;/*进程号*/<br /><br />}; </blockquote><br />struct&nbsp;nlmsghdr<br /><br />{<br /><br />__u32&nbsp;nlmsg_len;&nbsp;/*消息长度*/<br /><br />__u16&nbsp;nlmsg_type;/*消息类型*/<br /><br />__u16&nbsp;nlmsg_flags;/*额外的标志*/<br /><br />__u32&nbsp;nlmsg_seq;&nbsp;/*序列号*/<br /><br />__u32&nbsp;nlmsg_pid;&nbsp;/*进程号*/<br /><br />}; <br /><br />所有的IP&nbsp;Queue消息都将包含一个struct&nbsp;nlmsghdr消息头,具体的IP&nbsp;Queue消息则包含在NetLink消息的数据负载中。有关NetLink消息格式的详情可以参见手册页Netlink(7)。<br /><br /><br /><br />二、IP&nbsp;Queue编程接口<br /><br />使用IP&nbsp;Queue机制的程序必须包含如下的头文件:<br /><br />#include<br /><br />在这个头文件中定义了所有IP&nbsp;Queue消息的格式。以下谈到关于IP&nbsp;Queue的若干个数据结构以及宏定义都包含在该文头件。<br /><br />IP&nbsp;Queue消息可以分为两大类:由内核协议栈发给用户态进程的IP&nbsp;Queue消息和由用户态进程发给内核的IP&nbsp;Queue消息。<br /><br />由内核协议栈发给用户态进程的IP&nbsp;Queue消息(nlmsghdr.nlmsg_type&nbsp;=&nbsp;<br /><br />IPQM_PACKET),其数据结构为ipq_packet_msg_t,定义如下:<br /><br />/*&nbsp;Messages&nbsp;sent&nbsp;from&nbsp;kernel&nbsp;*/<br /><br />typedef&nbsp;struct&nbsp;ipq_packet_msg&nbsp;{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;long&nbsp;packet_id;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;报文的ID号&nbsp;*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;long&nbsp;mark;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;NF标记值&nbsp;*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;long&nbsp;timestamp_sec;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*报文到达时间(秒)&nbsp;&nbsp;*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;long&nbsp;timestamp_usec;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;报文到达时间(毫秒)&nbsp;*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;int&nbsp;hook;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;报文所处的NF&nbsp;hook点&nbsp;*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;char&nbsp;indev_name[IFNAMSIZ];&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;流入网口名称&nbsp;*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;char&nbsp;outdev_name[IFNAMSIZ];&nbsp;/*&nbsp;流出网口名称&nbsp;*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;short&nbsp;hw_protocol;&nbsp;&nbsp;&nbsp;&nbsp;/*硬件协议(网络顺序)*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;short&nbsp;hw_type;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;硬件类型&nbsp;*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;char&nbsp;hw_addrlen;&nbsp;/*硬件地址长度*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;char&nbsp;hw_addr[8];&nbsp;/*&nbsp;硬件地址&nbsp;*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;size_t&nbsp;data_len;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;报文数据的长度&nbsp;*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;char&nbsp;payload[0];&nbsp;&nbsp;/*&nbsp;报文本身的数据,可选&nbsp;*/<br /><br />}&nbsp;ipq_packet_msg_t;<br /><br /><br /><br />这个数据结构也被称为“报文的元数据”。个人理解,所谓“报文的元数据”应该是关于报文的摘要信息,而不包括报文数据的本身。从上面的这个结构体中也可以看出来。内核除可以单独向用户进程传递“报文的元数据”以外,也可以同时传递报文本身。此时,报文本身的数据将存储在ipq_packet_msg_t数据成员payload开始的地方。<br /><br />至于内核在什么情况下向用户传递报文的元数据,什么情况下向用户传递报文的元数据加报文本身的数据,那就要看用户所请求的模式了。下面将讲述用户态发到内核态消息的格式,也正好解答了我们这里提出的问题。<br /><br />用户态发到内核态的消息,其数据结构如下所示:<br /><br />typedef&nbsp;struct&nbsp;ipq_peer_msg&nbsp;{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;union&nbsp;{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_verdict_msg_t&nbsp;verdict;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_mode_msg_t&nbsp;mode;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;msg;<br /><br />}&nbsp;ipq_peer_msg_t;<br /><br /><br /><br />通过该数据结构可知,这类消息又分为“模式设置消息(nlmsghdr.nlmsg_type&nbsp;=&nbsp;IPQM_MODE)”和“断言消息(nlmsghdr.nlmsg_type&nbsp;=&nbsp;IPQM_VERDICT)”两个子类。<br /><br /><br /><br />“模式设置消息”的数据结构定义如下:<br /><br /> typedef&nbsp;struct&nbsp;ipq_mode_msg&nbsp;{<br /><br />  unsigned&nbsp;char&nbsp;value;/*&nbsp;请求的模式&nbsp;*/<br /><br />  size_t&nbsp;range;/*&nbsp;请求拷贝的报文长度*/<br /><br /> }&nbsp;ipq_mode_msg_t;<br /><br />这里,请求模式value的值可以是IPQ_COPY_NONE、IPQ_COPY_META和IPQ_COPY_PACKET,具体解释如下:<br /><br />(1)请求模式value为IPQ_COPY_NONE时,报文将被丢弃;<br /><br />(2)请求模式value为IPQ_COPY_META时,内核将在其后的报文传递中只传递“报文的元数据”。<br /><br />以上两种情形传递range的值将被内核忽略。内核中会将该值置为0。<br /><br />(3)请求模式value为IPQ_COPY_PACKET时,内核将同时传递“报文的元数据”和报文本身,报文本身的传递长度由ipq_mode_msg_t的另一个数据成员range指定。&nbsp;range的最大值不能超过IP报文的最大长度,也就是0xFFFF。否则,会被自动置为0xFFFF。如果请求的长度大于报文自身的长度,将会按照报文自身长度进行传递。<br /><br /><br /><br />另一子类即“断言消息”,其数据类型定义如下:<br />  typedef&nbsp;struct&nbsp;ipq_verdict_msg&nbsp;{<br /><br />unsigned&nbsp;int&nbsp;value;<br /><br />unsigned&nbsp;long&nbsp;id;<br /><br />size_t&nbsp;data_len;<br /><br />unsigned&nbsp;char&nbsp;payload[0];<br /><br />}&nbsp;ipq_verdict_msg_t;<br /><br /><br /><br />其中,value是用户态程序回传给内核的对当前报文的处理意见,可以是NF_ACCEPT或NF_DROP等值。id则是用以区分报文的标识号,即内核传来的ipq_packet_msg_t结构中的packet_id。当用户态程序修改了当前报文以后,需要将报文重新传递回内核,此时,新的报文内容必须存储在payload的开始处,并由data_len指明新报文的长度。<br /><br /><br /><br />从上述内容可以看出,在整个IP&nbsp;Queue的报文传递过程中,用户态程序和内核协议栈之间的互动顺序是:<br /><br />(1)用户态程序利用“模式设置消息”告诉内核协议栈所请求的报文传递模式;<br /><br />(2)根据这个模式,内核组织好等待传递的消息,通过NetLink&nbsp;Socket发给用户态程序;<br /><br />(3)用户态程序对接收到的数据包进行处理,得出该报文的处理意见(可能同时修改当前报文),并回传给内核。<br /><br /><br /><br />三、一个实现接收内核态发送的IP&nbsp;Queue数据包的用户态例程<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;由于IP&nbsp;Queue是使用Netlink机制进行内核态和用户态通信的。因此,用户态要接收内核态发送的IP&nbsp;Queue数据包,就需要设计相应的Netlink程序,也就是设计相应的基于Netlink的socket程序即可。这里,我不会详细介绍如何使用Netlink机制实现用户态和内核态进行通信。我假设阅读本文的朋友,已经熟悉了Netlink的使用。如果对Netlink的使用还不是很熟悉,那么可以参考独孤九贱大侠的**——《Linux&nbsp;用户态与内核态的交互——netlink&nbsp;篇》,其链接为:<br /><br />http://linux.chinaunix.net/bbs/thread-822500-1-1.html.<br /><br />这篇**提供了一个使用netlink的完整的例程,包括内核态和用户态。讲的非常清楚,我看完这篇**,又跑了一下上面提供的例程,基本上熟悉了Netlink的使用方法。<br /><br />当然,如果读者不想花时间再去了解netlink的话,也可以通过这篇**熟悉Netlink的使用。因为我这里提供的是完整的用户态例程,我会将源码完全提供出来,对于急于通过执行程序观察结果来学习Netlink和IP&nbsp;Queue的朋友,也可以通过随后提供的方法编译并执行程序。<br /><br />以下讲述用户态例程接收IP&nbsp;Queue数据包的程序设计。<br /><br />其实,由于Netlink程序也是使用socket的方式进行通信。那么接收IP&nbsp;Queue报文的方式应该遵循socket的标准流程,具体流程如下:<br /><br />(1)调用socket()创建一个地址类型为PF_NETLINK(AF_NETLINK)的套接字。该套接字使用SOCK_RAW方式传输数据,协议类型为NETLINK_FIREWALL,即使用IP&nbsp;Queue;<br /><br />(2)调用bind()将本地地址(Netlink通信双方使用该协议特有的地址格式,见下面struct&nbsp;sockaddr_nl)绑定到已建立的套接字上;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;sockaddr_nl&nbsp;{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sa_family_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nl_family;&nbsp;&nbsp;/*&nbsp;AF_NETLINK&nbsp;*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;short&nbsp;&nbsp;nl_pad;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;Zero.&nbsp;*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pid_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nl_pid;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;Process&nbsp;ID.&nbsp;*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;__u32&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nl_groups;&nbsp;&nbsp;/*&nbsp;Multicast&nbsp;groups&nbsp;mask.&nbsp;*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};<br /><br /><br /><br />(3)调用sendto()发送相关的配置信息,告诉内核应用程序准备接受的是数据包的元数据,还是同时包括数据包本身;<br /><br />(4)调用recvfrom()接受内核态发送来的IP&nbsp;Queue报文;<br /><br />(5)调用close()关闭套接字,结束通信。<br /><br />看了以上流程,我相信很多熟悉socket编程的朋友已经可以写出接收IP&nbsp;Queue报文的用户态程序了。<br /><br />本文中的示例代码的实现整体也是依照上面的步骤。但在细节的实现上,参考了iptables源码给给出的libipq库的实现代码。libipq库是iptables中封装的实现用户态接收和发送IP&nbsp;Queue报文操作的,也就相当于对上面总结的IP&nbsp;Queue报文接受流程进行封装。整个libipq库分别由libipq.c和libipq.h两个源文件。我这里将两个源文件移植(基于iptables-1.3.5版本)到示例代码中并裁剪,并编写了测试程序ip_user.c。因此,整个实现代码包含三个源文件:ip_user.c、libipq.c和libipq.h。<br /><br />以下将对三个源文件进行分析。<br /><br />1.&nbsp;libipq.h<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;该头文件定义了一个关键的数据结构,并提供了所有进行Netlink通信的API.<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;数据结构的定义如下:<br /><br />struct&nbsp;ipq_handle<br /><br />{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;fd;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;sockaddr_nl&nbsp;local;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;sockaddr_nl&nbsp;peer;<br /><br />};<br /><br />其中,fd是socket通信的描述符,local和peer分别是Netlink通信双方的地址。&nbsp;&nbsp;&nbsp;&nbsp;<br /><br />除了定义数据结构,剩下的主要就是提供给用户调用的API,函数列表如下:<br /><br />struct&nbsp;ipq_handle&nbsp;*ipq_create_handle(u_int32_t&nbsp;flags,&nbsp;u_int32_t&nbsp;protocol);<br /><br /><br /><br />int&nbsp;ipq_destroy_handle(struct&nbsp;ipq_handle&nbsp;*h);<br /><br /><br /><br />ssize_t&nbsp;ipq_read(const&nbsp;struct&nbsp;ipq_handle&nbsp;*h,&nbsp;unsigned&nbsp;char&nbsp;*buf,&nbsp;size_t&nbsp;len);<br /><br /><br /><br />int&nbsp;ipq_set_mode(const&nbsp;struct&nbsp;ipq_handle&nbsp;*h,&nbsp;u_int8_t&nbsp;mode,&nbsp;size_t&nbsp;len);<br /><br /><br /><br />ipq_packet_msg_t&nbsp;*ipq_get_packet(const&nbsp;unsigned&nbsp;char&nbsp;*buf);<br /><br /><br /><br />int&nbsp;ipq_message_type(const&nbsp;unsigned&nbsp;char&nbsp;*buf);<br /><br /><br /><br />int&nbsp;ipq_get_msgerr(const&nbsp;unsigned&nbsp;char&nbsp;*buf);<br /><br /><br /><br />int&nbsp;ipq_set_verdict(const&nbsp;struct&nbsp;ipq_handle&nbsp;*h,<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_id_t&nbsp;id,<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;int&nbsp;verdict,<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;size_t&nbsp;data_len,<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;char&nbsp;*buf);<br /><br /><br /><br />int&nbsp;ipq_ctl(const&nbsp;struct&nbsp;ipq_handle&nbsp;*h,&nbsp;int&nbsp;request,&nbsp;...);<br /><br /><br /><br />char&nbsp;*ipq_errstr(void);<br /><br />void&nbsp;ipq_perror(const&nbsp;char&nbsp;*s);<br /><br /><br /><br />我将在下面libipq.c的讲解中对若干我们将要用到的一些函数进行分析。<br /><br />2.&nbsp;libipq.c<br /><br />该源文件实现了libipq.h中定义的所有函数,并定义了一些出错信息。<br /><br />(1)ipq_create_handle()函数申请了一个struct&nbsp;ipq_handle&nbsp;*h结构体,用来存储随后创建的IPv4&nbsp;socket通信的fd,以及通信双方的地址。本函数完成了通信双方地址的初始化,并将本地地址绑定到已生成的fd上。<br /><br />ipq_create_handle()函数的源码如下<br /><br /><br /><br />struct&nbsp;ipq_handle&nbsp;*ipq_create_handle()<br /><br />{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;status;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;ipq_handle&nbsp;*h;<br /><br /><br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h&nbsp;=&nbsp;(struct&nbsp;ipq_handle&nbsp;*)malloc(sizeof(struct&nbsp;ipq_handle));<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(h&nbsp;==&nbsp;NULL)&nbsp;{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_errno&nbsp;=&nbsp;IPQ_ERR_HANDLE;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;NULL;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;memset(h,&nbsp;0,&nbsp;sizeof(struct&nbsp;ipq_handle));<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(protocol&nbsp;==&nbsp;PF_INET)<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h-&gtfd&nbsp;=&nbsp;socket(PF_NETLINK,&nbsp;SOCK_RAW,&nbsp;NETLINK_FIREWALL);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;else&nbsp;{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_errno&nbsp;=&nbsp;IPQ_ERR_PROTOCOL;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;free(h);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;NULL;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(h-&gtfd&nbsp;==&nbsp;-1)&nbsp;{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_errno&nbsp;=&nbsp;IPQ_ERR_SOCKET;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;close(h-&gtfd);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;free(h);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;NULL;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;memset(&h-&gtlocal,&nbsp;0,&nbsp;sizeof(struct&nbsp;sockaddr_nl));<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h-&gtlocal.nl_family&nbsp;=&nbsp;AF_NETLINK;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*传递本地的pid*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h-&gtlocal.nl_pid&nbsp;=&nbsp;getpid();<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h-&gtlocal.nl_groups&nbsp;=&nbsp;0;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;status&nbsp;=&nbsp;bind(h-&gtfd,&nbsp;(struct&nbsp;sockaddr&nbsp;*)&h-&gtlocal,&nbsp;sizeof(h-&gtlocal));<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(status&nbsp;==&nbsp;-1)&nbsp;{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_errno&nbsp;=&nbsp;IPQ_ERR_BIND;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;close(h-&gtfd);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;free(h);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;NULL;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;memset(&h-&gtpeer,&nbsp;0,&nbsp;sizeof(struct&nbsp;sockaddr_nl));<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h-&gtpeer.nl_family&nbsp;=&nbsp;AF_NETLINK;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*代表通信的另一方为内核*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h-&gtpeer.nl_pid&nbsp;=&nbsp;0;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h-&gtpeer.nl_groups&nbsp;=&nbsp;0;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;h;<br /><br />}<br /><br /><br /><br />ipq_destroy_handle()函数关闭由ipq_create_handle()建立起来的fd,并释放申请的内存。源码如下:<br /><br />int&nbsp;ipq_destroy_handle(struct&nbsp;ipq_handle&nbsp;*h)<br /><br />{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(h)&nbsp;{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;close(h-&gtfd);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;free(h);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;0;<br /><br />}<br /><br />(2)向内核发送模式请求的函数<br /><br />int&nbsp;ipq_set_mode(const&nbsp;struct&nbsp;ipq_handle&nbsp;*h,<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;u_int8_t&nbsp;mode,&nbsp;size_t&nbsp;range)<br /><br />{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*构造一个向内核发送报文的结构体*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;nlmsghdr&nbsp;nlh;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_peer_msg_t&nbsp;pm;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;req;<br /><br /><br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;memset(&req,&nbsp;0,&nbsp;sizeof(req));<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;req.nlh.nlmsg_len&nbsp;=&nbsp;NLMSG_LENGTH(sizeof(req));<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;req.nlh.nlmsg_flags&nbsp;=&nbsp;NLM_F_REQUEST;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;req.nlh.nlmsg_type&nbsp;=&nbsp;IPQM_MODE;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;req.nlh.nlmsg_pid&nbsp;=&nbsp;h-&gtlocal.nl_pid;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*告诉协议栈所请求的报文传递模式*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;req.pm.msg.mode.value&nbsp;=&nbsp;mode;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*请求内核返回报文的长度*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;req.pm.msg.mode.range&nbsp;=&nbsp;range;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;ipq_netlink_sendto(h,&nbsp;(void&nbsp;*)&req,&nbsp;req.nlh.nlmsg_len);<br /><br />}<br /><br />在构造完向内核发送的结构体req并设置相关内容之后,调用ipq_netlink_sendto函数发送用户态的请求数据,该函数代码如下:<br /><br />static&nbsp;ssize_t&nbsp;ipq_netlink_sendto(const&nbsp;struct&nbsp;ipq_handle&nbsp;*h,<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;void&nbsp;*msg,&nbsp;size_t&nbsp;len)<br /><br />{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;status&nbsp;=&nbsp;sendto(h-&gtfd,&nbsp;msg,&nbsp;len,&nbsp;0,<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(struct&nbsp;sockaddr&nbsp;*)&h-&gtpeer,&nbsp;sizeof(h-&gtpeer));<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(status&nbsp;&lt&nbsp;0)<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_errno&nbsp;=&nbsp;IPQ_ERR_SEND;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;status;<br /><br />}<br /><br /><br /><br />ipq_netlink_sendto函数直接调用了sendto系统调用发送用户态的数据,返回的是发送出去的数据长度。当sendto调用失败时,对全局变量ipq_errno&nbsp;赋值IPQ_ERR_SEND。这样方便以后用专门返回出错信息的函数引用。<br /><br />(3)用户态发送了请求数据包之后,就处于等待接收内核返回数据包的状态。一旦内核NF得到包处理函数返回NF_QUEUE时,该包就会被ip_queue模块发送到用户态。用户态接收IP&nbsp;Queue数据包的函数为:<br /><br />ssize_t&nbsp;ipq_read(const&nbsp;struct&nbsp;ipq_handle&nbsp;*h,&nbsp;unsigned&nbsp;char&nbsp;*buf,&nbsp;size_t&nbsp;len)<br /><br />该函数的代码如下。其中buf存储来自内核态的数据包,len为buf的长度。<br /><br />ssize_t&nbsp;ipq_read(const&nbsp;struct&nbsp;ipq_handle&nbsp;*h,<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;char&nbsp;*buf,&nbsp;size_t&nbsp;len)<br /><br />{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;ipq_netlink_recvfrom(h,&nbsp;buf,&nbsp;len);<br /><br />}<br /><br />该函数直接调用ipq_netlink_recvfrom()函数,其源码为:<br /><br />static&nbsp;ssize_t&nbsp;ipq_netlink_recvfrom(const&nbsp;struct&nbsp;ipq_handle&nbsp;*h,<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;char&nbsp;*buf,&nbsp;size_t&nbsp;len)<br /><br />{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;int&nbsp;addrlen;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;status;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;nlmsghdr&nbsp;*nlh;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*buf长度的校验,不能小于Netlink&nbsp;Message的头部长度*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(len&nbsp;&lt&nbsp;sizeof(struct&nbsp;nlmsgerr))&nbsp;{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_errno&nbsp;=&nbsp;IPQ_ERR_RECVBUF;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;-1;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;addrlen&nbsp;=&nbsp;sizeof(h-&gtpeer);<br /><br /><br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;status&nbsp;=&nbsp;recvfrom(h-&gtfd,&nbsp;buf,&nbsp;len,&nbsp;0,<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(struct&nbsp;sockaddr&nbsp;*)&h-&gtpeer,&nbsp;&addrlen);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(status&nbsp;&lt&nbsp;0)&nbsp;{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_errno&nbsp;=&nbsp;IPQ_ERR_RECV;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;status;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*判断接收到的发送方的地址长度是否正确*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(addrlen&nbsp;!=&nbsp;sizeof(h-&gtpeer))&nbsp;{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_errno&nbsp;=&nbsp;IPQ_ERR_RECV;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;-1;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*内核态向用户态发送数据报文时,其pid=0*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(h-&gtpeer.nl_pid&nbsp;!=&nbsp;0)&nbsp;{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_errno&nbsp;=&nbsp;IPQ_ERR_RECV;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;-1;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(status&nbsp;==&nbsp;0)&nbsp;{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_errno&nbsp;=&nbsp;IPQ_ERR_NLEOF;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;-1;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nlh&nbsp;=&nbsp;(struct&nbsp;nlmsghdr&nbsp;*)buf;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*判断是否发生数据报文被截断的情况*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(nlh-&gtnlmsg_flags&nbsp;&&nbsp;MSG_TRUNC&nbsp;||&nbsp;nlh-&gtnlmsg_len&nbsp;&gt&nbsp;status)&nbsp;{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_errno&nbsp;=&nbsp;IPQ_ERR_RTRUNC;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;-1;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;status;<br /><br />}<br /><br />该函数返回读取到报文的实际长度。<br /><br />至此,我们已经可以通过上面几个函数实现从内核态接收到既定模式的IP&nbsp;Queue报文。<br /><br /><br /><br />(4)输出出错信息<br /><br />char&nbsp;*ipq_errstr(void)<br /><br />{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;ipq_strerror(ipq_errno);<br /><br />}<br /><br />static&nbsp;char&nbsp;*ipq_strerror(int&nbsp;errcode)<br /><br />{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(errcode&nbsp;&lt&nbsp;0&nbsp;||&nbsp;errcode&nbsp;&gt&nbsp;IPQ_MAXERR)<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;errcode&nbsp;=&nbsp;IPQ_ERR_IMPL;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;ipq_errmap[errcode].message;<br /><br />}<br /><br />根据函数执行过程中记录的出错信息,打印对相关出错的具体提示。<br /><br /><br /><br />3.&nbsp;ipq_user.c<br /><br />这个函数就是具体的测试函数。功能比较简单,通过调用libipq.c中提供的API实现获取IP&nbsp;Queue数据包的简单信息,包括数据包在本地机上的入口以及报文的长度等,<br /><br />整个源码如下:<br /><br />/*<br /><br />*&nbsp;ipq_usr.c<br /><br />*<br /><br />*&nbsp;Testing&nbsp;program&nbsp;for&nbsp;receiving&nbsp;IP&nbsp;Queue&nbsp;packets&nbsp;from&nbsp;kernel&nbsp;2.6.18.3<br /><br />*<br /><br />*&nbsp;Dec&nbsp;1,&nbsp;2008<br /><br />*&nbsp;Godbach&nbsp;created.&nbsp;<br /><br />*&nbsp;<br /><br />*&nbsp;This&nbsp;program&nbsp;is&nbsp;free&nbsp;software;&nbsp;you&nbsp;can&nbsp;redistribute&nbsp;it&nbsp;and/or&nbsp;modify<br /><br />*&nbsp;it&nbsp;under&nbsp;the&nbsp;terms&nbsp;of&nbsp;the&nbsp;GNU&nbsp;General&nbsp;Public&nbsp;License&nbsp;as&nbsp;published&nbsp;by<br /><br />*&nbsp;the&nbsp;Free&nbsp;Software&nbsp;Foundation;&nbsp;either&nbsp;version&nbsp;2&nbsp;of&nbsp;the&nbsp;License,&nbsp;or<br /><br />*&nbsp;(at&nbsp;your&nbsp;option)&nbsp;any&nbsp;later&nbsp;version.<br /><br />*<br /><br />*&nbsp;This&nbsp;program&nbsp;is&nbsp;distributed&nbsp;in&nbsp;the&nbsp;hope&nbsp;that&nbsp;it&nbsp;will&nbsp;be&nbsp;useful,<br /><br />*&nbsp;but&nbsp;WITHOUT&nbsp;ANY&nbsp;WARRANTY;&nbsp;without&nbsp;even&nbsp;the&nbsp;implied&nbsp;warranty&nbsp;of<br /><br />*&nbsp;MERCHANTABILITY&nbsp;or&nbsp;FITNESS&nbsp;FOR&nbsp;A&nbsp;PARTICULAR&nbsp;PURPOSE.&nbsp;&nbsp;See&nbsp;the<br /><br />*&nbsp;GNU&nbsp;General&nbsp;Public&nbsp;License&nbsp;for&nbsp;more&nbsp;details.<br /><br />*<br /><br />*/<br /><br />#include&nbsp;<br /><br />#include&nbsp;<br /><br />#include&nbsp;<br /><br />#include&nbsp;'libipq.h'<br /><br /><br /><br />struct&nbsp;ipq_handle&nbsp;*h&nbsp;=&nbsp;NULL;<br /><br /><br /><br />static&nbsp;void&nbsp;sig_int(int&nbsp;signo)<br /><br />{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_destroy_handle(h);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf('Exit:&nbsp;%s<br />',&nbsp;ipq_errstr());<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exit(0);<br /><br />}<br /><br /><br /><br />int&nbsp;main(void)<br /><br />{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;char&nbsp;buf[1024];<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;creat&nbsp;handle*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h&nbsp;=&nbsp;ipq_create_handle(0,&nbsp;PF_INET);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(h&nbsp;==&nbsp;NULL){<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf('%s<br />',&nbsp;ipq_errstr());<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;0;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf('ipq_creat_handle&nbsp;success!<br />');<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*set&nbsp;mode*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;char&nbsp;mode&nbsp;=&nbsp;IPQ_COPY_PACKET;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;range&nbsp;=&nbsp;sizeof(buf);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;ret&nbsp;=&nbsp;ipq_set_mode(h,&nbsp;mode,&nbsp;range);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf('ipq_set_mode:&nbsp;send&nbsp;bytes&nbsp;=%d,&nbsp;range=%d<br />',&nbsp;ret,&nbsp;range);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*register&nbsp;signal&nbsp;handler*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;signal(SIGINT,&nbsp;sig_int);<br /><br /><br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*read&nbsp;packet&nbsp;from&nbsp;kernel*/<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;status;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;nlmsghdr&nbsp;*nlh;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_packet_msg_t&nbsp;*ipq_packet;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while(1){<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;status&nbsp;=&nbsp;ipq_read(h,&nbsp;buf,&nbsp;sizeof(buf));<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(status&nbsp;&gt&nbsp;sizeof(struct&nbsp;nlmsghdr))<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nlh&nbsp;=&nbsp;(struct&nbsp;nlmsghdr&nbsp;*)buf;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_packet&nbsp;=&nbsp;ipq_get_packet(buf);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf('recv&nbsp;bytes&nbsp;=%d,&nbsp;nlmsg_len=%d,&nbsp;indev=%s,&nbsp;datalen=%d,&nbsp;packet_id=%x<br />',&nbsp;status,&nbsp;nlh-&gtnlmsg_len,&nbsp;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ipq_packet-&gtindev_name,&nbsp;&nbsp;ipq_packet-&gtdata_len,&nbsp;ipq_packet-&gtpacket_id);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;0;<br /><br />}<br /><br /><br /><br />四、应用程序的测试<br /><br />1.&nbsp;测试环境的建立<br /><br />(1)内核态:要求已编译的内核支持Netlink机制,&nbsp;并进入内核源码目录net/ipv4/netfilter下,检查是否生成ip_queue.ko。如果有相应的文件,则确保该模块是否加载,没有加载的话,modprobe&nbsp;ip_queue进行加载<br /><br />(2)用户态:要求有已经安装iptables,并加上一条如下规则:<br /><br />iptables&nbsp;-I&nbsp;INPUT&nbsp;-p&nbsp;icmp&nbsp;-j&nbsp;QUEUE<br /><br />这里我们在INPUT链上开始处添加了一条对所有ICMP报文进行IP&nbsp;Queue的规则。通过添加不同的iptables规则,可以对不同的报文进行IP&nbsp;Queue。<br /><br /><br /><br />如果系统上没有安装iptables的话,那么可以用一个简单的内核模块来实现其功能。即在NF对应的Hook点上注册一个钩子函数,对于某种类型的数据包直接return&nbsp;NF_QUEUE即可。附件的源码中我提供了一个模块程序,在NF的PRE_ROUTING出注册了一个对所有ICMP报文return&nbsp;NF_QUEUE的模块。没有iptables的朋友可以使用这个小模块程序替代。<br /><br /><br /><br />注意:我这里所使用的内核为2.6.18.3。其他内核版本没有进行测试。不过,个人觉得如果用户态的应用程序应该在2.6上没有问题,只是提供的内核模块程序不能保证。<br /><br />2.&nbsp;程序的测试<br /><br />搭建好上面提示的环境之后,可以对应用程序的源码进行编译:<br /><br />gcc&nbsp;libipq.c&nbsp;ipq_user.c&nbsp;-o&nbsp;ipq_user<br /><br />执行ipq_user:<br /><br />[root@localhost&nbsp;ipq_user]#&nbsp;./ipq_user<br /><br />ipq_creat_handle&nbsp;success!<br /><br />ipq_set_mode:&nbsp;send&nbsp;bytes&nbsp;=44,&nbsp;range=1024<br /><br />随后,程序处于等待接受内核数据包的状态。我们从另外一台主机发送ping包到本地主机,然后看到终端的输出为:<br /><br />[root@localhost&nbsp;ipq_user]#&nbsp;./ipq_user<br /><br />ipq_creat_handle&nbsp;success!<br /><br />ipq_set_mode:&nbsp;send&nbsp;bytes&nbsp;=44,&nbsp;range=1024<br /><br />recv&nbsp;bytes&nbsp;=148,&nbsp;nlmsg_len=148,&nbsp;indev=eth0,&nbsp;datalen=60,&nbsp;packet_id=c2f9b500<br /><br />recv&nbsp;bytes&nbsp;=148,&nbsp;nlmsg_len=148,&nbsp;indev=eth0,&nbsp;datalen=60,&nbsp;packet_id=cb0c8c00<br /><br />recv&nbsp;bytes&nbsp;=148,&nbsp;nlmsg_len=148,&nbsp;indev=eth0,&nbsp;datalen=60,&nbsp;packet_id=c72aa920<br /><br />recv&nbsp;bytes&nbsp;=148,&nbsp;nlmsg_len=148,&nbsp;indev=eth0,&nbsp;datalen=60,&nbsp;packet_id=c2f9b3c0<br /><br />Exit:&nbsp;No&nbsp;error<br /><br />从以上信息中可以看出:<br /><br />(1)用户态发送的模式设置信息的包长度为44&nbsp;bytes;<br /><br />(2)接收到内核态发送的包长度为148bytes,&nbsp;这和从收到的IP&nbsp;Queue包中保存的长度nlmsg_len一致。<br />(3)ping包进入本地主机的eth0网口,报文的长度为60&nbsp;bytes。这个实际的ping包的长度一致。<br /><br /><br /><br />好了,到现在为止,我们已经成功的通过程序接收到内核态发送的IP&nbsp;Queue数据包。我将在下一篇**中讲解用户态对接收到报文的简单处理以及发送给内核的整个过程。<br /><br /><br /><br />附件<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;附件中提供了此次测试进行的应用程序的源码,和一个简单的对icmp报文进行IP&nbsp;Queue的内核模块程序。<br />&nbsp;<br /> &nbsp;&nbsp;<br />
zcying 发表于 2009-4-9 15:22 | 显示全部楼层

经典**!

  
您需要登录后才可以回帖 登录 | 注册

本版积分规则

139

主题

185

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部