Linux.ChinaUnix.net
ChinaUnix | Linux首页 | 新闻 | 博客 | 文章 | 专栏 | 新手 | 方案 | 图书 | 下载 | 人才 | 手册 | wiki | 搜索     
Linux论坛
  会员: 密码: 免费注册 | 忘记密码 | 会员登录 | 搜索 | 帮助 


原创精华帖子 Netfilter日志记录的实现分析

首页 » CU论坛 » Linux » 汇总贴列表 » 网络问题 »  
[打印] [订阅] [收藏] [推荐给朋友] [本帖文本页]
  本主题由 platinum 于 2008-1-23 00:19 加入精华 
独孤九贱   帅哥 (九贱)
天使



UID:83191
注册:2003-8-12
最后登录: 2008-07-18
帖子:1145
精华:13

可用积分:1317
信誉积分:100
专家积分:0 (本版)

来自:山城重庆
状态:...离线...

[资料] [站内短信] [Blog]


顶部
1楼 发表于 2007-11-21 14:47 
好久不灌水,来水一贴。
最近在用ulogd时,遇到一个小问题,就是经常用户空间收到的数据没有二层帧。不知是何原因,于是来看了一个Netfilter中ulogd模块(不是应用程序)的实现,虽然仍旧没有找到原因,也顺便把代码分析的笔记放上来共享一下。
注意:
1、看的是很老的ulogd 1.2.4的内核实现,不是最新的2.0,最新的好像用的libnetlink接口,偶还没有时间玩这个东东;
2、内核版本2.6.12

首先是初始化全局变量,创建netlink套接字,注册netlink target:

static int __init init(void)
{
        int i;

        DEBUGP("ipt_ULOG: init module\n");

        if (nlbufsiz >= 128*1024) {
                printk("Netlink buffer has to be <= 128kB\n");
                return -EINVAL;
        }

        /* ulog_buffers  这个数据很重要,它的下标对应的ulogd使用的netlink group。ulog_buffers [i]用来缓存数据,可以存放一个或多个记录的skb*/
        for (i = 0; i < ULOG_MAXNLGROUPS; i++) {
                init_timer(&ulog_buffers[i].timer);
                ulog_buffers[i].timer.function = ulog_timer;  //这个定时器后面会分析到
                ulog_buffers[i].timer.data = i;
        }

        nflognl = netlink_kernel_create(NETLINK_NFLOG, NULL);
        if (!nflognl)
                return -ENOMEM;

        if (ipt_register_target(&ipt_ulog_reg) != 0) {
                sock_release(nflognl->sk_socket);
                return -EINVAL;
        }
        if (nflog)
                nf_log_register(PF_INET, &ipt_logfn);
       
        return 0;
}

target函数ipt_ulog_target是个包裹函数,转向至ipt_ulog_packet:

static unsigned int ipt_ulog_target(struct sk_buff **pskb,
                                    const struct net_device *in,
                                    const struct net_device *out,
                                    unsigned int hooknum,
                                    const void *targinfo, void *userinfo)
{
        struct ipt_ulog_info *loginfo = (struct ipt_ulog_info *) targinfo;

        ipt_ulog_packet(hooknum, *pskb, in, out, loginfo, NULL);

        return IPT_CONTINUE;
}

ipt_ulog_packet实现了数据的记录,需要两点准备知识:
1、用户态各个参数的作用,看一下帮助:

--ulog-nlgroup nlgroup         NETLINK group used for logging
--ulog-cprange size            Bytes of each packet to be passed
--ulog-qthreshold              Threshold of in-kernel queue
--ulog-prefix prefix           Prefix log messages with this prefix.

这样才能理解对应的结构:

struct ipt_ulog_info {
        unsigned int nl_group;
        size_t copy_range;
        size_t qthreshold;
        char prefix[ULOG_PREFIX_LEN];
};

各个成员的含义

2、Linux Netlink的一些基本知识,以前偶在论坛上写过一贴关于此的,可以找一下,仅供参考;

来看ipt_ulog_packet函数:

