Chinaunix首页 | 论坛 | 博客
  • 博客访问: 305271
  • 博文数量: 42
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 847
  • 用 户 组: 普通用户
  • 注册时间: 2013-02-28 14:14
个人简介

北冥有鱼,其名曰鲲,鲲之大,不知其几千里也;化而为鸟,其名为鹏,鹏之背,不知其几千里也,怒而飞,其翼若垂天之云。

文章分类

全部博文(42)

文章存档

2015年(6)

2014年(7)

2013年(29)

分类: LINUX

2013-02-28 18:45:03

从用户空间来操作内核中Netfilter框架里自定义的HOOK函数

         本文承上一篇博客。主要是和大家探讨一下如何从用户空间操作我已经注册到Netfilter中的自定义hook函数。有些童鞋可能就纳闷,难道iptables不能操作到么?如果我们需要让iptables操作我们在Netfilter框架中做过的扩展,那么最有效最直接的办法就是开发一个match或者target。但我们现在注册的这个hook很明显和iptables命令行工具没多大关系,你要让iptables来管理这不是强人所难么。当然如果你一要这么干的话,办法肯定是有的,但者不属于本文所要讨论的范畴。


         说到内核空间与用户空间的通信,完全可以作为一个专题来讲,以后有机会把这方面总结一下写出来和大家分享。今天我们要用的方法就是借鉴了iptables和内核的通信方式,即采用getsockopt/setsockopt来实现用户空间和内核空间的交互。


         还是继续上一篇的练习代码,我们在它的基础上继续修改润色。和注册hook函数时的操作非常类似,我们首先要实例化一个struct nf_sockopt_ops{}结构体对象,然后用Linux提供的nf_register_sockopt()函数来将该对象注册到全局双向链表nf_sockopts中去,当我们在用户空间调用g(s)etsockopt时经过层层系统调用,最后就会在nf_sockopts链表中找到我们已经注册的响应函数。关于g(s)etsockopt的执行流程,感兴趣的童鞋可以回头看一下博文十二里的详细讲解。罗嗦的这么多,大家都耐烦了吧。OK,我们赶紧动手。

//添加必要的头文件

#include

#include

#include

 

//增加我们自定义的扩充命令字

#define SOCKET_OPT_BASE         128

#define SOCKET_OPT_SETTARGET    (SOCKET_OPT_BASE)

#define SOCKET_OPT_GETTARGET    (SOCKET_OPT_BASE)

#define SOCKET_OPT_MAX          (SOCKET_OPT_BASE +1)

 

#define    ETH    "eth0"  //interface name

#define    SIP     "192.168.6.130"

//#define    DIP     "118.6.24.132" //把原来这个注释掉,现在看它越看越不顺眼。

#define ADDRLEN 16  //IP地址的长度16字节,格式一般为xxx.xxx.xxx.xxx\0

static char dstIP[ADDRLEN]={0}; //这个就是我们要操作的目的IP

 

//为了醒目,我将两个接口分别在不同的函数中来实现。

static int recv_cmd(struct sock *sk,int cmd, void __user *user,unsigned int len)

{

        int ret = 0;

        if(cmd == SOCKET_OPT_SETTARGET)

        {

                memset(dstIP,0,ADDRLEN);

                if(0!=(ret = copy_from_user(dstIP,user,len))

                {

                      printk("error: can not copy data from userspace\n");

                      return -1;

               }

                printk("The target IP from User: %s \n",dstIP);

        }

        return ret;

}

 

 

static int send_cmd(struct sock *sk,int cmd, void __user *user,int *len)

{

        int ret = 0;

        if(cmd == SOCKET_OPT_GETTARGET)

        {

                if(0!=(ret = copy_to_user(user,dstIP,ADDRLEN)))

                {

                      printk("error: can not copy data to userspace\n");

                      return -1;

                }

                printk("The target IP to User: %s \n",dstIP);

        }

        return ret;

}

 

static struct nf_sockopt_ops my_sockops = {

        .pf = PF_INET,

        .set_optmin = SOCKET_OPT_SETTARGET,

        .set_optmax = SOCKET_OPT_MAX,

        .set = recv_cmd,

        .get_optmin = SOCKET_OPT_GETTARGET,

        .get_optmax = SOCKET_OPT_MAX,

        .get = send_cmd

};

static int index=1;

static unsigned int hook_func(unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *))

