Linux Netfilter 技术(3)-NF_HOOK

概述

上节已经按照Netfilter的框架实现自己的hook函数,下面说一下netfilter的HOOK的代码细节,先看一下之前的结构体

结构体&HOOK函数注册

struct nf_hook_ops {
    /* User fills in from here down. */
    nf_hookfn       *hook;
    struct net_device   *dev;
    void            *priv;
    u_int8_t        pf;
    unsigned int        hooknum;
    /* Hooks are ordered in ascending priority. */
    int         priority;
};

以及对这个填充,其中priority是代表什么意思,代码里面又是如何实现的?后面会有答案。

static const struct nf_hook_ops ipv4_test_in_ops = {
        .hook   = nf_ipv4_test_hook,
        .pf     = NFPROTO_IPV4,
        .hooknum = NF_INET_LOCAL_IN,
        .priority = NF_IP_PRI_FIRST,
};

我们具体看一下Netfilter是如何把我们的hook函数注册进去的

int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg)                                                                                                                                       
       ---> __nf_register_net_hook(net, reg->pf, reg);                                                                                                                                                           

看一下这个函数的具体实现细节,其中最主要的是将ipv4_test_in_ops注册到hook_entries里面

static int __nf_register_net_hook(struct net *net, int pf,
                  const struct nf_hook_ops *reg)
{
    struct nf_hook_entries *p, *new_hooks;
    struct nf_hook_entries __rcu **pp;

    ...

    pp = nf_hook_entry_head(net, pf, reg->hooknum, reg->dev);
    if (!pp)
        return -EINVAL;

    mutex_lock(&nf_hook_mutex);

    p = nf_entry_dereference(*pp);
    new_hooks = nf_hook_entries_grow(p, reg); //<===========(1)

    if (!IS_ERR(new_hooks))
        rcu_assign_pointer(*pp, new_hooks);

    ...
}

里面涉及到nf_hook_entries结构体

struct nf_hook_entries {
    u16             num_hook_entries;
    /* padding */
    struct nf_hook_entry        hooks[];

    /* trailer: pointers to original orig_ops of each hook,
     * followed by rcu_head and scratch space used for freeing
     * the structure via call_rcu.
     *
     *   This is not part of struct nf_hook_entry since its only
     *   needed in slow path (hook register/unregister):
     * const struct nf_hook_ops     *orig_ops[]
     *
     *   For the same reason, we store this at end -- its
     *   only needed when a hook is deleted, not during
     *   packet path processing:
     * struct nf_hook_entries_rcu_head     head
     */
};
  • num_hook_entries 是以pf、hooknum注册的函数的总数;
  • hooks[]包含注册函数名称和优先级;

里面的成员nf_hook_entry

struct nf_hook_entry {
    nf_hookfn           *hook;
    void                *priv;
};

注册hook函数的核心代码主要在__nf_register_net_hook函数的(1)函数,我们看一下nf_hook_entries_grow函数:

static struct nf_hook_entries *
nf_hook_entries_grow(const struct nf_hook_entries *old,
             const struct nf_hook_ops *reg)
{
...
    new = allocate_hook_entries_size(alloc_entries);
    if (!new)
        return ERR_PTR(-ENOMEM);

    new_ops = nf_hook_entries_get_hook_ops(new); // <======(1)
...

        if (inserted || reg->priority > orig_ops[i]->priority) {   //<======(2)
            new_ops[nhooks] = (void *)orig_ops[i];
            new->hooks[nhooks] = old->hooks[i];
            i++;
        }
        ...
        nhooks++;
    }

...
}
  • (1)处为新注册的函数alloc内存空间(因为在同一个pf、hooknum可以注册不同的函数);
  • (2)处是根据注册时的优先级(priority),将不同的hook函数排序,高优先级的排在数组的前面也就会先被执行;

通过代码可以发现__nf_register_net_hook主要作用就是为ipv4_test_in_ops结构体分配地址并将内容更新到nf_hook_entry里面,
代码里面有用到rcu锁的知识,以后可以详细了解学习一下,注册这边看完了,下面详细看一下HOOK是如何调用的。

HOOK 函数调用

可以看到在执行到我们注册的hook函数nf_ipv4_test_hook,那具体是如何调用的呢?看一下代码,在代码里面hooknum被调用的且是ipv4的地方就是在

int ip_local_deliver(struct sk_buff *skb)
{
    /*
     *  Reassemble IP fragments.
     */
    struct net *net = dev_net(skb->dev);

    if (ip_is_fragment(ip_hdr(skb))) {
        if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER))
            return 0;
    }

    return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,
               net, NULL, skb, skb->dev, NULL,
               ip_local_deliver_finish);
}

可以看到NF_HOOK函数的是从NFPROTO_IPV4, NF_INET_LOCAL_IN就是我们注册的,其实看到这里我们已经明白了是如何调用的,就是
根据内核协议栈一些必经的位置提前埋下hook点,我们注册的函数在此处被执行,虽然已经明白了,我们还是详细看看代码的实现
NF_HOOK

static inline int
NF_HOOK(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk, struct sk_buff *skb,
    struct net_device *in, struct net_device *out,
    int (*okfn)(struct net *, struct sock *, struct sk_buff *))
{
    int ret = nf_hook(pf, hook, net, sk, skb, in, out, okfn);   //<======(1)
    if (ret == 1)
        ret = okfn(net, sk, skb);     //<====(2)
    return ret;
}

