從用戶空間來操作內核中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不久,學識淺薄,分析難免有所疏漏的地方的還請各位高手和大俠爲小弟指正。
完。