全部博文(140)
分类: 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的初始化函数当中,做了以下几件事情。
初始化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_notifier。notifier机制是子系统之间进行通信的手段,当一个子系统发生状态的变化是,会调用通知链上所有的回调函数,而回调函数是其它子系统挂接上去的。 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 }, }; |
函数原型:static int nfqnl_recv_config(struct sock *ctnl, struct sk_buff *skb,
const struct nlmsghdr *nlh, const struct nlattr * const nfqa[])
struct sock *ctnl 根本没有用它,就先不考虑
struct sk_buff *skb 这个肯定是用户态发来的数据包的完整形式的表达
const struct nlmsghdr *nlh 这应该是skb的数据缓冲区的起始值,更具有实用价值
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_mode是NFQNL__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
};
参数分析:
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); } |