【Linux 驅動】Netfilter/iptables (六) 核心協議棧編程(發送skb)

來源:互聯網
上載者:User

核心態基於 Netfilter 構造skb資料包
前面第四篇介紹了 Netfilter Hook 機制,我們知道了資料包在協議棧中傳遞時會經過不同的HOOK點,而每個HOOK點上又被Netfilter預先註冊了一系列hook回呼函數,每個資料包經過時都會經過這些hook函數的處理。

在hook回呼函數中,你可以進行任何操作,這些操作你都可以自訂,然後以模組的形式載入進去。

這裡我們給出一個執行個體:每收到5個TCP報文就向指定的IP地址發送一個UDP報文,這個功能的開發涉及到核心協議棧編程,而不是我們之前學的使用者空間的 Socket 網路編程。

看代碼實現:

//linux kernel 3.13.0-43#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/netdevice.h>#include <linux/netfilter.h>#include <linux/netfilter_ipv4.h>#include <linux/ip.h>//for ip header#include <linux/types.h>#include <linux/skbuff.h>#include <linux/if_ether.h>#include <linux/if_packet.h>#include <net/tcp.h>#include <net/udp.h>#include <net/icmp.h>#include <linux/inet.h>MODULE_LICENSE("Dual BSD/GPL");#define ETH "wlan0"//用的本機無線網卡裝置#define SIP "192.168.1.27"//隨意#define DIP "118.6.24.132"//#define SPORT 39804#define DPORT 6980#define NIPQUAD(addr)\    ((unsigned char *)&addr)[0],\    ((unsigned char *)&addr)[1],\    ((unsigned char *)&addr)[2],\    ((unsigned char *)&addr)[3]unsigned char SMAC[ETH_ALEN] ={0x1c, 0x4b, 0xd6, 0x7a, 0x55, 0x96};//本機無線網卡硬體地址unsigned char DMAC[ETH_ALEN] = {0xe0, 0xcb, 0x4e, 0xb0, 0xed, 0xd8};//本機網卡硬體地址static struct nf_hook_ops nfho;//構建一個udp報文並發送static int build_and_xmit_udp(char *eth, u_char *smac, u_char *dmac,        u_char *pkt, int pkt_len, u_long sip, u_long dip,        u_short sport, u_short dport){    struct sk_buff *skb = NULL;    struct net_device *dev = NULL;    struct udphdr *udph = NULL;    struct iphdr *iph = NULL;    struct ethhdr *ethdr = NULL;    u_char *pdata = NULL;    int nret = 1;    if(NULL == smac || NULL == dmac)        goto out;    //根據裝置名稱獲得裝置指標    //這裡調用的函數高版本做了修改,多了個參數struct net*    if(NULL == (dev = dev_get_by_name(&init_net, eth)))        goto out;    //建立一個skb    skb = alloc_skb(pkt_len + sizeof(struct udphdr) + sizeof(struct iphdr) + sizeof(struct ethhdr), GFP_ATOMIC);    if(NULL == skb)        goto out;    //為skb預留空間,方便後面skb_buff協議封裝    skb_reserve(skb, pkt_len + sizeof(struct udphdr) + sizeof(struct iphdr) + sizeof(struct ethhdr));    //skb位元組填充    skb->dev = dev;    skb->pkt_type = PACKET_OTHERHOST;    skb->protocol = __constant_htons(ETH_P_IP);    skb->ip_summed = CHECKSUM_NONE;    skb->priority = 0;    //資料包封裝    //分別壓入應用程式層,傳輸層,網路層,鏈路層棧幀    //skb_push由後面往前面,與skb_put不同    pdata = skb_push(skb, pkt_len);    udph = (struct udphdr*)skb_push(skb, sizeof(struct udphdr));    iph = (struct iphdr*)skb_push(skb, sizeof(struct iphdr));    ethdr = (struct ethhdr*)skb_push(skb, sizeof(struct ethhdr));    //應用程式層資料填充    memcpy(pdata, pkt, pkt_len);    //傳輸層udp資料填充    memset(udph, 0, sizeof(struct udphdr));    udph->source = sport;    udph->dest = dport;    udph->len = htons(sizeof(struct udphdr) + pkt_len);//主機位元組序轉網路位元組序    udph->check = 0;//skb_checksum之前必須置0.協議規定    //網路層資料填充    iph->version = 4;    iph->ihl = sizeof(struct iphdr) >> 2;    iph->frag_off = 0;    iph->protocol = IPPROTO_UDP;    iph->tos = 0;    iph->daddr = dip;    iph->saddr = sip;    iph->ttl = 0x40;    iph->tot_len = __constant_htons(skb->len);    iph->check = 0;    iph->check = ip_fast_csum((unsigned char*)iph, iph->ihl);//計算校正和    skb->csum = skb_checksum(skb, iph->ihl*4, skb->len - iph->ihl*4, 0);//skb校正和計算    udph->check = csum_tcpudp_magic(sip, dip, skb->len - iph->ihl*4, IPPROTO_UDP, skb->csum);//dup和tcp偽首部校正和    //鏈路層資料填充    memcpy(ethdr->h_dest, dmac, ETH_ALEN);    memcpy(ethdr->h_source, smac, ETH_ALEN);    ethdr->h_proto = __constant_htons(ETH_P_IP);    //調用核心協議棧函數,發送資料包    if(dev_queue_xmit(skb) < 0)    {        printk("dev_queue_xmit error\n");        goto out;    }    nret = 0;//這裡是必須的    printk("dev_queue_xmit correct\n");    //出錯處理out:/*下面的0!=nret是必須的,前面即使不執行goto out,下面的語句程式也會執行,如果不加0!=nret語句,那麼前面dev_queue_xmit返回之後(已經kfree_skb一次了),再進入下面的語句第二次執行kfree_skb,就會導致系統死機*///關鍵在於知道dev_queue_xmit內部調用成功後,會kfree_skb,以及goto語句的作用    if(0 != nret && NULL != skb)//這裡前面的nret判斷是必須的,不然必定死機    {        dev_put(dev);//減少裝置的引用計數        kfree_skb(skb);//銷毀資料包    }    return nret;//F_ACCEPT;}atomic_t pktcnt = ATOMIC_INIT(0);//定義並初始化//鉤子函數,注意參數格式與開發環境源碼樹保持一致static unsigned int hook_func(const struct nf_hook_ops *ops,         struct sk_buff *skb,        const struct net_device *in,        const struct net_device *out,        int (*okfn)(struct sk_buff *)){    struct iphdr *iph = ip_hdr(skb);    int ret = NF_ACCEPT;     unsigned char *pdata = "hello kernel";    printk("hook function processing\n");    if(iph->protocol == IPPROTO_TCP)    {        atomic_inc(&pktcnt);        if(atomic_read(&pktcnt) % 5 == 0)        {            printk(KERN_INFO "Sending the %d udp packet\n", atomic_read(&pktcnt)/5);            ret = build_and_xmit_udp(ETH, SMAC, DMAC, pdata, strlen(pdata), in_aton(SIP), in_aton(DIP), htons(SPORT), htons(DPORT));        }    }    return ret;}static int __init hook_init(void){    nfho.hook = hook_func;//關聯對應處理函數    nfho.hooknum = NF_INET_LOCAL_OUT;//ipv4的本地出口處    nfho.pf = PF_INET;//ipv4,所以用這個    nfho.priority = NF_IP_PRI_FIRST;//優先順序,第一順位    nf_register_hook(&nfho);//註冊    return 0;}static void __exit hook_exit(void){    nf_unregister_hook(&nfho);//登出}module_init(hook_init);module_exit(hook_exit);

