日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux 内核抓包功能实现基础(二) netfilter处理

發布時間:2025/4/5 linux 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux 内核抓包功能实现基础(二) netfilter处理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

上篇博客主要介紹了內核抓包設計思路與效果,并沒有給出詳細的設計實現,看起來就像一個花架子,華而不實。本篇博客就結合具體的代碼介紹一下抓包的實現過程。先大致概括一下代碼的思路,抓包模塊啟動后,就去netfilter上面注冊兩個鉤子函數,分別放在PRE_ROUTING鏈和POSTROUTING鏈上,抓取進來的報文和出去的報文。當報文進入到netfilter層面處理時,鉤子函數對報文做一下簡單的匹配判斷,符合條件就復制一份發送出來。先看代碼:

/** Description : 內核抓包模塊demo,內核版本3.4.39* Date : 20180701* Author : fuyuande*/#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/socket.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> #include <linux/netfilter_arp.h> #include <linux/in.h> #include <linux/if_ether.h> #include <linux/if_arp.h> #include <linux/ip.h> #include <net/ip.h> #include <linux/skbuff.h> #include <linux/inet.h>#include "capture_demo.h"struct dst_entry *output_dst = NULL; //出口設備指針//查詢報文源端口或者目的端口 unsigned short capture_get_port(const struct sk_buff *skb,int dir) {struct iphdr *iph = NULL;struct tcphdr *tcph = NULL;struct udphdr *udph = NULL;unsigned short port = 0;iph = ip_hdr(skb);if(!iph){log_warn("ip header null \r\n");return 0;}if(iph->protocol == IPPROTO_TCP){tcph = tcp_hdr(skb);if(!tcph){log_warn("tcp header null \r\n");return 0;}if(dir == 0){port = ntohs(tcph->dest); tcph = NULL;return port;}else{ port = ntohs(tcph->source);tcph = NULL;return port;}}else if(iph->protocol == IPPROTO_UDP){udph = udp_hdr(skb);if(!udph){log_warn("udp header null \r\n");return 0;}if(dir == 0){port = ntohs(udph->dest);udph = NULL;return port;}else{port = ntohs(udph->source);udph = NULL;return port;}}elsereturn 0; }//查詢傳輸層協議 TCP/UDP/ICMP unsigned int capture_get_transport_protocol(const struct sk_buff *skb){struct iphdr *iph = NULL;iph = ip_hdr(skb);if(!iph)return 0;if(iph->protocol == IPPROTO_TCP)return (CAPTURE_TCP);if(iph->protocol == IPPROTO_UDP)return (CAPTURE_UDP);return 0; }//復制報文并添加新的頭域發送到指定的接收地址 int capture_send(const struct sk_buff *skb, int output) {struct ethhdr *oldethh = NULL;struct iphdr *oldiph = NULL;struct iphdr *newiph = NULL;struct udphdr *newudph = NULL; struct sk_buff *skb_cp = NULL;struct net *net = NULL;unsigned int headlen = 0;headlen = 60; // mac + ip + udp = 14 + 20 + 8 = 42, 這里分配大一點//如果報文頭部不夠大,在復制的時候順便擴展一下頭部空間,夠大的話直接復制if(skb_headroom(skb) < headlen){skb_cp = skb_copy_expand(skb,headlen,0,GFP_ATOMIC); if(!skb_cp){log_warn(" realloc skb fail \r\n");return -1;}}else{skb_cp = skb_copy(skb, GFP_ATOMIC);if(!skb_cp){log_warn(" copy skb fail \r\n");return -1;}}oldiph = ip_hdr(skb);if(!oldiph){log_warn("ip header null \r\n");kfree_skb(skb_cp);return -1;}/** 抓包報文格式---------------------------------------------------------------------| new mac | new ip | new udp | old mac | old ip| old tcp/udp | data |---------------------------------------------------------------------| new header | new data | --------------------------------------------------------------------- *///如果是出去的報文,因為是在IP層捕獲,MAC層尚未填充,這里將MAC端置零,并填寫協議字段if(output){skb_push(skb_cp,sizeof(struct ethhdr));skb_reset_mac_header(skb_cp);oldethh = eth_hdr(skb_cp);oldethh->h_proto = htons(ETH_P_IP); memset(oldethh->h_source,0,ETH_ALEN); memset(oldethh->h_dest,0,ETH_ALEN);if(skb_cp->dev != NULL)memcpy(oldethh->h_source,skb_cp->dev->dev_addr,ETH_ALEN); }else{//如果是進來的報文,MAC層已經存在,不做任何處理,直接封裝skb_push(skb_cp,sizeof(struct ethhdr));skb_reset_mac_header(skb_cp);oldethh = eth_hdr(skb_cp);oldethh->h_proto = htons(ETH_P_IP); }//添加IP, UDP頭部skb_push(skb_cp, sizeof(struct iphdr) + sizeof(struct udphdr)); skb_reset_network_header(skb_cp);skb_set_transport_header(skb_cp,sizeof(struct iphdr));newiph = ip_hdr(skb_cp);newudph = udp_hdr(skb_cp);if((newiph == NULL) || (newudph == NULL)){log_warn("new ip udp header null \r\n");kfree_skb(skb_cp);return -1;}/* 抓包的報文發送的時候是調用協議棧函數發送的,所以output鉤子函數會捕獲到抓包報文,* 這里我們要把抓包報文和正常報文區分開,區分方式就是判斷源端口,我們抓到的報文* 在送出去的時候填寫的是保留端口0,如果鉤子函數遇到這樣的報文就會直接let go* 防止重復抓包,這一點在測試的時候很重要,一旦重復抓包,系統就直接掛了...*/memcpy((unsigned char*)newiph,(unsigned char*)oldiph,sizeof(struct iphdr));newudph->source = htons(0);newiph->saddr = in_aton("1.1.1.1");newudph->dest = htons(8080); //抓包服務器端口newiph->daddr = in_aton("192.168.199.123"); //抓包服務器地址newiph->ihl = 5;newiph->protocol = IPPROTO_UDP; newudph->len = htons(ntohs(oldiph->tot_len) + sizeof(struct udphdr) + sizeof(struct ethhdr));newiph->tot_len = htons(ntohs(newudph->len) + sizeof(struct iphdr));/* disable gso_segment */ skb_shinfo(skb_cp)->gso_size = htons(0);//計算校驗和newudph->check = 0;newiph->check = 0;skb_cp->csum = 0;skb_cp->csum = csum_partial(skb_transport_header(skb_cp), htons(newudph->len), 0); newudph->check = csum_tcpudp_magic(newiph->saddr, newiph->daddr, htons(newudph->len), IPPROTO_UDP, skb_cp->csum); skb_cp->ip_summed = CHECKSUM_NONE;if (0 == newudph->check){newudph->check = CSUM_MANGLED_0;}newiph->check = ip_fast_csum((unsigned char*)newiph, newiph->ihl);//設置出口設備if(skb_dst(skb_cp) == NULL){if(output_dst == NULL){kfree_skb(skb_cp);return -1;}else{dst_hold(output_dst); skb_dst_set(skb_cp, output_dst);}}//路由查找if(ip_route_me_harder(skb_cp, RTN_UNSPEC)){kfree_skb(skb_cp);log_info("ip route failed \r\n");return -1;}//發送ip_local_out(skb_cp);return 0; }//輸入鉤子函數 static unsigned int capture_input_hook(unsigned int hooknum,struct sk_buff *skb,const struct net_device *in,const struct net_device *out,int (*okfn)(struct sk_buff *)) {struct iphdr *iph = NULL;unsigned short sport = 0;iph = ip_hdr(skb);if(unlikely(!iph))return NF_ACCEPT;//只處理TCP和UDPif(iph->protocol != IPPROTO_TCP && iph->protocol != IPPROTO_UDP)return NF_ACCEPT; //源地址和目的地址相同,只抓一次,在output鉤子上處理一遍就夠了if(iph->saddr == iph->daddr)return NF_ACCEPT;//設置傳輸層首部指針 skb_set_transport_header(skb, (iph->ihl*4)); //檢查端口,端口為0的let gosport = capture_get_port(skb,1);if(sport == 0)return NF_ACCEPT;//復制一份報文并發送出去 capture_send(skb, 0);//返回accept,讓系統正常處理return NF_ACCEPT; }//輸出鉤子函數 static unsigned int capture_output_hook(unsigned int hooknum,struct sk_buff *skb,const struct net_device *in,const struct net_device *out,int (*okfn)(struct sk_buff *)) {struct iphdr *iph;unsigned short sport = 0; iph = ip_hdr(skb);if(unlikely(!iph))return NF_ACCEPT;//只處理TCP或UDP if(iph->protocol != IPPROTO_TCP && iph->protocol != IPPROTO_UDP)return NF_ACCEPT;//如果源端口為0,是抓包報文,直接let it go, 否則進行抓包sport = capture_get_port(skb,1); if(output_dst == NULL){if(skb_dst(skb) != NULL){output_dst = skb_dst(skb);dst_hold(output_dst); log_info("dst get success \r\n"); }}if(sport != 0)capture_send(skb, 1);return NF_ACCEPT; }struct nf_hook_ops capture_hook_ops[] = {{.hook=capture_input_hook, //輸入鉤子處理函數.pf=NFPROTO_IPV4,.hooknum=NF_INET_PRE_ROUTING, //hook點.priority=NF_IP_PRI_FIRST + 10, //優先級},{.hook=capture_output_hook, //輸出鉤子處理函數.pf=NFPROTO_IPV4,.hooknum=NF_INET_POST_ROUTING, //hook點.priority=0, //優先級}, {} };static int __init capture_init(void) { //注冊鉤子函數if(nf_register_hooks(capture_hook_ops,ARRAY_SIZE(capture_hook_ops))!=0){log_warn("netfilter register fail");return -1;}log_info("capture module init \r\n");return 0; }static void __exit capture_exit(void) { //注銷鉤子函數nf_unregister_hooks(capture_hook_ops,ARRAY_SIZE(capture_hook_ops)); if(output_dst != NULL){dst_release(output_dst);log_info("dst release success \r\n");} log_info("capture module exit \r\n");return ; }module_init(capture_init) module_exit(capture_exit)MODULE_ALIAS("capture"); MODULE_AUTHOR("fuyuande"); MODULE_DESCRIPTION("capture module"); MODULE_LICENSE("GPL");

