概述
在上一篇文章《netfilter&iptables探讨(1)——基本原理》中,我们分析了netfilter和iptables机制的基本原理以及相关的模块和接口。本文将进一步探讨这套机制的基石——netfilter的原理和实现。
问题
- netfilter支持了多少个hook点?分别在内核协议栈的什么位置?
- 基于netfilter,可以对协议栈行为做哪些维度的修改?
- hook函数的参数和返回值是如何定义的,有什么意义?
netfilter的hook位置
上图来自wiki,是linux内核的报文处理全流程图,其中展示了当前内核支持了所有netfilter hook点。
首先需要明确的是,netfilter并不是专为支持iptables而产生的机制,而是Linux内核网络协议栈的公共机制。因此netfilter中内置的hook点其实相当多,在各类网络报文的处理逻辑中都有相对应的hook点,例如IPv4、IPv6、ARP、bridge等。iptables中所使用的是IPv4报文处理路径中的5个hook点,hook点在iptables中被称为链(chain),这可能是因为在每个hook点上都会用链表的形式维护多种iptables规则表的不同处理函数。IPv6的处理路径中也有5个hook点,功能逻辑上和IPv4的是一致的。
下面对netfilter hook点的介绍和图片很大部分来自于《来,今天飞哥带你理解 iptables 原理!》这篇文章,关于IPv4 hook点的更多细节可以在这篇文章中找到。
IPv4 | IPv6 | |
PRE_ROUTING | ip_rcv | ipv6_rcv |
LOCAL_IN | ip_local_deliver | ip6_input |
FORWARD | ip_forward | ip6_forward |
LOCAL_OUT | __ip_local_out | __ip6_local_out/ip6_xmit |
POST_ROUTING | ip_output | ip6_output |
上面的表格中列出了IPv4、IPv6协议栈中的5个hook点名称和位置。
- PRE_ROUTING:收到外部发来的报文,在ip_rcv/ipv6_rcv函数中,执行本地路由选择前执行
- LOCAL_IN:根据收到的报文的目的地址,判断报文是发送给本设备后,在本地报文处理函数ip_local_deliver/ip6_input中执行
- FORWARD:根据收到的报文的目的地址,判断报文不是发送给本设备,而是需要转发给其他设备,在转发函数ip_forward/ip6_forward中执行
- LOCAL_OUT:在本地发送报文的处理过程中,完成路由选择后,在__ip_local_out/__ip6_local_out中执行
- POST_ROUTING:在报文构造和路由选择完成后,在ip_output/ip6_output函数中执行。在本地发送报文的执行路径中,LOCAL_OUT和POST_ROUTING几乎是连续执行的。之所以要分别定义两处hook,是因为最终需要发送报文的不止是本地发送的场景,还有转发的场景,因此POST_ROUTING和LOCAL_OUT被执行到的情况并不是完全相同的。
在本地接收报文的过程中,会执行PRE_ROUTING和LOCAL_IN位置的hook函数。如下图所示:
在本地发送报文的过程中,会执行LOCAL_OUT和POST_ROUTING位置的hook函数。如下图所示:
在收到需要转发的报文的处理过程中,会执行PRE_ROUTING、FORWARD和POST_ROUTING位置的hook函数。如下图所示:
把以上这些路径结合起来,就是IP协议栈对报文的主要处理逻辑:
hook函数
netfilter中注册的hook函数指针定义为:
typedef unsigned int nf_hookfn(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state);
其中的参数priv是注册hook时提供的私有结构指针。skb是当前处理的数据报文。state则是一些网络上下文信息,包括报文的输入输出网卡、所属的netns、所属的socket信息等。
在hook函数中可以根据网络上下文以及当前的报文内容,对报文进行修改、丢弃、统计、改变后续执行流等各种操作。由于netfilter的使用者都是其他内核模块,本身就有最高权限,因此基于在netfilter hook中可以实现的功能几乎是没有任何限制的。
hook函数的返回值会影响协议栈对报文的后续处理方式,可能的返回值有:
NF_ACCEPT:当前hook函数执行完成,继续执行链表上的下一个hook函数。如果没有下一个hook函数则当前hook点执行完成,返回继续执行协议栈处理流程;
NF_DROP:报文skb需要丢弃,释放掉skb并结束处理流程;
NF_QUEUE:将报文skb加入队列。这里的队列处理逻辑也是可以定制和注册的,但目前只有nfnetlink_queue模块实现了这个接口。通过nfnetlink_queue提供的queue机制,可以将skb加入到一个队列中,并通过netlink协议接口发送给用户态的程序做进一步处理。
NF_STOLEN:报文skb已被hook函数处理完成或接管,直接结束处理流程;
NF_REPEAT:一些文章中说这个返回值会让hook函数被再次调用,在较新的内核上显然并非如此。从手头的5.9.11内核实现来看,hook函数返回NF_ACCEPT、NF_DROP、NF_QUEUE之外的值都会被当做NF_STOLEN处理。NF_REPEAT只会在一些模块的特定逻辑中使用,例如nfnetlink_queue的报文注入逻辑中。这个值不再能作为hook函数返回值使用。
小结
本文在前一篇文章的基础上,进一步分析了netfilter的原理和具体实现。
- netfilter在不同协议的处理流程中提供了不同的hook点,在IPv4和IPv6协议栈中分别支持了5个hook点。
- 在netfilter的hook函数中,可以根据网络上下文以及当前的报文内容,对报文进行修改、丢弃、统计、改变后续执行流等各种操作,作为内核的一部分,hook函数的功能几乎是无限的,因此必须慎重设计和实现。
- hook函数的参数包括hook自定义的私有指针、skb和网络上下文信息。hook函数的返回值有4种,不同的返回值会影响协议栈对报文的后续处理逻辑。
最后
以上就是妩媚季节为你收集整理的netfilter&iptables探讨(2)——netfilter原理与实现问题netfilter的hook位置hook函数小结的全部内容,希望文章能够帮你解决netfilter&iptables探讨(2)——netfilter原理与实现问题netfilter的hook位置hook函数小结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复