Chinaunix首页 | 论坛 | 博客
  • 博客访问: 48831
  • 博文数量: 18
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 12
  • 用 户 组: 普通用户
  • 注册时间: 2018-04-03 10:12
文章分类
文章存档

2018年(18)

我的朋友

分类: LINUX

2018-05-31 15:17:03

本文分析ip_queue的内核态源码。文中如有任何疏漏和差错,欢迎各位朋友指正。

由于本文内容较多,本人将其分为上、中、下三篇。其中上篇和下篇的链接如下:

上篇:http://blog.chinaunix.net/u/33048/showart_2139488.html

下篇:http://blog.chinaunix.net/u/33048/showart_2139500.html


 

      本文欢迎自由转载,但请标明出处,并保证本文的完整性。

      作者:Godbach

        Blog:http://Godbach.cublog.cn

      日期:2010/01/04

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->dataskb->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++;

}

该函数的功能很简答,就是将当前skbqueue管理结构添加到全局的queue管理链表queue_list中,并增加队列的统计计数。该链表记录了所有发往用户空间,且并未收到用户空间下发处理结果的数据包。

至此,数据包的入队处理函数已经分析完毕。一切顺利执行的话,现在用户态已经接收到关于该数据包的消息。

--未完待续
阅读(1453) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~