static void ipt_ulog_packet(unsigned int hooknum,
                            const struct sk_buff *skb,
                            const struct net_device *in,
                            const struct net_device *out,
                            const struct ipt_ulog_info *loginfo,
                            const char *prefix)
{
        ulog_buff_t *ub;
        ulog_packet_msg_t *pm;
        size_t size, copy_len;
        struct nlmsghdr *nlh;

        /* ffs == find first bit set, necessary because userspace
         * is already shifting groupnumber, but we need unshifted.
         * ffs() returns [1..32], we need [0..31] */
        unsigned int groupnum = ffs(loginfo->nl_group) - 1;

        /*
         * 计算需要拷贝的skb长度,如果未设置或者设置的值大于包的长度,则len为包长,否则取用户设置值
         */
        if ((loginfo->copy_range == 0) ||
            (loginfo->copy_range > skb->len)) {
                copy_len = skb->len;
        } else {
                copy_len = loginfo->copy_range;
        }

        /* 计算总长度:Netlink报头长度+ulog报头长度+数据包长度 */
        size = NLMSG_SPACE(sizeof(*pm) + copy_len);

        /* 取得对应Netlink group号的ulog数据包缓存 */
        ub = &ulog_buffers[groupnum];
       
        /* 暂时还没有理会,为什么非要这么大一个锁? */
        LOCK_BH(&ulog_lock);

        if (!ub->skb) {
                /* 如果没有,则分配之 */
                if (!(ub->skb = ulog_alloc_skb(size)))
                        goto alloc_failure;
        } else if (ub->qlen >= loginfo->qthreshold ||
                   size > skb_tailroom(ub->skb)) {
                /*
                 * 如果队列已满,或者当前缓存的剩余空间已经不足已装下当前包,
                 * 则把原来的发送至用户空间,然后再为当前包重新分配
                 */

                ulog_send(groupnum);

                if (!(ub->skb = ulog_alloc_skb(size)))
                        goto alloc_failure;
        }

        DEBUGP("ipt_ULOG: qlen %d, qthreshold %d\n", ub->qlen,
                loginfo->qthreshold);

        /* 初始化Netlink 消息首部 */
        nlh = NLMSG_PUT(ub->skb, 0, ub->qlen, ULOG_NL_EVENT,
                        sizeof(*pm)+copy_len);
        ub->qlen++;

        /* 跳过消息首部,指向数据区 */
        pm = NLMSG_DATA(nlh);

        /* 设置时间戳,如果没有的话 */
        if (skb->stamp.tv_sec == 0)
                do_gettimeofday((struct timeval *)&skb->stamp);

        /* 填充Netlink数据区中的ulog数据首部 */
        pm->data_len = copy_len;
        pm->timestamp_sec = skb->stamp.tv_sec;
        pm->timestamp_usec = skb->stamp.tv_usec;
        pm->mark = skb->nfmark;
        pm->hook = hooknum;
       
        /* 如果用户指定了一个前缀,设置之 */
        if (prefix != NULL)
                strncpy(pm->prefix, prefix, sizeof(pm->prefix));
        else if (loginfo->prefix[0] != '\0')
                strncpy(pm->prefix, loginfo->prefix, sizeof(pm->prefix));
        else
                *(pm->prefix) = '\0';
       
        /*
         * 要记录二层的帧,需要满足以下几个条件:
         * 1、存在一个输出设备(从本机发出的包,还没有二层帧);
         * 2、设备对应的二层的帧头部长度应大于0;
         * 3、…………………………………………………………………………小地ulog的规则;
         */
        if (in && in->hard_header_len > 0
            && skb->mac.raw != (void *) skb->nh.iph
            && in->hard_header_len <= ULOG_MAC_LEN) {
                memcpy(pm->mac, skb->mac.raw, in->hard_header_len);
                pm->mac_len = in->hard_header_len;
        } else
                pm->mac_len = 0;
       
        /* 拷贝进入接口,如果有的话 */
        if (in)
                strncpy(pm->indev_name, in->name, sizeof(pm->indev_name));
        else
                pm->indev_name[0] = '\0';
       
        /* 拷贝流出接口,如果有的话 */
        if (out)
                strncpy(pm->outdev_name, out->name, sizeof(pm->outdev_name));
        else
                pm->outdev_name[0] = '\0';

        /* 拷贝skb中的数据. */
        if (skb_copy_bits(skb, 0, pm->payload, copy_len) < 0)
                BUG();
       
        /* 如果队列大于1,即缓存了多个包,设置一个多重标志位,这个标志位在发送时清除 */
        if (ub->qlen > 1) {
                ub->lastnlh->nlmsg_flags |= NLM_F_MULTI;
        }

        /* lastnlh指向当前队列中的最后一个包,即当前包 */
        ub->lastnlh = nlh;

        /*
         * 定时器期的功能函数在ulog模块初始化时指向了ulog_timer(),它用于在指定的时间内,清空缓存,
         * 即除了队列长度达到指定要求、或者缓存空间不足之外,还有一个定时向用户空间发送的机制。
         */
        if (!timer_pending(&ub->timer)) {
                ub->timer.expires = jiffies + flushtimeout * HZ / 100;
                add_timer(&ub->timer);
        }

        /*
         * ub->qlen是当前netlink group下的已经收聚的队列的长度,qthreshold是用户指定的,需要收集的阀值,当达到这个值时,将其发送至用户空间
         */
        if (ub->qlen >= loginfo->qthreshold) {
                if (loginfo->qthreshold > 1)
                        nlh->nlmsg_type = NLMSG_DONE;
                ulog_send(groupnum);
        }

        UNLOCK_BH(&ulog_lock);

        return;

nlmsg_failure:
        PRINTR("ipt_ULOG: error during NLMSG_PUT\n");

alloc_failure:
        PRINTR("ipt_ULOG: Error building netlink message\n");

        UNLOCK_BH(&ulog_lock);
}

