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

2015年(5)

2014年(135)

分类: LINUX

2014-03-02 15:28:21

应用程序和内核的通信机制是Netlink,我们要分析两件事:1nfq_handle是用户和内核进行通信的句柄,该结构保存了所有与内核通信所必须的数据成员;2、既然是通信,一定有协议格式。 

         nfq_handle结构

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 *nfnlh

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支持多播,个数为32nl_groups 是多播组的掩码。例如,该socket想收听139号多播地址传来的信息,那么nl_groups的第139为置为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_handlesubsys成员还没有被初始化,因为我们只是利用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放到这里分析吧!

struct nfnl_subsys_handle * nfnl_subsys_open(

struct nfnl_handle *nfnlh, u_int8_t subsys_id,

u_int8_t cb_count(), u_int32_t subscriptions)

{

struct nfnl_subsys_handle *ssh;

assert(nfnlh);


if (subsys_id > NFNL_MAX_SUBSYS) {

errno = ENOENT;

return NULL;

}


/*

Subsys_id的值一般取以下的值

#define NFNL_SUBSYS_NONE 0

#define NFNL_SUBSYS_CTNETLINK 1

#define NFNL_SUBSYS_CTNETLINK_EXP 2

#define NFNL_SUBSYS_QUEUE 3

#define NFNL_SUBSYS_ULOG 4

#define NFNL_SUBSYS_OSF 5

#define NFNL_SUBSYS_IPSET 6

#define NFNL_SUBSYS_COUNT 7

*/

ssh = &nfnlh->subsys[subsys_id];

if (ssh->cb) {

errno = EBUSY;

return NULL;

}


/*

enum nfqnl_msg_types {

NFQNL_MSG_PACKET, /* packet from kernel to userspace */

NFQNL_MSG_VERDICT, /* verdict from userspace to kernel */

NFQNL_MSG_CONFIG, /* connect to a particular queue */

NFQNL_MSG_VERDICT_BATCH, /* batchv from userspace to kernel */

NFQNL_MSG_MAX

};

一般分配的回调结构提nfnl_callback的值得够大,4.

*/

ssh->cb = calloc(cb_count, sizeof(*(ssh->cb)));

if (!ssh->cb)

return NULL;

ssh->nfnlh = nfnlh;

ssh->cb_count = cb_count;

ssh->subscriptions = subscriptions;

ssh->subsys_id = subsys_id; //虽然它也作为h->nlnlh.subsys的下标


/* although now we have nfnl_join to subscribe to certain

* groups, just keep this to ensure compatibility ,同理更新绑定信息*/

if (recalc_rebind_subscriptions(nfnlh) < 0) {

free(ssh->cb);

ssh->cb = NULL;

return NULL;

}


return ssh;

}



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

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;

}

还是在这里分析 nfnl_callback_register这个结构吧!

int nfnl_callback_register(struct nfnl_subsys_handle *ssh,

u_int8_t type, struct nfnl_callback *cb)

{

assert(ssh);

assert(cb);


if (type >= ssh->cb_count) {

errno = EINVAL;

return -1;

}


memcpy(&ssh->cb[type], cb, sizeof(*cb));


return 0;

}



通过对以上代码的分析可知,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 messagequeue_num的值,递交给用户注册的不同的回调函数(用户可利用不同的queue_num,注册多个回调函数)。这也是很快将要分析的内容。

   Struct nfnl_subsys_handle

在上一节已经分析过了,struct nfnl_subsys_handle *nfnlssh = nfnlh->subsys[NFNL_SUBSYS_QUEUE].


   struct nfq_q_handle *qh_list

先看一下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,那么继续处理。

*/

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