注釋已經大致說明了。需要注意的是你所寫的核心程式介面以及資料結構等等必須和你的開發源碼樹保持一直,3.13版本對比2.6版本有較大改動,這裡主要體現在sk_buff上。

這裡是在hook函數中建立一個skb,然後手動填充各個協議棧的資料,最後調用核心協議棧函數dev_queue_xmit函數發送,這個函數是物理層的,一個驅動函數,所以我們構造的skb資料包需要填充應用程式層,傳輸層,網路層,以及鏈路層的資料。

這段程式一開始時,只要運行(載入模組),就會立馬死機,為此不知道強制關機重啟了多少遍,最後經調試以及查閱資料,發現是kfree_skb這個環節出錯,後來注釋掉這行代碼,程式運行無誤,測試也沒問題,但是總有bug,就是建立的資料包最終沒有銷毀。

後來查閱得知,因為存在兩次kfree_skb,在dev_queue_xmit調用成功後,已經kfree_skb一次了,返回後,後面的out:語句還會繼續執行,不加 0!=nret 判斷語句的話,還會再次調用 kree_skb ,導致死機。
這跟goto語句用的少(幾乎不用),不清楚goto語句的作用,現在應該這麼理解 goto out; 是該語句到 out: 之間的語句跳過,其餘依舊怎樣怎樣…

測試:make(本系列代碼的make檔案是一樣的),insmod wqlkp.ko
這裡我們用 wireshark抓包(root許可權)
1. 開啟一個視頻網站,然後dmesg

[ 3523.489826] Sending the 237 udp packet[ 3523.489850] dev_queue_xmit correct[ 3523.489922] hook function processing[ 3523.602361] hook function processing[ 3523.602532] hook function processing[ 3523.606425] hook function processing[ 3523.688857] hook function processing[ 3523.688864] Sending the 238 udp packet[ 3523.688899] dev_queue_xmit correct[ 3523.691320] hook function processing
利用wireshark抓包

可以看到,我們發送了一個資料內容為“hello kernel”的UDP報文。

關於協議棧編程以及上面的skb封裝,我們後面有時間再跟上。
本門還算一枚菜鳥,紕漏和不對之處還望指正。

參考資料:
sk_buff封裝和解鎖裝網路資料包的過程詳解

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.