这个函数中,涉及两个重要的函数,一个是分配ulog的skb,用于存储记录的数据,另一个是发送函数。先来看发送。
netlink的发送数据包,是通过调用API netlink_broadcast函数实现的,这里还要完成其它一些工作,比如删除定时器,因为数据已经发出去了,不需要它了,还有就是清除标志变量,修改一些起控制作用的成员的值,等等:

/* send one ulog_buff_t to userspace */
static void ulog_send(unsigned int nlgroupnum)
{
        ulog_buff_t *ub = &ulog_buffers[nlgroupnum];

        if (timer_pending(&ub->timer)) {
                DEBUGP("ipt_ULOG: ulog_send: timer was pending, deleting\n");
                del_timer(&ub->timer);
        }

        /* last nlmsg needs NLMSG_DONE */
        if (ub->qlen > 1)
                ub->lastnlh->nlmsg_type = NLMSG_DONE;

        NETLINK_CB(ub->skb).dst_groups = (1 << nlgroupnum);
        DEBUGP("ipt_ULOG: throwing %d packets to netlink mask %u\n",
                ub->qlen, nlgroupnum);
        netlink_broadcast(nflognl, ub->skb, 0, (1 << nlgroupnum), GFP_ATOMIC);

        ub->qlen = 0;
        ub->skb = NULL;
        ub->lastnlh = NULL;

}

另一个是分配函数:

static struct sk_buff *ulog_alloc_skb(unsigned int size)
{
        struct sk_buff *skb;

        /* alloc skb which should be big enough for a whole
         * multipart message. WARNING: has to be <= 131000
         * due to slab allocator restrictions */

        skb = alloc_skb(nlbufsiz, GFP_ATOMIC);
        if (!skb) {
                PRINTR("ipt_ULOG: can't alloc whole buffer %ub!\n",
                        nlbufsiz);

                /* try to allocate only as much as we need for
                 * current packet */

                skb = alloc_skb(size, GFP_ATOMIC);
                if (!skb)
                        PRINTR("ipt_ULOG: can't even allocate %ub\n", size);
        }

        return skb;
}

分配skb,就是调用alloc_skb函数,不过这个分配函数需要注意的一点就是分配的包的大小的问题,它是先尝试分配
static unsigned int nlbufsiz = 4096;
这么大的空间,如果失败了,才按需分配。这样做的原因还是在于,一个缓存可能要存放多个数据包。


忽忽浏览了一下,错误之处,大家帮偶指正。
OK,收工,继续去看为什么偶的系统记录下来的包,应该有而没有二层帧了……



您对本贴的看法:鲜花[0] 臭蛋[0]

