纸上得来终觉浅,绝知此事要躬行。 生命不息,奋斗不止。
分类: LINUX
2020-04-06 15:38:22
从用户空间来操作内核中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”的提示信息;然后,在第10个ICMP报文被探测到后发送了第一条UDP到我们所配置的目的地址,抓包工具也有证实;之后,我们又将目的IP改为“123.4.5.7”,内核打印:“The target IP from User:123.4.5.7”在第15,20个ICMP报文被探测到后又发了两条UDP报文到新IP地址;最后,用./umhook “s” “”命令将目的IP清除掉。整个过程十分流程自然,而我们的心情也无比的愉悦。
后记:
整个Netfilter系列从清明节开始陆陆续续一直写到端午前夕,也算是对自己有个交代了,另外也总结出来和大家分享一下自己的心得和收获。看到网上经常有人问学习Netfilter有什么好资料或教程,其实个人觉得,Netfilter是无缝嵌入到协议栈里的。如果你想了解它的基本原理那么就需要一点协议栈知识就足够,如果你想为它做开发,那么在掌握了内核编程的基础上,还需要对协议栈的实现有相当深厚的底蕴才可以。由于本人也是刚接触Netfilter不久,学识浅薄,分析难免有所疏漏的地方的还请各位高手和大侠为小弟指正。
完。