代碼里過濾條件很簡單,就是TCP/UDP報文,當收到這樣的報文就復制一份,添加新的頭域發送到遠端服務器上,這里需要指定遠端服務器的端口和IP地址,至于源端口和目的端口,是可以任意填寫的。我們調用的發送函數是ip_local_out(),這意味著抓包報文還會經過POST_ROUTING鏈,為了防止重復抓包,需要區分正常的報文和抓包報文,這里區分條件就是端口,抓包報文的源端口使用的是0,這樣當收到這樣的報文時候就直接accept處理。將報文發送出去的方式還有其它,例如dev_queue_xmit()接口,但是調用這個接口的前提是你已經知道了到遠端服務器的出口設備dev以及下一條的mac地址,這樣處理可能更快一點,不必再有協議棧處理一遍,不過呢,使用ip_local_out的一個優點就是協議棧會幫我們處理分片報文,如果抓到的報文過大的話,直接調用dev_queue_xmit有可能在發送途中被丟棄,而使用ip_local_out則IP協議棧會幫我們處理分片的事情,我們只需要調用這個接口就可以了。

看一下運行的實際效果圖:

wireshark可以看到抓包模塊送過來的報文,但是這樣的報文并不能直接拿來分析,因為它外面還封裝了mac, ip, udp頭,需要去掉這些頭部才能看到原始的報文,這就是抓包服務器的功能。今天先介紹到這,下一篇將抓包服務器的實現。

對了,代碼我放到github上:

https://github.com/FuYuanDe/capture_demo.git

git clone下來直接make && insmod 就可以運行了。

またね!

總結

以上是生活随笔為你收集整理的linux 内核抓包功能实现基础(二) netfilter处理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。