分类: LINUX
2015-01-05 11:29:24
原文地址:NF_QUEUE原理分析3——从用户态程序开始分析 作者:yandongxiao
我们找到了一个函数调用链:recv → nfq_handle_packet → nfnl_handle_packet → __nfnl_handle_msg → ssh->cb[type].call() → __nfq_rcv_pkt → 用户定义的回调函数。
用户调用recv接收数据包,调用 nfq_handle_packet处理数据包。nfq_handle_packet直接调用了 nfnl_handle_packet函数。
while ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0) {
nfq_handle_packet(h, buf, rv); /**nfq_create_queue里面的回调函数将会被调用*/
} |
该函数就是在buff中一遍遍的找到netlink message的头,并传递给__nfnl_handle_msg。注意,若__nfnl_handle_msg返回值小于0,那么函数直接返回。这也是我们判断用户注册的回调函数的返回值的标准 int nfnl_handle_packet(struct nfnl_handle *h, char *buf, int len)
{
while (len >= NLMSG_SPACE(0)) {
u_int32_t rlen;
struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
if (nlh->nlmsg_len < sizeof(struct nlmsghdr)
|| len < nlh->nlmsg_len)
return -1;
rlen = NLMSG_ALIGN(nlh->nlmsg_len);
if (rlen > len)
rlen = len;
if (__nfnl_handle_msg(h, nlh, rlen) < 0)
return -1;
len -= rlen; buf += rlen; }
return 0; } |
static int __nfnl_handle_msg(struct nfnl_handle *h, struct nlmsghdr *nlh, int len) { struct nfnl_subsys_handle *ssh; u_int8_t type = NFNL_MSG_TYPE(nlh->nlmsg_type); u_int8_t subsys_id = NFNL_SUBSYS_ID(nlh->nlmsg_type);
int err = 0;
if (subsys_id > NFNL_MAX_SUBSYS) return -1;
ssh = &h->subsys[subsys_id]; //找到子系统的nfnl_subsys_hanle结构体.
if (nlh->nlmsg_len < NLMSG_LENGTH(NLMSG_ALIGN(sizeof(struct nfgenmsg)))) return -1;
if (type >= ssh->cb_count) return -1;
if (ssh->cb[type].attr_count) { //子系统的某一类消息,如果存在属性的话。就会创建一个struct nfattr * []的数组。 struct nfattr *nfa[ssh->cb[type].attr_count];
err = nfnl_check_attributes(h, nlh, nfa); //将属性的头指针都放到nfa当中 if (err < 0) return err; if (ssh->cb[type].call) return ssh->cb[type].call(nlh, nfa, ssh->cb[type].data); //调用我们所谓的统筹调度函数 }
return 0; } |
static int __nfq_rcv_pkt(struct nlmsghdr *nlh, struct nfattr *nfa[], void *data) { struct nfgenmsg *nfmsg = NLMSG_DATA(nlh); struct nfq_handle *h = data; //pkt_cb中将data指向了nfq_handle. u_int16_t queue_num = ntohs(nfmsg->res_id); //数据包所属的queue_num
/*nfq_handle中的nfq_q_handle *qh_list是一个链表结构,链表中元素的添加我们稍后讲,find_qh就是遍历链表中的每个元素,与之id进行比较。并返回链表中的节点. struct nfq_q_handle *qh = find_qh(h, queue_num); struct nfq_data nfqa;
if (!qh) return -ENODEV; if (!qh->cb) return -ENODEV; nfqa.data = nfa;
return qh->cb(qh, nfmsg, &nfqa, qh->data); } static struct nfnl_callback pkt_cb = { .call = &__nfq_rcv_pkt, .attr_count = NFQA_MAX, }; |
此时用户注册的回调函数终于被调用了。
综上所述:nf_queue仍然使用的netlink机制与内核之间进行通信,不同的是nf_queue根据不同的queue_num,将数据包传递给不同的用户注册的回调函数。但是请注意,它们共用一个socket套接字。