概述
上节已经按照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
才被调用的。