使用 netfilter 處理IPv6報文

        netfilter對IPv6的處理和IPv4流程類似,只有鉤子函數註冊協議不同,優先級和註冊的鏈都是一樣的。

    {
	.hook=nf_input_hook_v4,
	.pf=NFPROTO_IPV4,               //IPv4協議
	.hooknum=NF_INET_POST_ROUTING,
	.priority=0,
    },	
    {
        .hook=nf_input_hook_v6,
        .pf=NFPROTO_IPV6,               //IPv6協議
        .hooknum=NF_INET_PRE_ROUTING,
        .priority=NF_IP_PRI_FIRST,
    },

下面是一個netfilter ipv6小栗子,對本機出去的IPv6 UDP報文做了端口變換處理,介紹一下IPv6地址操作、IPv6報文udp校驗值計算等等。

內核版本: 3.4.39

/*
 *  Netfilter IPv6 Demo, NAT
 *  Autor : Mason
 *  Date  : 20180731
 */

#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/in.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <net/ip.h>
#include <net/dsfield.h>
#include <linux/skbuff.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <net/route.h>
#include "nfdemov6.h"

/* IPv6輸入報文處理函數 */
static unsigned int nfdemo_input_hook_v6(unsigned int hooknum,
			       struct sk_buff *skb,
			       const struct net_device *in,
			       const struct net_device *out,
			       int (*okfn)(struct sk_buff *))
{   
    /* do something */
    
    return NF_ACCEPT;                          
}

/* IPv6輸出報文處理函數,這裏對IPv6 UDP報文做了源端口變換 */
static unsigned int nfdemo_output_hook_v6(unsigned int hooknum,
			       struct sk_buff *skb,
			       const struct net_device *in,
			       const struct net_device *out,
			       int (*okfn)(struct sk_buff *))
{          
    struct ipv6hdr *iph6;
    struct udphdr *udph;    
    unsigned int udp_len;
    
    /* 獲取IPv6首部指針 */
    iph6 = ipv6_hdr(skb);
    if (!iph6)
        return NF_ACCEPT;

    /* 過濾 ::/128 空類型地址 */
    if (ipv6_addr_any(&iph6->saddr) || ipv6_addr_any(&iph6->daddr))
        return NF_ACCEPT;

    /* 過濾源地址和目的地址相等的報文 */
    if (ipv6_addr_equal(&iph6->saddr, &iph6->daddr))
        return NF_ACCEPT;

    /* 過濾環回地址報文 ::1/128 */
    if (ipv6_addr_loopback(&iph6->saddr) || ipv6_addr_loopback(&iph6->daddr))        
        return NF_ACCEPT;

    /* 只處理UDP報文 */    
    if (iph6->nexthdr != NEXTHDR_UDP)
        return NF_ACCEPT;

    /* 設置傳輸層首部 */
    skb_set_transport_header(skb, sizeof(struct ipv6hdr));

    /* 獲取UDP首部 */
    udph = udp_hdr(skb);
    if (!udph )
        return NF_ACCEPT;

    /* 更改端口 */        
    udph->source = htons(6666);    

    /* 重新計算校驗和    
     * IPv6校驗和計算使用 csum_ipv6_magic() 接口
     * IPv4校驗和計算使用 csum_tcpudp_magic() 接口
     */       
    udph->check = 0; 
    udp_len = ntohs(udph->len);                               
    skb->csum = csum_partial(skb_transport_header(skb), udp_len, 0);

    udph->check = csum_ipv6_magic(&iph6->saddr, &iph6->daddr, udp_len, IPPROTO_UDP, skb->csum);        
    skb->ip_summed = CHECKSUM_NONE;

    /* 如果udp首部校驗和爲0,替換成CSUM_MANGLED_0 */
    if (0 == udph->check)
        udph->check = CSUM_MANGLED_0;

    /* 替換完成,把報文還給系統協議棧繼續處理 */
    return NF_ACCEPT;               
}


struct nf_hook_ops nfdemo_hook_ops[] ={
    {
        .hook = nfdemo_input_hook_v6,       /* 鉤子處理函數 */
        .pf = NFPROTO_IPV6,                 /* 協議類型IPv6 */
        .hooknum = NF_INET_PRE_ROUTING,     /* Pre_Routing鏈 */
        .priority = NF_IP_PRI_FIRST + 20,   /* 優先級 */
    },
    {
        .hook = nfdemo_output_hook_v6,        /* 鉤子處理函數 */
        .pf = NFPROTO_IPV6,                   /* 協議類型IPv6 */
        .hooknum = NF_INET_PRE_ROUTING,       /* Post_Routing鏈 */
        .priority = NF_IP_PRI_FIRST + 20,     /* 優先級 */
    },
    {}
};

/* 模塊入口 */
static int __init nfdemov6_init(void)
{
    printk("nfv6demo init \r\n");

    /* 註冊 Netfilter 鉤子函數 */
    nf_register_hooks(nfdemo_hook_ops,ARRAY_SIZE(nfdemo_hook_ops));    
    return 0;
}

/* 模塊出口 */
static void __exit nfdemov6_exit(void)
{
    printk("nfv6demo exit \r\n");

    /* 註銷 Netfilter 鉤子函數 */
    nf_unregister_hooks(nfdemo_hook_ops,ARRAY_SIZE(nfdemo_hook_ops));    
    return ;
}


module_init(nfdemov6_init)
module_exit(nfdemov6_exit)
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mason");


代碼在本機編譯、運行通過。

調試遇到一個bug,地址替換直接使用128,IPv6地址 長度爲40字節128位,導致直接覆蓋後面內容,悲催

當然啦,聰明的你應該不會出現這樣的錯誤

//正確的做法
memcpy(remote_ip.in6, &iph6->saddr, sizeof(struct in6_addr));

//錯誤的做法
memcpy(remote_ip.in6, &iph6->saddr, 128);

有問題歡迎提出來

またね

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章