全部博文(140)
分类: LINUX
2014-03-02 15:28:21
应用程序和内核的通信机制是Netlink,我们要分析两件事:1、nfq_handle是用户和内核进行通信的句柄,该结构保存了所有与内核通信所必须的数据成员;2、既然是通信,一定有协议格式。
struct nfq_handle { struct nfnl_handle *nfnlh; struct nfnl_subsys_handle *nfnlssh; struct nfq_q_handle *qh_list; }; |
用户调用struct nfq_handle * nfq_open(void) 函数即可完成对struct nfq_handle的填充。接下来,让我们来逐个分析每个数据成员.
struct nfnl_handle { int fd; struct sockaddr_nl local; struct sockaddr_nl peer; u_int32_t subscriptions; u_int32_t seq; //保存创建nfnl_handle的时间 u_int32_t dump; u_int32_t rcv_buffer_size; /* for nfnl_catch */ u_int32_t flags; struct nlmsghdr *last_nlhdr; struct nfnl_subsys_handle subsys[NFNL_MAX_SUBSYS+1]; }; |
Int fd : 用户态利用netlink与内核进行通信,它使用的是标准的socket套接字。所以,fd保存的是socket函数调用的返回值.
struct sockaddr_nl local 和 struct sockaddr_nl peer: 保存用户态地址。Netlink使用struct socketaddr_nl结构保存地址,该结构信息如下:
struct sockaddr_nl { __kernel_sa_family_t nl_family; /* AF_NETLINK */ unsigned short nl_pad; /* zero, 作填充用 */ __u32 nl_pid; /* port ID, 相当与网络通信中的IP+port的作用 */ __u32 nl_groups; /* multicast groups mask */ }; |
Struct sockaddr_nl结构中最重要的两个成员为nl_pid 和 nl_groups. nl_pid 相当与网络通信中的IP+port的作用, 在用户态它的值要做到唯一(稍后介绍如何做)。 nl_groups 为多播所用,netlink支持多播,个数为32,nl_groups 是多播组的掩码。例如,该socket想收听1、3、9号多播地址传来的信息,那么nl_groups的第1,3和9为置为1,其它位置0即可。如果不想接受组播消息,将nl_groups设置为0即可。一般都是置0. http://blog.chinaunix.net/uid-28455968-id-4121859.html 博文有对nl_groups更为详细的介绍和应用。
现在我们就看一下,如何给local 和 peer进行赋值,如下面的代码所示:
******************************************************************************* 完成nfnl_handle的初始化工作 struct nfnl_handle *nfnlh; unsigned int addr_len;
nfnlh = malloc(sizeof(*nfnlh)); if (!nfnlh) return NULL; memset(nfnlh, 0, sizeof(*nfnlh));
******************************************************************************* 创建socket套接字 nfnlh->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER); if (nfnlh->fd == -1) goto err_free;
******************************************************************************** getsocetname的介绍,请看本人的一个例子程序(在其它文章中)。以下代码的主要目的是:1、检查local.nl_family是否为AF_NETLINK , 检查AF_NETLINK的地址信息是否为sizeof(nfnlh->local); 2、将其它local结构的其它变量设置为0(注意必须为0)
nfnlh->local.nl_family = AF_NETLINK; nfnlh->peer.nl_family = AF_NETLINK; addr_len = sizeof(nfnlh->local); getsockname(nfnlh->fd, (struct sockaddr *)&nfnlh->local, &addr_len); if (addr_len != sizeof(nfnlh->local)) { errno = EINVAL; goto err_close; } if (nfnlh->local.nl_family != AF_NETLINK) { errno = EINVAL; goto err_close; }
nfnlh->seq = time(NULL); // 保存nfnl_handle创建的时间 nfnlh->rcv_buffer_size = NFNL_BUFFSIZE; //设置数据包的最大值
******************************************************************************* bind地址 don't set pid here, only first socket of process has real pid !!! binding to pid '0' will default. 这里提醒我们,local中的pid的值要保持为0,当bind函数被调用时系统会为我们分配一个更合适的值(保证唯一性),但是者要求pid的值必须先设置为0。这也是getsockname的一个作用
if (recalc_rebind_subscriptions(nfnlh) < 0) // 这里我们暂且认为就是bind的过程 goto err_close;
******************************************************************************** 再次调用getsockname 此时再调用getsockname将多返回一个信息,即pid的值. getsockname的另外一个作用是使得再次调用bind时返回0(表示成功,连续两次调用bind时,第二次就会返回-1.)进一步分析表明,getsockname被调用后,再次bind就可更新绑定信息。 nf_queue确实对同一socket进行了多次bind,所以这里的getsockname是必须的*/ addr_len = sizeof(nfnlh->local); getsockname(nfnlh->fd, (struct sockaddr *)&nfnlh->local, &addr_len); if (addr_len != sizeof(nfnlh->local)) { errno = EINVAL; goto err_close; } |
通过分析上面的代码,相信local值的填充过程已经清晰了。至于peer值的填充更加简单,即nfnlh->peer.nl_family = AF_NETLINK,其它全部设置为0(内核的PID就是0)。
u_int32_t subscriptions: 即用户态希望监听的多播地址的掩码信息。要清楚subscriptions的作用就要分析刚才包装bind的那个函数, recalc_rebind_subscriptions。
static int recalc_rebind_subscriptions(struct nfnl_handle *nfnlh) { int i, err; u_int32_t new_subscriptions = nfnlh->subscriptions; /*nfnl_handle 所关心的多播地址*/ /*以下是统计nfnl_handle中每个功能子系统所关心的多播地址*/ for (i = 0; i < NFNL_MAX_SUBSYS; i++) new_subscriptions |= nfnlh->subsys[i].subscriptions;
/*这里是关键,首先表明了以上两个subscriptions确实都与多播相关,因为最终赋值给了local.nl_groups,表示的也就是自己希望监听哪些多播,数据到来时,获取该数据包;其次,local.nl_groups的值改变了,需要再一次进行bind操作,通知到操作系统,bind的改变.*/ nfnlh->local.nl_groups = new_subscriptions; err = bind(nfnlh->fd, (struct sockaddr *)&nfnlh->local, sizeof(nfnlh->local)); if (err == -1) return -1;
nfnlh->subscriptions = new_subscriptions; /*更新subscriptions,看来加进来的子系统一般不会的监听地址都不怎么变*/ return 0; } |
u_int32_t seq: seq 已在前面的代码中被赋值了,seq = time(0)。表示创建nfnl_handle的时间。
u_int32_t rcv_buffer_size: 设置recvfrom缓冲区的大小(默认8192),若数据包过长,会发生截断。通过函数nfnl_catch验证
u_int32_t flags : nfnlh->flags |= NFNL_F_SEQTRACK_ENABLED; //暂且没有更多的关于flags的信息。
u_int32_t dump : 暂时不清楚它的含义,猜测保存的是抓到的数据包的个数/字节数
struct nlmsghdr *last_nlhdr : 一个数据包中可包含多个netlink message消息,last_nlhdr指针指向的是刚刚分析过的nlmsghdr结构体。为了验证struct nlmsghdr *last_nlhdr的含义,分析以下代码:
struct nlmsghdr *nfnl_get_msg_next(struct nfnl_handle *h, const unsigned char *buf, size_t len) { struct nlmsghdr *nlh; size_t remain_len;
assert(h); assert(buf); assert(len > 0);
/* if last header in handle not inside this buffer, * drop reference to last header */ if (!h->last_nlhdr || (unsigned char *)h->last_nlhdr >= (buf + len) || (unsigned char *)h->last_nlhdr < buf) { h->last_nlhdr = NULL; return NULL; }
/* n-th part of multipart message */ if (h->last_nlhdr->nlmsg_type == NLMSG_DONE || h->last_nlhdr->nlmsg_flags & NLM_F_MULTI) { /* if last part in multipart message or no * multipart message at all, return */ h->last_nlhdr = NULL; return NULL; }
/* 从这里已经可以看出,每次来的数据包中有可能是多个netlink msg叠加的结果, 而last_nlhdr就是保存最后一个访问到的netlink msg的头部*/ remain_len = (len - ((unsigned char *)h->last_nlhdr - buf)); nlh = NLMSG_NEXT(h->last_nlhdr, remain_len);
if (!NLMSG_OK(nlh, remain_len)) { h->last_nlhdr = NULL; return NULL; } h->last_nlhdr = nlh; return nlh; } |
struct nfnl_subsys_handle subsys[NFNL_MAX_SUBSYS+1]:这是最后一个,也是最为重要的一个数据结构。 按照惯例,先看一下它的数据结构。 NFNL_MAX_SUBSYS=16,即nf_queue只是netfilter netlink系统下的一个子系统,那么我们看一下每一个子系统的结构:
struct nfnl_subsys_handle { struct nfnl_handle *nfnlh; /*就是上面创建的那个nfnl_handle*/ u_int32_t subscriptions; /*与多播相关,表示自己希望监听的多播地址*/ u_int8_t subsys_id; /* 用来标识一个子系统, h->nfnlh.subsys数组的下标值所对应的nfnl_subsys_handle 中的subsys_id 相等*/ u_int8_t cb_count; /*cb指向的内存中,结构体nfnl_callback个数*/ struct nfnl_callback *cb; /* 统筹调度的作用 */ }; |
接下来看一下nf_queue子系统会想subsys中添加什么?
struct nfq_handle *nfq_open_nfnl(struct nfnl_handle *nfnlh) { ********************************************************************************* 创建并初始化nfq_handle实体 struct nfq_handle *h; int err; h = malloc(sizeof(*h)); if (!h) return NULL; memset(h, 0, sizeof(*h)); h->nfnlh = nfnlh; /*nfq_handle 的nfnlh成员被初始化*/
******************************************************************************** 我们清楚,nfnl_handle的subsys成员还没有被初始化,因为我们只是利用netfilter netlink中的QUEUE子系统,所以数组subsys中的元素没必要都初始化,这里以NFNL_SUBSYS_QUEUE 为下标,将一个元素初始化为一个nfnl_subsys_handle结构体。具体的初始化过程我们后面讲,然后再将结构体nfnl_subsys_handle的指针赋值给h->nfnlssh。虽然可以利用h->nfnlh寻找到该子系统。*/ h->nfnlssh = nfnl_subsys_open(h->nfnlh, NFNL_SUBSYS_QUEUE(3), NFQNL_MSG_MAX(4), 0); if (!h->nfnlssh) { /* FIXME: nfq_errno */ goto out_free; }
******************************************************************************** nfnl_subsys_open函数并没有对nfnl_subsys_handle结构中的cb成员进行赋值,该结构是一个回调函数。这里就是利用nfnl_callback_register将回调结构pkt_cb挂接上去,具体挂接动作也在后面分析*/ pkt_cb.data = h; //注意这里的注册的是 NFQNL_MSG_PACKET,总结时再分析。 err = nfnl_callback_register(h->nfnlssh, NFQNL_MSG_PACKET, &pkt_cb); if (err < 0) { nfq_errno = err; goto out_close; } return h; }
|
通过对以上代码的分析可知,struct nfnl_subsys_handle subsys[NFNL_MAX_SUBSYS+1]结构中,每一个元素代表了一个子系统。NF_QUEUE子系统只是其中一个。所以在nfq_open函数中,也只是对subsys[NFNL_SUBSYS_QUEUE]进行初始化,其它填充为0. 而每个nfnl_subsys_handle(子系统)结构中最重要的是他的回调函数结构。有成员: cb_count 和 nfnl_callback。刚才我们说它具有统筹调度的作用是站在NF_QUEUE子系统上说的,因为上面初始化了cb[NFQNL_MSG_PACKET]。 NFQNL_MSG_PACKET是指packet from kernel to userspace. 即HOOK点的回调函数返回值为NF_QUEUE时,内核将数据包递交给了用户态,用户态正是利用刚才注册的回调函数进行数据包的接受,然后根据netlink message中queue_num的值,递交给用户注册的不同的回调函数(用户可利用不同的queue_num,注册多个回调函数)。这也是很快将要分析的内容。
在上一节已经分析过了,struct nfnl_subsys_handle *nfnlssh = nfnlh->subsys[NFNL_SUBSYS_QUEUE].
先看一下nfq_q_handle的结构:
struct nfq_q_handle { struct nfq_q_handle *next; //说明它是一个链表 struct nfq_handle *h; u_int16_t id; //queue_num,唯一标识该结构体 nfq_callback *cb; //真正的回调函数 void *data; //应该是传递给回调函数的参数 };
typedef int nfq_callback(struct nfq_q_handle *gh, struct nfgenmsg *nfmsg, struct nfq_data *nfad, void *data); //这是回调函数的定义。 |
让我们仔细分析一下回调函数的各个参数:
struct nfq_q_handle *qh :应该是qh_list链表中的一个元素,我们不应去修改它的任何值。
struct nfgenmsg *nfmsg: 除去nlmsghdr的内容,也就是说是netlink传输的数据。数据包内容有(nlmsghdr + nfgenmsg + (nlattr + 某结构)* )+ ,注意我们使用正则表达式的一些内容。
struct nfq_data *nfa : 成员只有struct nfattr *nfa[],它并非是内核传递给用户的数据,而是在nfnl_step处理每一个netlink message时(一个数据包中可能会有多个nlmsg,所以用户更应该利用nfa来操作属于自己的nfattr),将所有的struct nfattr的结构地址保存起来,封装成了这个结构给回调函数。
void *data 应该是用户可以给回调函数传送值的过程。
返回值呢:
< 0 , 那么这个数据包接下来的netlink message就不会再被处理了(一个数据包有多个netlink message)
= 0,那么继续处理。
*/