从netfilter hook执行原理分析iptables为什么自定义链无法主动调用只能从其它链跳转过来
1、netfilter hook执行原理
netfilter 框架是 Linux 网络子系统里的一个核心模块,iptables 就是基于 netfilter 框架实现的一个网络包处理工具。
netfilter hook被调用后执行的过程剖析:
-
所有点位都是通过一个叫 NF_HOOK 的函数来执行的
nf_hook(pf, hook, net, sk, skb, in, out, okfn)有八个参数: -
nf_hook参数解析:
net:网络命名空间
pf:协议(比如:ipv4的
hook:点位(比如 output)
处理的包:sk(socket)
indev:放数据包进来的网络设备
outdev:要放数据包出去的网络设备
okfn:如果执行完 hook 数据包没被扔掉的话, 就掉用这个函数,也就是下一阶段要执行的函数。一个指向函数的指针,该函数将在该 hook 即将终止时调用,通常传入数据包处理路径上的下一个处理函数。 -
nf_hook_slow(skb, &state, hook_head, 0)这个调用中hook_head就是hook钩子函数的链表头,遍历改hook点的钩子函数,比如:对应iptables的规则,用户自定义hook函数等
-
nf_hook 函数根据 pf 和 hook 参数到内核存放 netfilter 规则的钩子数组里取出规则数组,然后在 nf_hook_slow 函数中去匹配并执行规则
-
nf_hook_slow 函数按数组顺序去匹配并执行每一条规则
-
NF_ACCEPT: 在处理路径上正常继续(实际上是在 NF-HOOK 中最后执行传入的 okfn)。
-
每个钩子函数都有以下返回值,以决定下一步怎么走:
NF_ACCEPT: 在处理路径上正常继续(实际上是在 NF-HOOK 中最后执行传入的 okfn)。
NF_DROP: 丢弃数据包,终止处理。
NF_STOLEN: 数据包已转交,终止处理。
NF_QUEUE: 将数据包入队后供其他处理。
NF_REPEAT: 重新调用当前 hook。
内核在处理流量的时候如何调用到netfilter hook:
-
内核协议栈对PREROUTING的调用:
Linux 在网络包接收在 IP 层的入口函数是 ip_rcv。网络在这里包碰到的第一个 HOOK 就是 PREROUTING
int ip_rcv(struct sk_buff *skb, ......){
......
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
}
-
内核协议栈对INPUT的调用:
NF_HOOK 这个函数会执行到 iptables 中 pre_routing 里的各种表注册的各种规则。
当处理完后,进入 ip_rcv_finish。在这里函数里将进行路由选择。
如果发现是本地设备上的接收,会进入 ip_local_deliver 函数。接着是又会执行到 LOCAL_IN 钩子,这也就是我们说的 INPUT 链。
int ip_local_deliver(struct sk_buff *skb){
......
return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, skb, skb->dev, NULL,
ip_local_deliver_finish);
}
-
内核协议栈对OUTPUT的调用:
Linux 在网络包发送的过程中,首先是发送的路由选择,然后碰到的第一个 HOOK 就是 OUTPUT,然后接着进入 POSTROUTING 链。
int __ip_local_out(struct sk_buff *skb)
{
struct iphdr *iph = ip_hdr(skb);
iph->tot_len = htons(skb->len);
ip_send_check(iph);
return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, skb, NULL,
skb_dst(skb)->dev, dst_output);
}
-
内核协议栈对POSTROUTING的调用:
output处理后,并获取到之前的选路,将进入 ip_output,这里会调用到POSTROUTING,处理完后就发送出去
//file: net/ipv4/ip_output.c
int ip_output(struct sk_buff *skb)
{
...
//再次交给 netfilter,完毕后回调 ip_finish_output
return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, skb, NULL, dev,
ip_finish_output,
!(IPCB(skb)->flags & IPSKB_REROUTED));
}
-
内核协议栈对FORWARD的调用:
除了接收和发送过程以外,Linux 内核还可以像路由器一样来工作。它将接收到网络包(不属于自己的),然后根据路由表选到合适的网卡设备将其转发出去。
这个过程中,先是经历接收数据的前半段。在 ip_rcv 中经过 PREROUTING 链,然后路由后发现不是本设备的包,那就进入 ip_forward 函数进行转发,在这里又会遇到 FORWARD 链。最后还会进入 ip_output 进行真正的发送,遇到 POSTROUTING 链。转发过程的这几步和接收过程一模一样的。不过内核路径就要从上面的 input 方法调用开始不一样。非本设备的不会进入 ip_local_deliver,而是会进入到 ip_forward。
//file: net/ipv4/ip_forward.c
int ip_forward(struct sk_buff *skb)
{
......
return NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD, skb, skb->dev,
rt->dst.dev, ip_forward_finish);
}
hook宏执行流程图解:
在执行 NF-HOOK 宏触发指定的 hook 时,将调用 nf_iterate 函数迭代这个 hook 对应的 nf_hook_ops 链表,并依次调用每一个 nf_hook_ops 的注册函数成员 hookfn。示意图如下:
这种链式调用回调函数的工作方式,也让 netfilter hook 被称为 Chain
每个回调函数也必须返回一个 netfilter 向量;如果该向量为 NF_ACCEPT,nf_iterate 将会继续调用下一个 nf_hook_ops 的回调函数,直到所有回调函数调用完毕后返回 NF_ACCEPT;如果该向量为 NF_DROP,将中断遍历并直接返回 NF_DROP;如果该向量为 NF_REPEAT,将重新执行该回调函数。 nf_iterate 的返回值也将作为 NF-HOOK 的返回值,网络栈将根据该向量值判断是否继续执行处理函数。
netfilter hook 的回调函数机制具有以下特性:
- 回调函数按优先级依次执行,只有上一回调函数返回 NF_ACCEPT 才会继续执行下一回调函数。
- 任一回调函数都可以中断该 hook 的回调函数执行链,同时要求整个网络栈中止对数据包的处理。
2、为什么自定义链无法主动触发,只能从其它链跳转过来被动触发
自定义链的触发依赖jump target:
跳转目标(jump target)jump target 是跳转到其 他 chain 继续处理的动作。很多内置的 chain,它们和调用它们的 netfilter hook 紧密联系在一起。iptables 也支持管理员创建他们自己的用于管理目的的自定义chain。
为什么自定义链无法主动调用只能从其它链跳转过来:
用户定义的 chain 只能通过从另一个规则跳转(jump)到它,因为它们没有注册到 netfilter hook,因为netfilter hook中没有自定义hook点,只有固定的5个内置点。 用户定义的 chain 可以看作是对调用它的 chain 的扩展。例如,用户定义的 chain 在结 束的时候,可以返回 netfilter hook,也可以继续跳转到其他自定义 chain。