__________________________________

擦掉眼泪,抚平伤口,站起来重建家园,一定要坚强!!!
CU可用积分兑换Linux/Unix精品图书 |《Ubuntu标准教程》书评获奖名单公布 | 致电800-858-2903,了解DELL如何为你量身订制笔记本
songpure520
精灵王
IT民工


CU奥运火炬传递手2008
UID:579784
注册:2007-6-25
最后登录: 2008-07-20
帖子:335
精华:0

可用积分:839
信誉积分:105
专家积分:0 (本版)

来自:广州
状态:...离线...

[资料] [站内短信] [Blog]


顶部
2楼 发表于 2008-1-22 17:34 
这个帖竟然没人顶!!顶一下!!满关注九贱大侠的帖子

[ 本帖最后由 songpure520 于 2008-1-23 22:39 编辑 ]



您对本贴的看法:鲜花[0] 臭蛋[0]
CU可用积分兑换Linux/Unix精品图书 |《Ubuntu标准教程》书评获奖名单公布 | 致电800-858-2903,了解DELL如何为你量身订制笔记本
heizi_liu   帅哥 (heizi_liu)
精灵



UID:489499
注册:2006-11-13
最后登录: 2008-07-16
帖子:203
精华:2

可用积分:212
信誉积分:100
专家积分:0 (本版)

来自:江苏
状态:...离线...

[资料] [站内短信] [Blog]


顶部
3楼 发表于 2008-1-23 09:52 
赞!顶!再好好的顶!



您对本贴的看法:鲜花[0] 臭蛋[0]
CU可用积分兑换Linux/Unix精品图书 |《Ubuntu标准教程》书评获奖名单公布 | 致电800-858-2903,了解DELL如何为你量身订制笔记本
scuwb   帅哥
侠客




UID:601384
注册:2007-8-11
最后登录: 2008-06-25
帖子:40
精华:0

可用积分:39
信誉积分:0
专家积分:0 (本版)

状态:...离线...

[资料] [站内短信] [Blog]


顶部
4楼 发表于 2008-5-11 20:31 
九贱兄写得非常的好~ 我一直在研究NetFilter中的ULOG,这些资料真是精华啊`



您对本贴的看法:鲜花[0] 臭蛋[0]

__________________________________

开心过好每一天!
CU可用积分兑换Linux/Unix精品图书 |《Ubuntu标准教程》书评获奖名单公布 | 致电800-858-2903,了解DELL如何为你量身订制笔记本
ShadowStar   帅哥
风云使者



UID:167404
注册:2004-6-27
最后登录: 2008-07-20
帖子:412
精华:3

可用积分:480
信誉积分:100
专家积分:0 (本版)

来自:沈阳 - 北京
状态:...离线...

[资料] [站内短信] [Blog]


顶部
5楼 发表于 2008-5-12 09:24 


QUOTE:
原帖由 独孤九贱 于 2007-11-21 14:47 发表
好久不灌水,来水一贴。
最近在用ulogd时,遇到一个小问题,就是经常用户空间收到的数据没有二层帧。不知是何原因,于是来看了一个Netfilter中ulogd模块(不是应用程序)的实现,虽然仍旧没有找到原因,也顺便 ...

我没有看那些代码,瞎猜的:

因为skb->data已经偏移到了IP层头部,所以没有二层帧。



您对本贴的看法:鲜花[0] 臭蛋[0]

__________________________________

IPP2P-0.99.16

AMD64 3200+
ASUS nForce4 Ultra
VDATA DDR400 512MB X4
NVRaid 300GB
Geforce 6600GT
CU可用积分兑换Linux/Unix精品图书 |《Ubuntu标准教程》书评获奖名单公布 | 致电800-858-2903,了解DELL如何为你量身订制笔记本

首页 » CU论坛 » Linux » 汇总贴列表 » 网络问题 »

 


Copyright © 2001-2008 ChinaUnix.net All Rights Reserved     联系我们:

感谢所有关心和支持过ChinaUnix的朋友们    转载本站内容请注明原作者名及出处

京ICP证041476号


清除 Cookies - Linux时代 - Archiver - WAP - TOP

Processed in 0.051650 second(s), 8 queries , Gzip enabled