Chinaunix首页 | 论坛 | 博客
  • 博客访问: 541323
  • 博文数量: 140
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 650
  • 用 户 组: 普通用户
  • 注册时间: 2012-12-11 19:00
文章存档

2015年(5)

2014年(135)

分类: LINUX

2014-03-02 15:37:09

那么接下来看一下内核态回调函数的注册,以及回调函数之间如何做到了一一对应的关系。nfnetlink_queue.c文件是一个独立的模块文件,因为有module_init(nfnetlink_queue_init); module_exit(nfnetlink_queue_fini); 这两句话。那么nf_queue回调函数的注册也就在初始化当中完成。

static int __init nfnetlink_queue_init(void)

{

int i, status = -ENOMEM;


for (i = 0; i < INSTANCE_BUCKETS; i++)

INIT_HLIST_HEAD(&instance_table[i]);


netlink_register_notifier(&nfqnl_rtnl_notifier); //根据通知链可知,当发生了nf_queue感兴趣的event(什么事件?),那么就会调用nfqnl_rtnl_notifier进行处理.

****************************************************************************

status = nfnetlink_subsys_register(&nfqnl_subsys); //这里就是注册子系统回调函数的地方。这是我们注册的子系统

*******************************************************************************

static const struct nfnetlink_subsystem nfqnl_subsys = {

.name = "nf_queue", 子系统名称

.subsys_id = NFNL_SUBSYS_QUEUE, 子系统的id,很关键。内核收到nlmsghdr后就是根据nl_type的高8位来判断,这个数据包该交给哪个内核子系统处理.

.cb_count = NFQNL_MSG_MAX, //这是下面结构体的个数

.cb = nfqnl_cb,

};

看来内核当中的nfnl_callback结构体还是比较复杂的。

struct nfnl_callback {

int (*call)(struct sock *nl, struct sk_buff *skb,

const struct nlmsghdr *nlh,

const struct nlattr * const cda[]);

int (*call_rcu)(struct sock *nl, struct sk_buff *skb,

const struct nlmsghdr *nlh,

const struct nlattr * const cda[]);

const struct nla_policy *policy; /* netlink attribute policy */

const u_int16_t attr_count; /* number of nlattr's */

};

这个结构在nf_queue当中被实例化为了:

static const struct nfnl_callback nfqnl_cb[NFQNL_MSG_MAX] = {

[NFQNL_MSG_PACKET] = { .call_rcu = nfqnl_recv_unsupp,

.attr_count = NFQA_MAX, },

[NFQNL_MSG_VERDICT] = { .call_rcu = nfqnl_recv_verdict,

.attr_count = NFQA_MAX,

.policy = nfqa_verdict_policy },

[NFQNL_MSG_CONFIG] = { .call = nfqnl_recv_config,

.attr_count = NFQA_CFG_MAX,

.policy = nfqa_cfg_policy },

[NFQNL_MSG_VERDICT_BATCH]={ .call_rcu = nfqnl_recv_verdict_batch,

.attr_count = NFQA_MAX,

.policy = nfqa_verdict_batch_policy },

};

可以想象,当一个Netlink Message数据包到来时以nl_type的低八位为下标,在回调函数数组nfqnl_cb中查找相应的回调函数进行对数据包的处理。做到了回调函数之间,一一对应的关系。

********************************************************************************

if (status < 0) {

printk(KERN_ERR "nf_queue: failed to create netlink socket\n");

goto cleanup_netlink_notifier;

}


#ifdef CONFIG_PROC_FS

if (!proc_create("nfnetlink_queue", 0440,

proc_net_netfilter, &nfqnl_file_ops))

goto cleanup_subsys;

#endif


*********************************************************************************

这个函数与我们要分析的主题关系,其实并不太大。register_netdevice_notifier函数是子系统之间进行通信的手段,采用了订阅者/发布者的设计模式。netdevice(网络设备)的状态发生改变时,会调用通知链上的每一个回调函数,包括nfqnl_dev_notifier。我们在接下来,先分析nfqnl_dev_notifier

register_netdevice_notifier(&nfqnl_dev_notifier);

return status;


#ifdef CONFIG_PROC_FS

cleanup_subsys:

nfnetlink_subsys_unregister(&nfqnl_subsys);

#endif

cleanup_netlink_notifier:

netlink_unregister_notifier(&nfqnl_rtnl_notifier);

return status;

}



综上所述:在nf_queue的初始化函数当中,做了以下几件事情。

  1. 初始化instance_table数组,数组长度16. 每一个元素都是struct hlist_head结构。实际指向的结构是

************************************************************************************

