好久不灌水,来水一贴。
最近在用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,收工,继续去看为什么偶的系统记录下来的包,应该有而没有二层帧了……