- 论坛徽章:
- 36
|
(三)ip_queue报文入队处理函数的注册
在上面分析的模块注册代码中,IP Queue的报文入队处理函数的注册是通过调用nf_register_queue_handler()来实现的。因此,有必要了解一下该函数的源码,源码位于nf_queue.c中:
- /* return EBUSY when somebody else is registered, return EEXIST if the
- * same handler is registered, return 0 in case of success. */
- int nf_register_queue_handler(int pf, struct nf_queue_handler *qh)
- {
- int ret;
- /*IP协议族的值必须在当前指定的范围内*/
- if (pf >= NPROTO)
- return -EINVAL;
- write_lock_bh(&queue_handler_lock);
- /*该queue handler已经被注册过了*/
- if (queue_handler[pf] == qh)
- ret = -EEXIST;
- /*该协议族已经被注册了handler了*/
- else if (queue_handler[pf])
- ret = -EBUSY;
- /*将该协议的queue hanler指向参数qh*/
- else {
- queue_handler[pf] = qh;
- ret = 0;
- }
- write_unlock_bh(&queue_handler_lock);
- return ret;
- }
复制代码
该函数的代码比较简单,先来了解一下函数的两个参数:
(1)pf:IP协议族的值,PF_INET和PF_INET6分别代表IPv4和IPv6。
(2)qh:NF中对报文进行Queue的结构体,定义如下(netfilter.h):
- /* Packet queuing */
- struct nf_queue_handler {
- int (*outfn)(struct sk_buff *skb, struct nf_info *info,
- unsigned int queuenum, void *data);
- void *data;
- char *name;
- };
复制代码
由此可见,该结构体主要包含一个函数指针,用于处理NF框架需要Queue的报文。data应该是用来保存一些私有数据,name则是该queue handler的名称。
代码中已经包含了该函数源码的简单注释,这里再对该函数进行一下简单的总结:
(1) 每个协议族只能注册一个queue handler;
(2) 随后报文的处理中,可以根据报文的协议族,就可以找到报文进行Queue时的处理函数queue_handler[pf]->outfn()。
在IP Queue模块中,queue handler的注册如下所示:
- status = nf_register_queue_handler(PF_INET, &nfqh);
复制代码
可见,注册的是IPv4协议族的报文处理函数,而nfqh结构体的定义如下:
- static struct nf_queue_handler nfqh = {
- .name = "ip_queue",
- .outfn = &ipq_enqueue_packet,
- };
复制代码
这里,IP Queue处理报文的函数终于闪亮登场了,我们前面啰嗦了半天,主要就是想理顺一下思路,顺理成章的引出该函数。
(四)入队函数ipq_enqueue_packet —— 发送数据包到用户空间
第二部分已经分析过了该函数会在什么条件下被触发。这里详细分析该函数的实现,代码在ip_queue.c中。
分析代码之前,我们先了解一下IP Queue对数据包队列管理的核心数据结构:
- struct ipq_queue_entry {
- struct list_head list;
- struct nf_info *info;
- struct sk_buff *skb;
- };
复制代码
所有被Queue到用户空间的数据包都会有这样一个结构体。其中,
该数据结构的第1个元素是个双向链表结构,用于将所有queue的数据包用双向链表连
接起来,实现队列管理。
第2个元素同样是一个结构体,其定义在netfilter.h中:
- /* Each queued (to userspace) skbuff has one of these. */
- struct nf_info
- {
- /* The ops struct which sent us to userspace. */
- struct nf_hook_ops *elem;
-
- /* If we're sent to userspace, this keeps housekeeping info */
- int pf;
- unsigned int hook;
- struct net_device *indev, *outdev;
- int (*okfn)(struct sk_buff *);
- };
复制代码
这个结构体应该很容易看出他的作用,就是记录下当数据包被Queue时所在的hook函数的相关信息,包括hook的操作结构、协议号、hook点等相关信息。当用户空间下发了对数据包的处理结果时,内核就需要参考这个结构对数据包进行进一步的处理。具体的我们会在后面数据包回注函数中分析。
第3个元素是skb本身,也就是回注函数中要处理的数据包。
OK,我们下面就开始如对函数的分析。
- static int
- ipq_enqueue_packet(struct sk_buff *skb, struct nf_info *info,
- unsigned int queuenum, void *data)
- {
- int status = -EINVAL;
- struct sk_buff *nskb;
- struct ipq_queue_entry *entry;
- /*判断用户配置的模式,可以为拷贝元数据或者整个数据包的信息*/
- if (copy_mode == IPQ_COPY_NONE)
- return -EAGAIN;
- /*为即将入队的数据包分配一个struct ipq_queue_entry 结构体,用于实现对数据包的管理,我们称之为queue管理结构体*/
- entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
- if (entry == NULL) {
- printk(KERN_ERR "ip_queue: OOM in ipq_enqueue_packet()\n");
- return -ENOMEM;
- }
- /*将该数据包对应的相关信息保存到管理结构体中*/
- entry->info = info;
- entry->skb = skb;
-
- /*构建用于发往用户空间的消息*/
- nskb = ipq_build_packet_message(entry, &status);
- if (nskb == NULL)
- goto err_out_free;
-
- write_lock_bh(&queue_lock);
-
- /如果用户空间没有开启进程,等待接收消息的话,就释放该消息/
- if (!peer_pid)
- goto err_out_free_nskb;
- /*如果当前队列中的数据包总数超过了设置的最大值,则是放该消息,并且增加丢弃数据包的统计计数*/
- if (queue_total >= queue_maxlen) {
- queue_dropped++;
- status = -ENOSPC;
- if (net_ratelimit())
- printk (KERN_WARNING "ip_queue: full at %d entries, "
- "dropping packets(s). Dropped: %d\n", queue_total,
- queue_dropped);
- goto err_out_free_nskb;
- }
- /* 将消息发送给用户空间 */
- status = netlink_unicast(ipqnl, nskb, peer_pid, MSG_DONTWAIT);
- if (status < 0) {
- queue_user_dropped++;
- goto err_out_unlock;
- }
- /*成功发送到用户空间之后,将该数据包的queue管理结构添加到全局队列链表中*/
- __ipq_enqueue_entry(entry);
- write_unlock_bh(&queue_lock);
- return status;
- err_out_free_nskb:
- kfree_skb(nskb);
-
- err_out_unlock:
- write_unlock_bh(&queue_lock);
- err_out_free:
- kfree(entry);
- return status;
- }
复制代码
该函数成功执行之后,就将数据包相关的信息发送到用户空间,同时将该数据包对应的queue管理结构加到全局链表中。
下面对于ipq_enqueue_packet函数中调用的几个重要函数做一些分析。
首先是构建消息的函数ipq_build_packet_message。
- static struct sk_buff *
- ipq_build_packet_message(struct ipq_queue_entry *entry, int *errp)
- {
- unsigned char *old_tail;
- size_t size = 0;
- size_t data_len = 0;
- struct sk_buff *skb;
- struct ipq_packet_msg *pmsg;
- struct nlmsghdr *nlh;
- read_lock_bh(&queue_lock);
- /*根据用户配置的copy模式,确定发给用户空间消息的长度*/
- switch (copy_mode) {
- /*对于初始模式和拷贝元数据的模式,消息应该包括netlink的消息头和ipq的消息头,长度为两个消息头长度之和,即sizeof(struct nlmsghdr) + sizeof(struct ipq_packet_msg),并考虑对齐的因素。这里直接调用netlink封装的宏NLMSG_SPACE来计算长度。该宏本身已经包含了netlink消息头的长度*/
- case IPQ_COPY_META:
- case IPQ_COPY_NONE:
- /*消息长度为netlink的消息头和ipq的消息头的长度之和*/
- size = NLMSG_SPACE(sizeof(*pmsg));
- data_len = 0;
- break;
-
- case IPQ_COPY_PACKET:
- if (entry->skb->ip_summed == CHECKSUM_HW &&
- (*errp = skb_checksum_help(entry->skb,
- entry->info->outdev == NULL))) {
- read_unlock_bh(&queue_lock);
- return NULL;
- }
- /*如果用户需要拷贝整个数据包的内容,那么如果配置的长度为0或者超出数据包的实际长度,则以数据包的实际长度进行拷贝*/
- if (copy_range == 0 || copy_range > entry->skb->len)
- data_len = entry->skb->len;
- else
- data_len = copy_range;
- /*消息长度为netlink的消息头、ipq的消息头的长度以及要拷贝数据包的长度之和*/
- size = NLMSG_SPACE(sizeof(*pmsg) + data_len);
- break;
-
- default:
- *errp = -EINVAL;
- read_unlock_bh(&queue_lock);
- return NULL;
- }
- read_unlock_bh(&queue_lock);
- /*为构建的消息申请内存,同样适用skb结构体,消息的内容应该在skb->data和skb->tail之间*/
- skb = alloc_skb(size, GFP_ATOMIC);
- if (!skb)
- goto nlmsg_failure;
- /*记录一下新分配的skb中的tail指针的地址,此时应该skb->tail指向skb->data*/
- old_tail= skb->tail;
- /*填充netlink消息头*/
- nlh = NLMSG_PUT(skb, 0, 0, IPQM_PACKET, size - sizeof(*nlh));
- /*获取netlink消息体的起始地址,即ipq的消息头*/
- pmsg = NLMSG_DATA(nlh);
- memset(pmsg, 0, sizeof(*pmsg));
- /*将相关的ipq消息记录到消息头中*/
- /*将skb对应的queue管理结构体的地址作为packet_id*/
- pmsg->packet_id = (unsigned long )entry;
- pmsg->data_len = data_len;
- pmsg->timestamp_sec = entry->skb->tstamp.off_sec;
- pmsg->timestamp_usec = entry->skb->tstamp.off_usec;
- pmsg->mark = entry->skb->nfmark;
- pmsg->hook = entry->info->hook;
- pmsg->hw_protocol = entry->skb->protocol;
-
- /*记录被queue数据包对应的输入设备名称*/
- if (entry->info->indev)
- strcpy(pmsg->indev_name, entry->info->indev->name);
- else
- pmsg->indev_name[0] = '\0';
- /*记录被queue数据包对应的输出设备名称*/
- if (entry->info->outdev)
- strcpy(pmsg->outdev_name, entry->info->outdev->name);
- else
- pmsg->outdev_name[0] = '\0';
- /*记录被queue数据包对应的链路层的协议类型及地址长度*/
- if (entry->info->indev && entry->skb->dev) {
- pmsg->hw_type = entry->skb->dev->type;
- if (entry->skb->dev->hard_header_parse)
- pmsg->hw_addrlen =
- entry->skb->dev->hard_header_parse(entry->skb,
- pmsg->hw_addr);
- }
- /* data_len != 0 说明需要拷贝数据包的原始数据。skb_copy_bits 函数的实现不再分析,其主要功能就是将从skb->data+offset开始的数据拷贝data_len个字节到pmsg->payload中,即ipq消息的载荷。另外一个需要注意的问题,如果skb挂载的有分片包,则skb_copy_bits也会按照顺序拷贝分片包中的数据*/
- if (data_len)
- if (skb_copy_bits(entry->skb, 0, pmsg->payload, data_len))
- BUG();
- /*设置netlink消息的长度*/
- nlh->nlmsg_len = skb->tail - old_tail;
- return skb;
- nlmsg_failure:
- if (skb)
- kfree_skb(skb);
- *errp = -EINVAL;
- printk(KERN_ERR "ip_queue: error creating packet message\n");
- return NULL;
- }
复制代码
其次,对于netlink_unicast函数,我们这里不具体分析,它的作用就是将内核封装好的netlink消息发送到用户态。
最后分析一下__ipq_enqueue_entry 函数。
- static inline void
- __ipq_enqueue_entry(struct ipq_queue_entry *entry)
- {
- list_add(&entry->list, &queue_list);
- queue_total++;
- }
复制代码
该函数的功能很简答,就是将当前skb的queue管理结构添加到全局的queue管理链表queue_list中,并增加队列的统计计数。该链表记录了所有发往用户空间,且并未收到用户空间下发处理结果的数据包。
至此,数据包的入队处理函数已经分析完毕。一切顺利执行的话,现在用户态已经接收到关于该数据包的消息。
--未完待续 |
|