该结构体相当重要,每当用户定义一个回调函数时,即用户态的nfq_handle结构中的 nfq_q_handle *qh_list 成员又增加了一个元素。此时,内核也会增加一个相应nfqnl_instance实例。


struct nfqnl_instance {

struct hlist_node hlist; /* global list of queues,横向来说是一个哈希表 */

struct rcu_head rcu;


int peer_pid; /*记录着用户态的地址*/

unsigned int queue_maxlen;

unsigned int copy_range; /*拷贝给用户的数据包的最大值*/

unsigned int queue_dropped; /*因为队列爆满,丢弃掉的数据包的个数*/

unsigned int queue_user_dropped; /*用户丢弃掉的数据包的个数.*/


u_int16_t queue_num; /* number of this queue,哈希表就是以queue_num进行哈希,相同的哈希值再跟在hlist_head后面。*/

u_int8_t copy_mode; /*拷贝模式*/

/*

* Following fields are dirtied for each queued packet,

* keep them in same cache line if possible.

*/

spinlock_t lock;

unsigned int queue_total; /*队列中元素的个数*/

unsigned int id_sequence; /* 'sequence' of pkt ids */

struct list_head queue_list; /* packets in queue, 数据包被挂载的地方 */

};


2、调用netlink_register_notifier(&nfqnl_rtnl_notifier);函数,在netlink_register链上注册一个回调函数nfqnl_rtnl_notifiernotifier机制是子系统之间进行通信的手段,当一个子系统发生状态的变化是,会调用通知链上所有的回调函数,而回调函数是其它子系统挂接上去的。 netlink_register链所在的子系统(或称为netlink_register子系统)会检测用户态socket的连接状况,如果发生改变则发出通知。

nfqnl_rcv_nl_event(struct notifier_block *this,

unsigned long event, void *ptr)

{

struct netlink_notify *n = ptr;


/*nfqnl_rtnl_notifier对事件是NETLINK_URELEASE(如用户程序关闭),且协议是NETLINK_NETFILTER(说明关闭了一个socket连接),那么就该以pid为索引,清空在内核中保存的一些资源*/

if (event == NETLINK_URELEASE && n->protocol == NETLINK_NETFILTER) {

int i;


/* destroy all instances for this pid */

spin_lock(&instances_lock);

****************************************************************************

就是将instance_table遍历(注意是数组中的每个元素),凡是存在peer_id 与事件的产生者的pid相同,那么从哈希链表去去掉。

注意:之所以要进行遍历,是因为用户太的一个socket上面有多个queue_num值的回调函数。所以并非简单的以queue_num为索引来释放资源,而是以pid来进行释放

for (i = 0; i < INSTANCE_BUCKETS; i++) {

struct hlist_node *tmp, *t2;

struct nfqnl_instance *inst;

struct hlist_head *head = &instance_table[i];


hlist_for_each_entry_safe(inst, tmp, t2, head, hlist) {

if ((n->net == &init_net) &&

(n->pid == inst->peer_pid))

__instance_destroy(inst);

}

}

spin_unlock(&instances_lock);

}

return NOTIFY_DONE;

}


3、调用函数status = nfnetlink_subsys_register(&nfqnl_subsys),将nfqnl_subsys挂到subsys_table 上去,就相当与

subsys_table[n->subsys_id] = n


4、调用函数register_netdevice_notifier(&nfqnl_dev_notifier),当网络设备状态发生改变时(例如新安装了一个网卡,或者网线被拔掉),通知链上的所有回调函数被调用。可先不必理会。


那么我们在subsys_table上注册的 nfqnl_subsys的类型如下,再次强调一下下:

static const struct nfnl_callback nfqnl_cb[NFQNL_MSG_MAX] = {

[NFQNL_MSG_PACKET] = { .call_rcu = nfqnl_recv_unsupp,

.attr_count = NFQA_MAX, },

[NFQNL_MSG_VERDICT] = { .call_rcu = nfqnl_recv_verdict,

.attr_count = NFQA_MAX,

.policy = nfqa_verdict_policy },

[NFQNL_MSG_CONFIG] = { .call = nfqnl_recv_config,

.attr_count = NFQA_CFG_MAX,

.policy = nfqa_cfg_policy },

[NFQNL_MSG_VERDICT_BATCH]={ .call_rcu = nfqnl_recv_verdict_batch,

.attr_count = NFQA_MAX,

.policy = nfqa_verdict_batch_policy },

};


nfqnl_recv_config

