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