求助:利用netfilter和libnetfilter_queue子系统进行简单上传回传测试出现崩溃问题
各位大神,我想完成一个HTTP访问控制的功能。所以想利用netfilter拦截HTTP数据包并且返回NF_QUEUE,上层应用利用libnetfilter_queue子系统进行接收处理并返回接受或丢弃。目前测试的功能如下:
1)netfilter内核模块挂载到POST_ROUTING,检测到目的端口是80或8080后 return NF_QUEUE,否则 return NF_ACCEPT;
2) libnetfilter_queue上层应用接收到拷贝上来的数据包后,对包进行一个计数,并cout<<" This is a HTTP PACKET"<<endl;而后return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);
现在的情况是,程序运行起来貌似没问题,打开百度首页都能正常计数输出。但是我打开像新浪这种大的门户网站后因为包的数量特别多,我刷新几次之后电脑就会打印oops之后崩溃。这个问题困扰了好久,还望各位请教。
以下是源码:
(1)netfilter内核模块
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/string.h>
#include <linux/kmod.h>
#include <linux/vmalloc.h>
#include <linux/workqueue.h>
#include <linux/spinlock.h>
#include <linux/socket.h>
#include <linux/net.h>
#include <linux/in.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <net/sock.h>
#include <asm/uaccess.h>
#include <asm/unistd.h>
#include <linux/if_arp.h>
//定义钩子函数结构体
struct nf_hook_opspost_hook;
//实例化钩子函数
static unsigned int watch_out(unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
//复制skbuff结构体,将指针指向ip头和tcp头
struct sk_buff *sk;
struct iphdr *iph;
struct tcphdr *tcph;
sk = skb_copy(skb, 1);
iph = ip_hdr(sk);
tcph = (void *) iph + iph->ihl * 4;
//判断协议是否为TCP,是则继续判断,否则放行返回NF_ACCEPT
if ( iph->protocol == IPPROTO_TCP)
{
//判断数据包是否是HTTP数据包,是则将数据包排队NF_QUEUE等待上层空间发回响应策略
if(tcph->dest == htons(8080) || tcph->dest == htons(80)) //利用端口号区分服务,将FTP,HTTP 数据包上传。
{
return NF_QUEUE;
}
else
return NF_ACCEPT;
}
else
return NF_ACCEPT;
}
int init_module()
{
printk("------Client_Lite_Kernel Start------\n");
//将钩子函数注册到POST_RPOTING挂载点上,设置优先级最高。
post_hook.hook = watch_out;
post_hook.pf = PF_INET;
post_hook.priority = NF_IP_PRI_FIRST;
post_hook.hooknum= NF_INET_POST_ROUTING;
nf_register_hook(&post_hook);
return 0;
}
void cleanup_module()
{
printk("------Client_Lite_Kernel finish------\n");
//注销钩子函数
nf_unregister_hook(&post_hook);
}
MODULE_INIT(init_module);
MODULE_EXIT(cleanup_module);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhaoyun");
(2)libnetfilter_queue上层用户应用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <linux/netfilter.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <libnetfilter_queue/libnetfilter_queue.h>
#include <iostream>
#include <string.h>
#define LENGTH 4096
using namespace std;
static int count=0;
static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data)
{
int id = 0, pload_len;
unsigned char *pload;
struct nfqnl_msg_packet_hdr *ph;
//get unique ID of packet in queue
ph = nfq_get_msg_packet_hdr(nfa);
if(ph)
{
id = ntohl(ph->packet_id);
}
//get payload
pload_len = nfq_get_payload(nfa, &pload);
if(pload_len == -1)
{
pload_len = 0;
}
struct iphdr *iph = (struct iphdr *)pload;
struct tcphdr *tcph =(struct tcphdr*)((u_int8_t*)iph + (iph->ihl<<2));
if(tcph->dest == htons(80) || tcph->dest == htons(8080))
{
count++;
cout<<count<<" This is a HTTP PACKET"<<endl;
return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);
}
else
{
return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);
}
}
int main(int argc, const char *argv[])
{
int len, fd;
char buf;
struct nfq_handle *h;
struct nfq_q_handle *qh;
//call nfq_open() to open a NFQUEUE handler
h = nfq_open();
if(!h)
{
fprintf(stderr, "error during nfq_open()\n");
exit(1);
}
//unbinging existing nf_queue handler for PE_INET(if any)
if(nfq_unbind_pf(h, PF_INET) < 0)
{
fprintf(stderr, "error during nfq_unbind_pf()\n");
exit(1);
}
//binding nfnetlink_queue as nf_queue handler for PF_INET
if(nfq_bind_pf(h, PF_INET) < 0)
{
fprintf(stderr, "error during nfq_bind_pf()\n");
exit(1);
}
//binding this socket to queue '0'
qh = nfq_create_queue(h, 0, &cb, NULL);
if(!qh)
{
fprintf(stderr,"error during nfq_create_queue()\n");
exit(1);
}
//setting copy_packet mode
if(nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0)
{
fprintf(stderr, "can't set packet_copy_mode\n");
exit(1);
}
//get the file descriptor associated with the nfqueue handler
fd = nfq_fd(h);
//handle a packet received from the nfqueue subsystem
while ((len = recv(fd, buf, sizeof(buf), 0)) && len >= 0)
{
nfq_handle_packet(h, buf, len);
}
nfq_destroy_queue(qh);
nfq_close(h);
return 0;
}
(3)oops信息
PS:我在Ubuntu12.04LST(内核3.8.0-39-generic),Ubuntu10.04LTS(内核2.6.32-38-generic),中标麒麟V3(内核2.6.32-220.2.1.2.ky3.2.i686),中标麒麟V5(2.6.27.41-170.2.117_ND5_1.i686)上都测试过此代码,虽然崩溃的时间不相同,但是都会出现崩溃。其中中标麒麟打印的OOPS信息如上,Ubuntu直接卡死没有任何输出。
:'(:'(没有人接触过libnetfilter_queue么。 :)) 你在代码里边,不加规则试试程序能不能崩溃
然后一点一点加注释,确定崩的地方
回复 3# kkddkkdd11
通过看oops信息和objdump反编译ko文件,发现是内核模块的问题,错误出在skb_copy上,他有可能拷贝不成功造成sk指针为空,然后就崩溃了。我还是不明百为什么开始能拷贝成功,刷着刷着就不行了。谢谢你的回复,新手小白第一次发帖,十分感动!
回复 1# zhaoyun0819
感觉你写 QUEUE 的那个 kernel module 有点多余啊,直接通过 iptables 下一条 NFQUEUE 的规则即可。
用户态调用 libnetfilter_queue 获取报文即可。
如果你要确定 skb_copy 调用是否正确,可以看一下kernel 中对于 NFQUEUE 这个 target 的实现代码就行了。
回复 1# zhaoyun0819
你看的 kernel module 基本上可以通过类似下面一条iptables 规则搞定
iptables -t mangle -A POSTROUTING -p tcp -m multiport --dport80,8080-j NFQUEUE --queue-num 10
然后专注用户态程序即可。
从最后的oops信息看,是在atomic上下文中发生了调度,从哪儿看出是skb指针为空导致的问题呢? 回复 6# Godbach
嗯,好像是的,这个内核模块确实可以通过iptables规则来实现。是因为我之后在内核模块还有别的代码实现功能,所以简单测试一下。我把拷贝改成sk = skb_copy(skb, GFP_ATOMIC );之后就没有问题了目前,虽然我也具体不太清楚其中的原理。十分感谢!!
回复 7# humjb_1983
确实是上下文,我oops没贴全,另外一次实验的时候还有oops信息提示 EIP is at watch_out+0xf/0x88
我用objdump反汇编找到了是在 iph = ip_hdr(sk);处,所以加了容错,if(sk==NULL){printk("<0>""sk pointer is NULL \n");return NF_ACCEPT;},结果就打印了错误信息,所以发现是sk为空,skb_copy()拷贝没成功。 回复 8# zhaoyun0819
你源代码中 skb_copy() 中第二个参数是 1,GFP_ATOMIC 的值也应该就是 1 啊。
在中断上下文,申请内存的话,是应该用 GFP_ATOMIC。