函数原型:static int nfqnl_recv_config(struct sock *ctnl, struct sk_buff *skb,

const struct nlmsghdr *nlh, const struct nlattr * const nfqa[])

  1. struct sock *ctnl 根本没有用它,就先不考虑

  2. struct sk_buff *skb 这个肯定是用户态发来的数据包的完整形式的表达

  3. const struct nlmsghdr *nlh 这应该是skb的数据缓冲区的起始值,更具有实用价值

  4. const struct nlattr * const nfqa[] 这个是内核对缓冲区内容的包装,与用户态一样。

首先,该函数被调用的时机是当消息类型NFQNL_MSG_CONFIG时;函数都做了些什么?

static int nfqnl_recv_config(struct sock *ctnl, struct sk_buff *skb,

const struct nlmsghdr *nlh,

const struct nlattr * const nfqa[])

{

struct nfgenmsg *nfmsg = NLMSG_DATA(nlh);

u_int16_t queue_num = ntohs(nfmsg->res_id);

struct nfqnl_instance *queue;

struct nfqnl_msg_config_cmd *cmd = NULL;

int ret = 0;



if (nfqa[NFQA_CFG_CMD]) {

cmd = nla_data(nfqa[NFQA_CFG_CMD]);


************************************************************************

来让我们了解一下协议绑定的真正含义吧。内核需要为每一类协议都提供一个队列(用于存放交付给用户的数据包,但是用户还没有返回处理意见的)。那么内核就提供了一个如下的数据类型:

static const struct nf_queue_handler *queue_handler[NFPROTO_NUMPROTO] ;

struct nf_queue_handler类型如下:

struct nf_queue_handler {

int (*outfn)(struct nf_queue_entry *entry,

unsigned int queuenum); //每个数据包被封装为了一个entry. 该函数的作用就是将属于queue_num的数据包挂接到它对应的instance_table[i]下。

char *name;

};

NF_QUEUEbind,就是queue_handle[pf] = {&nfqnl_enqueue_packet, “nf_queue”}. nfqnl_enqueue_packet 负责将数据包入队列.

所以不bind不行啊!

***************************************************************************************

/* Commands without queue context - might sleep */

switch (cmd->command) {

case NFQNL_CFG_CMD_PF_BIND:

return nf_register_queue_handler(ntohs(cmd->pf),

&nfqh); //协议解绑定

case NFQNL_CFG_CMD_PF_UNBIND:

return nf_unregister_queue_handler(ntohs(cmd->pf),

&nfqh); //协议绑定

}

}


rcu_read_lock();

queue = instance_lookup(queue_num); //instance_table中查找.

if (queue && queue->peer_pid != NETLINK_CB(skb).pid) {

ret = -EPERM;

goto err_out_unlock; //没找到,就错误返回.

}


**********************************************************************************

从该if语句判断可知,instance_tables就是当用户调用nfq_create_queue,真真正正要创建一个回调函数时,增加一个元素。

if (cmd != NULL) {

switch (cmd->command) {

case NFQNL_CFG_CMD_BIND:

if (queue) { //根据queue_num的值已经被人注册了。

ret = -EBUSY;

goto err_out_unlock;

}

//这个函数创建了一个nfqnl_instance的结构,可以看到它默认的copy_modeNFQNL__COPY_NONE. 所有数据包的queue_num为它的包,都会被挂接到它的下面.

queue = instance_create(queue_num, NETLINK_CB(skb).pid);

if (IS_ERR(queue)) {

ret = PTR_ERR(queue);

goto err_out_unlock;

}

break;

case NFQNL_CFG_CMD_UNBIND:

if (!queue) { //同理

ret = -ENODEV;

goto err_out_unlock;

}

instance_destroy(queue);

break;

case NFQNL_CFG_CMD_PF_BIND:

case NFQNL_CFG_CMD_PF_UNBIND:

break;

default:

ret = -ENOTSUPP;

break;

}

}


*********************************************************************

执行到这里一般表示,用户态调用了nfq_set_mode函数。内核态就调用相应的nfqnl_set_mode,注意这个函数填充的是queue结构。

if (nfqa[NFQA_CFG_PARAMS]) {

struct nfqnl_msg_config_params *params;


if (!queue) { //首先queue必须存在

ret = -ENODEV;

goto err_out_unlock;

}

params = nla_data(nfqa[NFQA_CFG_PARAMS]);

nfqnl_set_mode(queue, params->copy_mode,

ntohl(params->copy_range));

}



***********************************************************************

在我们的用户态的实验当中,没有设置nf_queue中队列长度的代码。实际上也是发送NFQA_CFG_QUEUE_MAXLEN即可。

if (nfqa[NFQA_CFG_QUEUE_MAXLEN]) {

__be32 *queue_maxlen;


if (!queue) {

ret = -ENODEV;

goto err_out_unlock;

}

queue_maxlen = nla_data(nfqa[NFQA_CFG_QUEUE_MAXLEN]);

spin_lock_bh(&queue->lock);

queue->queue_maxlen = ntohl(*queue_maxlen);

spin_unlock_bh(&queue->lock);

}


err_out_unlock:

rcu_read_unlock();

return ret;

}


