Chinaunix

标题: 求助:利用netfilter和libnetfilter_queue子系统进行简单上传回传测试出现崩溃问题 [打印本页]

作者: zhaoyun0819    时间: 2014-07-02 10:19
标题: 求助:利用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_ops  post_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[LENGTH];
    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直接卡死没有任何输出。

作者: zhaoyun0819    时间: 2014-07-03 09:24
没有人接触过libnetfilter_queue么。
作者: kkddkkdd11    时间: 2014-07-03 11:42
) 你在代码里边,不加规则试试程序能不能崩溃
然后一点一点加注释,确定崩的地方


作者: zhaoyun0819    时间: 2014-07-03 18:02
回复 3# kkddkkdd11

通过看oops信息和objdump反编译ko文件,发现是内核模块的问题,错误出在skb_copy上,他有可能拷贝不成功造成sk指针为空,然后就崩溃了。我还是不明百为什么开始能拷贝成功,刷着刷着就不行了。谢谢你的回复,新手小白第一次发帖,十分感动!


   
作者: Godbach    时间: 2014-07-04 00:04
回复 1# zhaoyun0819

感觉你写 QUEUE 的那个 kernel module 有点多余啊,直接通过 iptables 下一条 NFQUEUE 的规则即可。

用户态调用 libnetfilter_queue 获取报文即可。

如果你要确定 skb_copy 调用是否正确,可以看一下kernel 中对于 NFQUEUE 这个 target 的实现代码就行了。


   
作者: Godbach    时间: 2014-07-04 00:14
回复 1# zhaoyun0819

你看的 kernel module 基本上可以通过类似下面一条  iptables 规则搞定
iptables -t mangle -A POSTROUTING -p tcp -m multiport --dport  80,8080  -j NFQUEUE --queue-num 10


然后专注用户态程序即可。

   
作者: humjb_1983    时间: 2014-07-04 09:15
从最后的oops信息看,是在atomic上下文中发生了调度,从哪儿看出是skb指针为空导致的问题呢?
作者: zhaoyun0819    时间: 2014-07-04 15:30
回复 6# Godbach

嗯,好像是的,这个内核模块确实可以通过iptables规则来实现。是因为我之后在内核模块还有别的代码实现功能,所以简单测试一下。我把拷贝改成sk = skb_copy(skb, GFP_ATOMIC );之后就没有问题了目前,虽然我也具体不太清楚其中的原理。十分感谢!!

   
作者: zhaoyun0819    时间: 2014-07-04 15:35
回复 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()拷贝没成功。
作者: Godbach    时间: 2014-07-04 16:32
回复 8# zhaoyun0819

你源代码中 skb_copy() 中第二个参数是 1,GFP_ATOMIC 的值也应该就是 1 啊。

在中断上下文,申请内存的话,是应该用 GFP_ATOMIC。

   
作者: zhaoyun0819    时间: 2014-07-05 10:10
回复 10# Godbach

我在gfp.h里找到的,不知到有关系没。
#define __GFP_HIGH        ((__force gfp_t)0x20u)        /* Should access emergency pools? */
#define GFP_ATOMIC        (__GFP_HIGH)
   
作者: Godbach    时间: 2014-07-07 14:30
回复 11# zhaoyun0819

嗯。应该是这个问题。我看成另外一个文件里的了

   
作者: ssliao    时间: 2014-09-28 17:16
怎么延时发送包,我加了usleep一段时间,处理icmp包好像还是不行呢
作者: ssliao    时间: 2014-10-10 14:50
搞定了,高兴
作者: melodywxl    时间: 2017-03-24 21:40
Lz,我可以问下,对于2.6.36内核版本的,应该去下载哪个版本的libnetfilter 库呢

作者: HazeC    时间: 2017-03-27 09:25
这个问题重点应该是netfilter调用skb_copy是否处于中断上下文了。




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2