在(1)处执行我们以NFPROTO_IPV4, NF_INET_LOCAL_IN注册的所有函数,(2)处根据注册函数执行的结果决定是否执行回调函数

nf_hook
 --> nf_hook_slow

nf_hook_slow

int nf_hook_slow(struct sk_buff *skb, struct nf_hook_state *state,
         const struct nf_hook_entries *e, unsigned int s)
{
    unsigned int verdict;
    int ret;

    for (; s < e->num_hook_entries; s++) {
        verdict = nf_hook_entry_hookfn(&e->hooks[s], skb, state); //<====(1)
        switch (verdict & NF_VERDICT_MASK) {
        case NF_ACCEPT:
            break;
        case NF_DROP:
            kfree_skb(skb);
            ret = NF_DROP_GETERR(verdict);
            if (ret == 0)
                ret = -EPERM;
            return ret;
        case NF_QUEUE:
            ret = nf_queue(skb, state, e, s, verdict);
            if (ret == 1)
                continue;
            return ret;
        default:
            /* Implicit handling for NF_STOLEN, as well as any other
             * non conventional verdicts.
             */
            return 0;
        }
    }

    return 1;
}

在(1)遍历执行所有注册的函数。
到这里基本已经了解了HOOK函数是如何注册和调用的,总结代码路径基本就是
注册

nf_register_net_hook

调用

NF_HOOK
 -->nf_hook
   -->nf_hook_slow
     -->nf_ipv4_test_hook

验证

前面是理论和代码分析,根据我们Linux Netfilter 技术(2)-代码里面提供的kernel module
可以使用gdb+qemu方式看一下是否如我们分析。

(gdb) bt
#0  nf_ipv4_test_hook (priv=0x0 <irq_stack_union>, skb=0xffff88806c671d00, state=0xffff88807cb83c30) at /root/learn-linux/netfilter_hook/netfilter_hook.c:53
#1  0xffffffff81754654 in nf_hook_entry_hookfn (entry=<optimized out>, entry=<optimized out>, state=<optimized out>, skb=<optimized out>) at include/linux/netfilter.h:119
#2  nf_hook_slow (skb=0x0 <irq_stack_union>, state=0xffff88806c671d00, e=0xffff88807cb83c30, s=<optimized out>) at net/netfilter/core.c:511
#3  0xffffffff817608cc in nf_hook (sk=<optimized out>, outdev=<optimized out>, okfn=<optimized out>, indev=<optimized out>, skb=<optimized out>, net=<optimized out>, hook=<optimized out>, pf=<optimized out>)
    at include/linux/netfilter.h:244
#4  NF_HOOK (pf=<optimized out>, sk=<optimized out>, out=<optimized out>, okfn=<optimized out>, in=<optimized out>, skb=<optimized out>, net=<optimized out>, hook=<optimized out>)
    at include/linux/netfilter.h:287
#5  ip_local_deliver (skb=0xffff88806c671d00) at net/ipv4/ip_input.c:260
#6  0xffffffff81760b53 in NF_HOOK (sk=<optimized out>, pf=<optimized out>, hook=<optimized out>, in=<optimized out>, out=<optimized out>, okfn=<optimized out>, skb=<optimized out>, net=<optimized out>)
    at include/linux/netfilter.h:289
#7  NF_HOOK (pf=<optimized out>, sk=<optimized out>, out=<optimized out>, okfn=<optimized out>, in=<optimized out>, skb=<optimized out>, net=<optimized out>, hook=<optimized out>)
    at include/linux/netfilter.h:283
#8  ip_rcv (skb=0xffff88806c671d00, dev=0xffff88806d257000, pt=<optimized out>, orig_dev=<optimized out>) at net/ipv4/ip_input.c:500
#9  0xffffffff816f1db2 in __netif_receive_skb_core (skb=0xffff88806c671d00, pfmemalloc=<optimized out>) at net/core/dev.c:1977
#10 0xffffffff816f45d2 in netif_receive_skb_internal (skb=<optimized out>) at net/core/dev.c:5071
#11 netif_receive_skb_internal (skb=0x0 <irq_stack_union>) at net/core/dev.c:5036
#12 0xffffffff816f532a in napi_skb_finish (skb=<optimized out>, ret=GRO_NORMAL) at net/core/dev.c:5445
#13 napi_gro_receive (napi=0xffff888036075008, skb=0xffff88806c671d00) at net/core/dev.c:5476
#14 0xffffffffc00e2755 in ?? ()
#15 0x0000000000000001 in irq_stack_union ()
#16 0x0000000000000000 in ?? ()

简单整理一下:

napi_gro_receive
    napi_skb_finish
        netif_receive_skb_internal
            ip_rcv
                NF_HOOK                        (1)
                  ip_local_deliver
                    NF_HOOK                    (2)
                        nf_hook
                            nf_hook_slow
                                nf_ipv4_test_hook

可以看到和我们理论分析是一样的,只是在整个代码执行路径NF_HOOK被执行了两次

NF_INET_PRE_ROUTING(1)  -->  NF_INET_LOCAL_IN(2)

也就可以判断这个流量是发往本地的流量,值得一提的是(2)处是因为(1)处执行了回调函数ip_rcv_finish才被调用的。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