linux内核ip头定义,Linux内核IP Queue机制的分析(三)——ip_queue内核模块的分析(中)...

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

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

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

作者:Godbach

日期: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->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中,并增加队列的统计计数。该链表记录了所有发往用户空间,且并未收到用户空间下发处理结果的数据包。

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

--未完待续

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值