{

        const struct iphdr *iph = (*skb)->nh.iph;

        int ret = NF_ACCEPT;

 

        if(iph->protocol == 1){

           atomic_inc(&pktcnt);

           if(pktcnt%5 == 0)

          {

    //简单校验一下IP地址的合法性

    if(strcmp(dstIP,””) !=0 && strcmp(dstIP,”0.0.0.0”)!=0)

    {

                                   printk(KERN_INFO "Sending the %d udp pkt !\n",index++);

                                  ret = build_and_xmit_udp(ETH,SMAC,DMAC,"hello",5,

                                                                           in_aton(SIP),in_aton(dstIP),

                                                                         htons(SPORT),htons(DPORT));

}else{

   printk(“Have %d tims:target IP illegal.Nothing to do!\n”,pktcnt/5);

}

           }

        }

        return ret;

}

static int __init myhook_init(void)

{

    nf_register_sockopt(&my_sockops); //注册我们的sockops对象

    return nf_register_hook(&nfho);

}

 

static void __exit myhook_fini(void)

{

    nf_unregister_hook(&nfho);

    nf_unregister_sockopt(&my_sockops); //注销我们的sockops对象

}

         以上代码的着色部分,需要大家格外留意。因为是示例,所以合法性校验及错误处理的几个地方就是简单象征性地照顾了一下。内核部分的改动就弄完了,接下来我们继续写用户空间的代码usermyhook.c,如下:

#include

#include

#include

#include

#include

#include

 

#define SOCKET_OPS_BASE         128

#define SOCKET_OPT_SETTARGET    (SOCKET_OPS_BASE)

#define SOCKET_OPT_GETTARGET    (SOCKET_OPS_BASE)

#define SOCKET_OPT_MAX          (SOCKET_OPS_BASE +1)

 

int main(int argc,char** argv)

{

    int sockfd,len,ret;

    char targetIP[16]={0};

 

    if(0>(sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_RAW)))

    {

        printf("can not create a socket\n");

        return -1;

    }


    //仅为示范而生。未作输入合法性和参数合法性校验。

    if('s' == *argv[1])

{

    len = strlen(argv[2])+1;

        ret = setsockopt(sockfd,IPPROTO_IP,SOCKET_OPT_SETTARGET,argv[2],len);

        if(0 != ret)

        { 

              printf("setsockopt error: - %d : %s\n",errno,strerror(errno));

             return -1;

        }

        printf("setsockopt: ret=%d, wanted IP=%s\n",ret,argv[2]);

    }else{

        len = sizeof(char)*16;

        ret = getsockopt(sockfd,IPPROTO_IP,SOCKET_OPT_GETTARGET,targetIP,&len);

        if(0 != ret)

        {   

              printf("getsockopt error: - %d : %s\n",errno,strerror(errno));

               return -1;

        }

        printf("getsockopt: ret=%d,gotten IP=%s\n",ret,targetIP);

     }

     close(sockfd);

     return 0;

}

         将该文件编译:gcc -o umhook usermyhook.c

         把重新编译出来的myhook.ko模块加入内核,然后用我们编译出来的umhook工具来动态指定我们要往哪个IP地址发送UDP报文。该工具的用法:

         ./umhook “s” “182.134.150.6” //设置目的IP

  ./umhook “g” //获取目的IP


验证过程和结果如下:

当我们的myhook.ko模块刚加载时内核中的目的IP地址dstIP={0},所以在探测到第五个ICMP报文时并没有发送UDP报文;紧接着,我们用./umhook “s” “123.4.5.6”设置目的IP地址为“123.4.5.6”之后,内核探测到这次改变,打印出“The target IP from User:123.4.5.6”的提示信息;然后,在第10ICMP报文被探测到后发送了第一条UDP到我们所配置的目的地址,抓包工具也有证实;之后,我们又将目的IP改为“123.4.5.7”,内核打印:“The target IP from User:123.4.5.7”在第1520ICMP报文被探测到后又发了两条UDP报文到新IP地址;最后,用./umhook “s” “”命令将目的IP清除掉。整个过程十分流程自然,而我们的心情也无比的愉悦。 


后记:

整个Netfilter系列从清明节开始陆陆续续一直写到端午前夕,也算是对自己有个交代了,另外也总结出来和大家分享一下自己的心得和收获。看到网上经常有人问学习Netfilter有什么好资料或教程,其实个人觉得,Netfilter是无缝嵌入到协议栈里的。如果你想了解它的基本原理那么就需要一点协议栈知识就足够,如果你想为它做开发,那么在掌握了内核编程的基础上,还需要对协议栈的实现有相当深厚的底蕴才可以。由于本人也是刚接触Netfilter不久,学识浅薄,分析难免有所疏漏的地方的还请各位高手和大侠为小弟指正。

完。

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

zsszss00002016-01-26 16:55:23

shaqianqing:不知道为什么我没有权限回复您的短消息,所以写在了这里,DPI是获取消息的七层数据包的特征,是将包抓到应用层来获取的还是之间在钩子函数上做的处理函数,如果是直接在钩子函数做的修改,那不是要动内核的东西(抱歉,我目前对内核还不大理解,我觉得改钩子函数就是动了内核)吗,这只是一个插件,觉得应该是应用层的东西,感觉动内核的话不太合理吧,

其实我有着和你一样的困惑,我研究也不深,也许你直接去问一下原作者效果会更好一些。

回复 | 举报

shaqianqing2015-11-13 17:16:29

shaqianqing:不知道为什么我没有权限回复您的短消息,所以写在了这里,DPI是获取消息的七层数据包的特征,是将包抓到应用层来获取的还是之间在钩子函数上做的处理函数,如果是直接在钩子函数做的修改,那不是要动内核的东西(抱歉,我目前对内核还不大理解,我觉得改钩子函数就是动了内核)吗,这只是一个插件,觉得应该是应用层的东西,感觉动内核的话不太合理吧,

上行数据包:当设备通过DPI插件检测到有上购物网站的数据包之后,将包扔给L2TP去处理(我目前对这个的理解是通过iptables规则将这些包扔给1701端口,这样l2tp在这个端口上抓到数据包进行处理),这里的处理也就是给这些数据包打上二层包头;我想结合着netifilter链来
理解这个东西,上行包首先进入prerouting链,猜想DPI插件是在这里对数据包进行过滤,是否是上购物网站的数据包,之后通过iptables规则将包扔给L2TP去处理,那么问题来了,L2TP给这些数据打上L2TP的包头之后,会做什么,我觉得L2TP是二层的东西,但是这个包本来是一个路由包,应该还是走他本来的路由WAN口出去,那么这里就相当于进入了input链之后通过本地程序L2TP处理之后,要如何发到forward链上转发出去呢,不知道这样处理之后是否会丢失LAN口信息,如果丢失的话,出口查路由表是不是就查不出来了

下行的数据包,进入到prerouting之后,发现是l2tp的包,会被发往本机的L2TP处理,即就是去掉L2TP包头,这之后会如何找到对应的LAN口转发出去;
以上是我对这个问题的想法,是否有做过类似东西的,感兴趣的话可以说说想法,看我理解的是否合理

回复 | 举报

shaqianqing2015-11-13 17:16:19

shaqianqing:不知道为什么我没有权限回复您的短消息,所以写在了这里,DPI是获取消息的七层数据包的特征,是将包抓到应用层来获取的还是之间在钩子函数上做的处理函数,如果是直接在钩子函数做的修改,那不是要动内核的东西(抱歉,我目前对内核还不大理解,我觉得改钩子函数就是动了内核)吗,这只是一个插件,觉得应该是应用层的东西,感觉动内核的话不太合理吧,

我们最近要实现的功能是通过DPI插件实现上购物网站的数据加速,具体是当路由器检测到有上购物网站的数据包之后通过L2TP VPN通道转发出去;
我的想法是,当用户开启上网加速的功能之后,路由器通过L2TP创建VPN通道(在上网的WAN口上创建VPN通道),此时是路由器的L2TP控制报文的交互,创建成功之后;

回复 | 举报

shaqianqing2015-11-13 13:19:55

不知道为什么我没有权限回复您的短消息,所以写在了这里,DPI是获取消息的七层数据包的特征,是将包抓到应用层来获取的还是之间在钩子函数上做的处理函数,如果是直接在钩子函数做的修改,那不是要动内核的东西(抱歉,我目前对内核还不大理解,我觉得改钩子函数就是动了内核)吗,这只是一个插件,觉得应该是应用层的东西,感觉动内核的话不太合理吧,