综上所述:用户发送的消息类型为NFQNL_MSG_CONFIG时,可以进行的操作就是

enum nfqnl_attr_config {

NFQA_CFG_UNSPEC,

NFQA_CFG_CMD, /* nfqnl_msg_config_cmd,又分为了四小项 */

NFQA_CFG_PARAMS, /* nfqnl_msg_config_params,又分为了三小项目 */

NFQA_CFG_QUEUE_MAXLEN, /* __u32 */

__NFQA_CFG_MAX

};


Nfqnl_recv_verdict

参数分析:

static int nfqnl_recv_verdict(struct sock *ctnl, struct sk_buff *skb,

const struct nlmsghdr *nlh,

const struct nlattr * const nfqa[])

与上一节相同,省略。


static int nfqnl_recv_verdict(struct sock *ctnl, struct sk_buff *skb,

const struct nlmsghdr *nlh, const struct nlattr * const nfqa[])

{

struct nfgenmsg *nfmsg = NLMSG_DATA(nlh);

u_int16_t queue_num = ntohs(nfmsg->res_id);


struct nfqnl_msg_verdict_hdr *vhdr;

struct nfqnl_instance *queue;

unsigned int verdict;

struct nf_queue_entry *entry;


queue = instance_lookup(queue_num); 寻找,注意它lookup使用的方法。

if (!queue)


queue = verdict_instance_lookup(queue_num, NETLINK_CB(skb).pid);

if (IS_ERR(queue))

return PTR_ERR(queue);


vhdr = verdicthdr_get(nfqa); //获得数据包中nfqnl_msg_verdict_hdr类型结构体

if (!vhdr)

return -EINVAL;


verdict = ntohl(vhdr->verdict);


entry = find_dequeue_entry(queue, ntohl(vhdr->id)); //摘下entry

if (entry == NULL)

return -ENOENT;

if (nfqa[NFQA_PAYLOAD]) {

if (nfqnl_mangle(nla_data(nfqa[NFQA_PAYLOAD]),

nla_len(nfqa[NFQA_PAYLOAD]), entry) < 0) //如果数据包有修改,则修改之.

verdict = NF_DROP;

}


if (nfqa[NFQA_MARK])

entry->skb->mark = ntohl(nla_get_be32(nfqa[NFQA_MARK]));


nf_reinject(entry, verdict); //这里关乎对数据包的处理结果.

return 0;

}


接下来,看一下nf_reinject是如何让数据包继续在协议栈中传输起来的。

void nf_reinject(struct nf_queue_entry *entry, unsigned int verdict)

{

struct sk_buff *skb = entry->skb;

struct list_head *elem = &entry->elem->list;

const struct nf_afinfo *afinfo;

int err;

rcu_read_lock();

nf_queue_entry_release_refs(entry); //queue中释放.

/* Continue traversal iff userspace said ok... */

if (verdict == NF_REPEAT) {

elem = elem->prev;

verdict = NF_ACCEPT;

}

if (verdict == NF_ACCEPT) {

afinfo = nf_get_afinfo(entry->pf);

if (!afinfo || afinfo->reroute(skb, entry) < 0)

verdict = NF_DROP;

}

if (verdict == NF_ACCEPT) { //继续执行后面的hook回调函数.

next_hook:

verdict = nf_iterate(&nf_hooks[entry->pf][entry->hook],

skb, entry->hook,

entry->indev, entry->outdev, &elem,

entry->okfn, INT_MIN);

}

switch (verdict & NF_VERDICT_MASK) {

case NF_ACCEPT:

case NF_STOP:

local_bh_disable();

entry->okfn(skb);

local_bh_enable();

break;

case NF_QUEUE:

err = __nf_queue(skb, elem, entry->pf, entry->hook,

entry->indev, entry->outdev, entry->okfn,

verdict >> NF_VERDICT_QBITS);

if (err < 0) {

if (err == -ECANCELED)

goto next_hook;

if (err == -ESRCH &&

(verdict & NF_VERDICT_FLAG_QUEUE_BYPASS))

goto next_hook;

kfree_skb(skb);

}

break;

case NF_STOLEN:

break;

default:

kfree_skb(skb);

}

rcu_read_unlock();

kfree(entry);

}

阅读(5528) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~