• Netfilter的使用和实现


    本文主要内容:Netfilter的原理和实现浅析,以及示例模块。

    内核版本:2.6.37

    Author:zhangskd @ csdn blog

    概述

    Netfilter为多种网络协议(IPv4、IPv6、ARP等)各提供了一套钩子函数。

    在IPv4中定义了5个钩子函数,这些钩子函数在数据包流经协议栈的5个关键点被调用。

    这就像有5个钓鱼台,在每个钓鱼台放了一个鱼钩(钩子函数),把经过的数据包钓上来,

    然后根据自定义的规则,来决定数据包的命运:

    可以原封不动的放回IPv4协议,继续向上层递交;可以进行修改,再放回IPv4协议;也可以直接丢弃。

    Netfilter主要采用连接跟踪(Connection Tracking)、包过滤(Packet Filtering)、地址转换(NAT)、包处理

    (Packet Mangling)四种技术。

    (1) IP层的5个钓鱼台

    enum nf_inet_hooks {
        NF_INET_PRE_ROUTING,
        NF_INET_LOCAL_IN,
        NF_INET_FORWARD,
        NF_INET_LOCAL_OUT,
        NF_INET_POST_ROUTING,
        NF_INET_NUMHOOKS
    };
    

    支持的协议类型:

    enum {
        NFPROTO_UNSPEC = 0,
        NFPROTO_IPV4 = 2,
        NFPROTO_ARP = 3,
        NFPROTO_BRIDGE = 7,
        NFPROTO_IPV6 = 10,
        NFPROTO_DECNET = 12,
        NFPROTO_NUMPROTO,
    };
    

     

    (2) 钩子函数

    typedef unsigned int nf_hookfn(unsigned int hooknum,
                                   struct sk_buff *skb,
                                   const struct net_device *in,
                                   const struct net_device *out,
                                   int (*okfn) (struct sk_buff *));
    
    /* 处理函数返回值 */
    #define NF_DROP 0 /* drop the packet, don't continue traversal */
    #define NF_ACCEPT 1 /* continue traversal as normal */
    #define NF_STOLEN 2 /* I've taken over the packet, don't continue traversal */
    #define NF_QUEUE 3 /* queue the packet (usually for userspace handling) */
    #define NF_REPEAT 4 /* call this hook again */
    #define NF_STOP 5
    #define NF_MAX_VERDICT NF_STOP

    (3) Netfilter实体

    在使用Netfilter时,需要定义一个nf_hook_ops实例。

    struct nf_hook_ops {
        struct list_head list;
        /* User fills in from here down. */
        nf_hookfn *hook; /* 要注册的钩子函数 */
        struct module *owner;
        u_int8_t pf; /* 协议类型 */
        unsigned int hooknum; /* 哪个钓鱼台 */
        /* Hooks are ordered in asending priority. */
        int priority; /* 数值越小,优先级越高 */
    };
    typedef __u8 u_int8_t;
    

     

    (4) 注册与注销

    /* Functions to register/unregister hook points. */
    int nf_register_hook(struct nf_hook_ops *reg);
    void nf_unregister_hook(struct nf_hook_ops *reg);

    实现

    Netfilter定义了一个全局链表:

    struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];
    EXPORT_SYMBOL(nf_hooks);
    static DEFINE_MUTEX(nf_hook_mutex);
    

    (1) 注册函数

    注册函数会把nf_hook_ops放入nf_hooks相应的位置中。

    int nf_register_hook(struct nf_hook_ops *reg)
    {
        struct nf_hook_ops *elem;
        int err;
    
        err = mutex_lock_interruptible(&nf_hook_mutex);
        if (err < 0)
            return err;
    
        list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) {
            if (reg->priority < elem->priority)
                break;
        }
    
        list_add_rcu(&reg->list, elem->list.prev); /* 把netfilter实例添加到队列中 */
        mutex_unlock(&nf_hook_mutex);
        return 0;
    }
    

    (2) 注销函数

    void nf_unregister_hook(struct nf_hook_ops *reg)
    {
        mutex_lock(&nf_hook_mutex);
        list_del_rcu(&reg->list); /* 把netfilter实例从队列中删除 */
        mutex_unlock(&nf_hook_mutex);
        synchronize_net();
    }
    

    (3) 内核接口

    内核的Netfilter钩子函数调用:

    NF_HOOK

        |--> NF_HOOK_THRESH

                   |--> nf_hook_thresh

                             |--> nf_hook_slow

                                       |--> nf_iterate

    static inline int NF_HOOK(uint8_t pf, unsigned int hook, struct sk_buff *skb,
        struct net_device *in, struct net_device *out, int (*okfn)(struct sk_buff *))
    {
        /* INT_MIN表示要调用钓鱼台的所有钩子函数 */
        return NF_HOOK_THRESH(pf, hook, skb, in, out, okfn, INT_MIN); 
    }
    
    static inline int NF_HOOK_THRESH(uint8_t pf, unsigned int hook, struct sk_buff *skb,
        struct net_device *in, struct net_device *out, int (*okfn)(struct sk_buff *), int thresh)
    {
        int ret = nf_hook_thresh(pf, hook, skb, in, out, okfn, thresh);
        if (ret == 1)
            ret = okfn(skb); /* 如果skb没被处理掉,调用此函数 */
    
        return ret;
    }
    
    /**
     * nf_hook_thresh - call a netfilter hook
     * Returns 1 if the hook has allowed the packet to pass.
     * The function okfn must be invoked by the caller in this case.
     * Any other return value indicates the packet has been consumed by the hook.
     */
    static inline int nf_hook_thresh(u_int8_t pf, unsigned int hook, struct sk_buff *skb,
        struct net_device *indev, struct net_device *outdev, int (*okfn)(struct sk_buff *), int thresh)
    {
    #ifndef CONFIG_NETFILTER_DEBUG
        /* 如果协议pf的hook点上没有已注册的nf_hook_ops实例,直接返回1 */
        if (list_empty(&nf_hooks[pf][hook]))
            return 1;
    #endif
    
        return nf_hook_slow(pf, hook, skb, indev, outdev, okfn, thresh);
    }
     
    /* Returns 1 if okfn() needs to be executed by the caller, -EPERM for NF_DROP, 0 otherwise. */
    int nf_hook_slow(u_int8_t pf, unsigned int hook, struct sk_buff *skb, struct net_device *indev,
        struct net_device *outdev, int (*okfn)(struct sk_buff *), int hook_thresh)
    {
        struct list_head *elem;
        unsigned int verdict;
        int ret = 0;
    
        /* We may already have this, but read-locks nest anyway */
        rcu_read_lock();
    
        elem = &nf_hooks[pf][hook];
    next_hook:
        verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev, outdev, &elem, okfn, hook_thresh);
    
        if (verdict == NF_ACCEPT || verdict == NF_STOP) {
            ret = 1;
        } else if (verdict == NF_DROP) {
            kfree_skb(skb);
            ret = -EPERM;
        } else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
            if (! nf_queue(skb, elem, ph, hook, indev, outdev, okfn, verdict >> NF_VERDICT_BITS))
                goto next_hook;
        }
    
        rcu_read_unlock();
    
        return ret;
    }
    
    unsigned int nf_iterate(struct list_head *head, struct sk_buff *skb, unsigned int hook,
        const struct net_device *indev, const struct net_device *outdev, struct list_head **i,
        int (*okfn)(struct sk_buff *), int hook_thresh)
    {
        unsigned int verdict;
    
        /* 
         * The caller must not block between calls to this function because of risk of
         * continuing from deleted element.
         */
        list_for_each_continue_rcu(*i, head) {
            struct nf_hook_ops *elem = (struct nf_hook_ops *) *i;
    
            /* 优先级>=hook_thresh的都会被执行 */
            if (hook_thresh > elem_priority)
                continue;
    
            verdict = elem->hook(hook, skb, indev, outdev, okfn); /* 已注册的执行函数 */
    
            if (verdict != NF_ACCEPT) {
    #ifdef CONFIG_NETFILTER_DEBUG
                if (unlikely((verdict & NF_VERDICT_MASK) > NF_MAX_VERDICT)) {
                    NFDEBUG("Evil return from %p(%u).
    ", elem->hook, hook);
                    continue;
                }
    #endif
    
                if (verdict != NF_REPEAT)
                    return verdict;
                *i = (*i)->prev;
            }
        }
    
        return NF_ACCEPT;
    }
    

    使用

    以下是一个简单的模块,加载到一个HTTP服务器上。

    通过在PRE_ROUTING处注册my_hookfn,改变接收数据包的源IP为8.8.8.8(Google DNS server)。

    当客户端向服务器发送一个请求时,肯定收不到服务器的响应:)

    #include <linux/netfilter.h>
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/netfilter_ipv4.h>
    #include <linux/ip.h>
    #include <linux/inet.h>
    
    /**
     * Hook function to be called.
     * We modify the packet's src IP.
     */
    unsigned int my_hookfn(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;
        iph = ip_hdr(skb);
    
        /* log the original src IP */
        printk(KERN_INFO"src IP %pI4
    ", &iph->saddr);
    
        /* modify the packet's src IP */
        iph->saddr = in_aton("8.8.8.8");
    
        return NF_ACCEPT;
    }
    
    /* A netfilter instance to use */
    static struct nf_hook_ops nfho = {
        .hook = my_hookfn,
        .pf = PF_INET,
        .hooknum = NF_INET_PRE_ROUTING,
        .priority = NF_IP_PRI_FIRST,
        .owner = THIS_MODULE,
    };
    
    static int __init sknf_init(void)
    {
        if (nf_register_hook(&nfho)) {
            printk(KERN_ERR"nf_register_hook() failed
    ");
            return -1;
        }
        return 0;
    }
    
    static void __exit sknf_exit(void)
    {
        nf_unregister_hook(&nfho);
    }
    
    module_init(sknf_init);
    module_exit(sknf_exit);
    MODULE_AUTHOR("zhangsk");
    MODULE_LICENSE("GPL");
    


     

  • 相关阅读:
    修改linux下某一个文件夹下所有文件内容
    jenkins对结果进行断言问题
    linux 循环处理文件夹下所有文件脚本
    LR java Vuser 相关依赖JAR包,配置文件处置方法
    Jmeter函数 唯一取值 笔记
    jmeter+java vuser+rmi+dubbo脚本
    eclipse快捷键
    猫狗队列
    用固定长度的数组实现stack queue
    两个单链表相交的问题
  • 原文地址:https://www.cnblogs.com/aiwz/p/6333286.html
Copyright © 2020-2023  润新知