Linux内核IP Queue机制的分析(三)――ip_queue内核模块的分析
2010年09月25日
reference:http://linux.chinaunix.net/bbs/thread-1152070-1-1.html
本文分析ip_queue的内核态源码。文中如有任何疏漏和差错,欢迎各位朋友指正。
本文欢迎自由转载,但请标明出处,并保证本文的完整性。
作者:Godbach
Blog:http://Godbach.cublog.cn
日期:2010/01/04
本系列的前两篇文章如下:
1. Linux内核IP Queue机制的分析(一)――用户态接收数据包
http://blog.chinaunix.net/u/33048/showart_1678213.html
2. Linux内核IP Queue机制的分析(二)――用户态处理并回传数据包
http://blog.chinaunix.net/u/33048/showart_1839753.html
本文大纲如下:
一、IP Queue的生效
二、网络层中IP报文进入IP Queue的流程
三、ip_queue代码分析
(一)数据结构的定义
(二)ip_queue模块的加载和卸载
(三)ip_queue报文入队处理函数的注册
(四)入队函数ipq_enqueue_packet ―― 发送数据包到用户空间
(五)接收和处理用户空间的配置―― 接收用户空间的数据包
(六)数据包的最终处理
---------------------------------------------------------------------------------------------------
一、IP Queue的生效
数据包能够进入ip_queue模块,需要两个动作:
(1)模块的加载:modprobe ip_queue
(2)NF上对数据包执行NF_QUEUE的动作,这个可以通过用户态配置一条iptables规则实现:
iptables -A INPUT -p tcp --dport 21 -j QUEUE
这里假设对发往本机的TCP报文端口为21的进行QUEUE。
有了以上两个步骤, 所有匹配到(2)中的报文将会调用IP Queue模块的相关函数。
二、网络层中IP报文进入IP Queue的流程
本文中分析的代码的内核版本为2.6.18.3.
这里我们以本地接收报文为例,如果是转发的报文,可比照着分析即可。
IP层接收报文的函数为:ip_rcv()(ip_input.c)。该函数对报文进行一些初步的检查后,就将报文交给PREROUTING Hook点注册的钩子函数处理:
return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
复制代码
由以上代码可见,报文经过钩子函数之后,由ip_rcv_finish()接着处理。该函数主要完成数据报文的路由查找。
如果是发往本地的报文,则会调用ip_local_deliver()函数:
return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
ip_local_deliver_finish);
复制代码
该函数主要功能就是将报文交给NF_IP_LOCAL_IN hook点的钩子函数进行处理。我们在第一部分中添加的一条iptables规则就是对于经过INPUT链的TCP报文且目的端口21执行动作QUEUE。如果此时用户空间已经开启socket等待接收IP Queue报文的话,那么对应的报文就会进入用户空间,然后就可以参照我们之前提供的用户空间例程进行处理。
这里,我们简单列出在NF_IP_LOCAL_IN中NF_HOOK宏的调用函数过程:
NF_HOOK()->NF_HOOK_THRESH()->nf_hook_thresh()->nf_hook_slow()
当nf_hook_slow函数返回值为NF_QUEUE时,进一步调用nf_queue()。该函数对所有动作为QUEUE的报文进行处理,其中关键的一行代码如下:
status = queue_handler[pf]->outfn(*skb, info, queuenum,
queue_handler[pf]->data);
复制代码
这行代码就是将报文按照IP层的协议交给对应的queue handler。IPv4协议中注册的queue handler为ipq_enqueue_packet(),即我们要分写ip_queue模块的代码。
至此,需要QUEUE的报文已经走入了我们要分析ip_queue,我们下面就开始走入正题,分析ip_queue的代码。
三、ip_queue代码分析
ip_queue模块的代码较为简单,包含ip_queue.h和ip_queue.c。我们将分别该两个源文件进行分析。
(一)数据结构的定义
ip_queue模块的数据结构定义在头文件ip_queue.h中,主要定义了用于在内核态和用户态传输数据的相关数据结构。其代码如下:
/*
* This is a module which is used for queueing IPv4 packets and
* communicating with userspace via netlink.
*
* (C) 2000 James Morris, this code is GPL.
*/
#ifndef _IP_QUEUE_H
#define _IP_QUEUE_H
#ifdef __KERNEL__
#ifdef DEBUG_IPQ
#define QDEBUG(x...) printk(KERN_DEBUG ## x)
#else
#define QDEBUG(x...)
#endif /* DEBUG_IPQ */
#else
#include
#endif /* ! __KERNEL__ */
/* 内核态发送到用户态的消息的数据结构*/
typedef struct ipq_packet_msg {
unsigned long packet_id; /* ID of queued packet */
unsigned long mark; /* Netfilter mark value */
long timestamp_sec; /* Packet arrival time (seconds) */
long timestamp_usec; /* Packet arrvial time (+useconds) */
unsigned int hook; /* Netfilter hook we rode in on */
char indev_name[IFNAMSIZ]; /* Name of incoming interface */
char outdev_name[IFNAMSIZ]; /* Name of outgoing interface */
unsigned short hw_protocol; /* Hardware protocol (network order) */
unsigned short hw_type; /* Hardware type */
unsigned char hw_addrlen; /* Hardware address length */
unsigned char hw_addr[8]; /* Hardware address */
size_t data_len; /* Length of packet data */
unsigned char payload[0]; /* Optional packet data */
} ipq_packet_msg_t;
/* 用户态发送到内核态的模式消息 */
typedef struct ipq_mode_msg {
unsigned char value; /* Requested mode */
size_t range; /* Optional range of packet requested */
} ipq_mode_msg_t;
/* 用户态发送到内核态的断言消息 */
typedef struct ipq_verdict_msg {
unsigned int value; /* Verdict to hand to netfilter */
unsigned long id; /* Packet ID for this verdict */
size_t data_len; /* Length of replacement data */
unsigned char payload[0]; /* Optional replacement packet */
} ipq_verdict_msg_t;
/*统一封装起来的用户态发内核态的信息的数据结构*/
typedef struct ipq_peer_msg {
union {
ipq_verdict_msg_t verdict;
ipq_mode_msg_t mode;
} msg;
} ipq_peer_msg_t;
/* 报文传输的模式*/
enum {
IPQ_COPY_NONE, /* Initial mode, packets are dropped */
IPQ_COPY_META, /* Copy metadata */
IPQ_COPY_PACKET /* Copy metadata + packet (range) */
};
#define IPQ_COPY_MAX IPQ_COPY_PACKET
/* IP Queue消息的类型 */
#define IPQM_BASE 0x10 /* standard netlink messages below this */
#define IPQM_MODE (IPQM_BASE + 1) /* Mode request from peer */
#define IPQM_VERDICT (IPQM_BASE + 2) /* Verdict from peer */
#define IPQM_PACKET (IPQM_BASE + 3) /* Packet from kernel */
#define IPQM_MAX (IPQM_BASE + 4)
#endif /*_IP_QUEUE_H*/
复制代码
该头文件中定义的相关数据结构和宏应该和用户空间引用的头文件的内容是保持一致的。因此,对于该文件中的数据结构的详细解释,可以参考本系列文章的第一篇《Linux内核IP Queue机制的分析(一)――用户态接收数据包》中第二部分IP Queue编程接口。
(二)ip_queue模块的加载和卸载
分析一个内核模块的源代码,通常我们先看模块加载时做哪些工作,这样就可以了解到模块具体的功能会在什么条件下执行。而模块的卸载通常就是将模块加载时注册的函数和申请的资源进行释放等工作。因此,这里简单的分析一下init函数的实现,代码在ip_queue.c中。
static int __init ip_queue_init(void)
{
int status = -ENOMEM;
struct proc_dir_entry *proc;
/*注册netlink的通知链*/
netlink_register_notifier(&ipq_nl_notifier);
/*内核态创建netlink的socket,并注册ipq_rcv_sk函数实现接收用户空间下发的配置数据*/
ipqnl = netlink_kernel_create(NETLINK_FIREWALL, 0, ipq_rcv_sk,
THIS_MODULE);
if (ipqnl == NULL) {
printk(KERN_ERR "ip_queue: failed to create netlink socket\n");
goto cleanup_netlink_notifier;
}
/*注册proc文件*/
proc = proc_net_create(IPQ_PROC_FS_NAME, 0, ipq_get_info);
if (proc)
proc->owner = THIS_MODULE;
else {
printk(KERN_ERR "ip_queue: failed to create proc entry\n");
goto cleanup_ipqnl;
}
/*注册网络设备的通知链*/
register_netdevice_notifier(&ipq_dev_notifier);
ipq_sysctl_header = register_sysctl_table(ipq_root_table, 0);
/*注册IP Queue机制的报文处理结构,主要包含一个报文的入队处理函数,下面具体分析*/
status = nf_register_queue_handler(PF_INET, &nfqh);
if (status sk_socket);
mutex_lock(&ipqnl_mutex);
mutex_unlock(&ipqnl_mutex);
cleanup_netlink_notifier:
netlink_unregister_notifier(&ipq_nl_notifier);
return status;
}
复制代码
IP Queue模块的初始化函数最重要的两个工作:
(1)创建用于IP Queue的netlink socket,实现接收用户态的数据的函数ipq_rcv_sk();
(2)注册IP Queue报文的入队处理函数,并将数据包按照用户的配置将相关信息发向用户空间。当NF框架的hook函数对报文的处理返回NF_QUEUE时,该函数作为报文下一步被处理的入口函数。
模块初始化中还有一些其他诸如注册通知链的工作,这里我们不作具体分析,想了解相关细节的朋友可以查阅具体的资料。至于通知链的相关知识,也可以参考论坛上scutan兄的精华帖《内核通知链 学习笔记》。链接:
http://linux.chinaunix.net/bbs/viewthread.php?tid=1051266。
(三)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处理报文的函数终于闪亮登场了,我们前面
发表评论
-
解读Flex性能优化基本原则
2012-01-20 01:18 351解读Flex性能优化基本原 ... -
Silverlight与Flex的比较选择
2012-01-20 01:18 382Silverlight与Flex的比较选择 2010年11月 ... -
flex基础概念
2012-01-20 01:17 509flex基础概念 2011年03月26日 SDK(Sof ... -
FLEX与C#交互概要
2012-01-20 01:17 668FLEX与C#交互概要 2010年07月21日 准备工作 ... -
Flex Spring整合包
2012-01-20 01:17 754Flex Spring整合包 2010年11月10日 A ... -
ADO.NET的最佳实践技巧
2012-01-19 09:07 480ADO.NET的最佳实践技巧 2 ... -
oracle错误一览表三
2012-01-19 09:07 1200oracle错误一览表三 2011年02月15日 OR ... -
Windows Mobile系统名词解释
2012-01-19 09:07 636Windows Mobile系统名词解 ... -
操作系统结构
2012-01-17 01:36 450操作系统结构 2010年10月20日 操作系统结构 ... -
2010-11-12
2012-01-17 01:36 5462010-11-12 2010年11月12日 ... -
delphi笔记2
2012-01-17 01:36 968delphi笔记2 2011年06月16日 一个类型声明 ... -
Java体系结构对信息安全的支持
2012-01-17 01:36 672Java体系结构对信息安全的支持 2010年06月09日 ... -
完成端口的一些问题
2012-01-17 01:36 546完成端口的一些问题 2010年11月23日 测试完成端口 ... -
2011年第十届中国机械(越南)展览会
2012-01-15 20:47 6052011年第十届中国机械(越南)展览会 2010年11月22 ... -
来到越南
2012-01-15 20:47 512来到越南 2010年07月23日 因为工作的需要,我和同 ... -
2011-2-3
2012-01-15 20:47 5272011-2-3 2011年02月03日 美国驻广州总 ... -
越南签证、柬埔寨签证价格表
2012-01-15 20:47 553越南签证、柬埔寨签证价格表 2011年03月31日 20 ... -
javaScript 控制textArea输入字数
2012-01-11 12:26 461javaScript 控制textArea输入字数 2011 ... -
oracle数据库互访的问题
2012-01-11 12:26 526oracle数据库互访的问题 2011年03月01日 点 ... -
怎么我的php不支持网址传值?
2012-01-11 12:26 552怎么我的php不支持网址传值? 2011年03月01日 ...
相关推荐
Linux内核IP Queue机制的分析Linux内核IP Queue机制的分析Linux内核IP Queue机制的分析Linux内核IP Queue机制的分析Linux内核IP Queue机制的分析
用户态接受数据报文的代码,测试内核态与用户态通信的TCP和UDP通信代码
\Linux内核机制之等待队列,消息讲述了wait_queue队列的数据结构和在内核中的实现源码,有助于对如何使用队列更加一目了然。
有关linux内核模块编写,有关使用dev_queue_xmit、dev_hard_start_xmit的方法实现数据包的发送的内核模块的相关资料整理
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
在 Linux kernel 里有一个数据结构可以帮助我们做到这样的功能。这个数据结构就是本位要为大家介绍的 wait queue。在 kernel 里,wait_queue 的应用很广,举凡 device driver semaphore 等方面都会使用到 wait_queue...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,对时间度量、系统调用进行了分析和讨论;最后讲解了Linux内核中常见的同步机制,使读者掌握每处理器变量和RCU...
6中断处理程序被分解为top half和bottom half的原因,介绍linux的softirq,tasklet,ksoftirqd和work queue,分析进程与top half,bottom half的竞争情形和同步。(4小时) 7掌握内核同步原理和方法:原子操作,...
在内核代码 2.6.15.5中/kernel/fork.c第1255-1261中有如下代码: 1. p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid); 2. if (!IS_ERR(p)) { 3. struct ...
本文分析了内核同步及互斥的几种机制Waitqueue、Event及Semaphore的实现,详细分析了其实现流程。Event及Semaphore本质上都是基于Waitqueue和自旋锁实现的。总结了静态定义及动态初始化数据结构的相关规则,这对于...
atomic_queue 基于带有循环缓冲区的C ++ 14多生产者多消费者无锁队列。 这些队列遵循的主要设计原理是极简主义:原子操作的最基本要求,固定大小的缓冲区,值语义。 这些品质也有局限性: 最大队列大小必须在编译...
当采用MILO这样的引导程序来引导Linux时,不需要上面所说的Bootloader,而只需要 vmlinux或vmlinux.gz,引导程序会主动解压加载内核到0x1000(小内核)或0x100000(大内核),并直接进入内核引导部分,即本文的第二...
DA1458x RW内核 函数接口说明文档。 RivieraWaves内核是一个小而高效的实时操作系统,提供以下功能: * 交换消息 * 消息保存 * 定时器功能 * 内核也提供了一种用于推迟行动事件功能 源码结构如下: ke_config.h 用来...
这个流程图粗略地描述了softirq, tasklet, bottomhalt, task queue这些对象之间的联系及调用流程。 主要依据:《Linux内核的Softirq机制》和《软中断概况》 图中可能存在错误,希望您的指正!