ipvs hook点在netfilter中的位置以及优先级
1、netfilter栈全景图
2、Netfilter hooks
五个hook点:
每个 hook 在内核网络栈中对应特定的触发点位置,以 IPv4 协议栈为例,有以下 netfilter hooks 定义:
- NF_INET_PRE_ROUTING: 这个 hook 在 IPv4 协议栈的 ip_rcv 函数或 IPv6 协议栈的 ipv6_rcv 函数中执行。所有接收数据包到达的第一个 hook 触发点(实际上新版本 Linux 增加了 INGRESS hook 作为最早触发点),在进行路由判断之前执行。
- NF_INET_LOCAL_IN: 这个 hook 在 IPv4 协议栈的 ip_local_deliver() 函数或 IPv6 协议栈的 ip6_input() 函数中执行。经过路由判断后,所有目标地址是本机的接收数据包到达此 hook 触发点。
- NF_INET_FORWARD: 这个 hook 在 IPv4 协议栈的 ip_forward() 函数或 IPv6 协议栈的 ip6_forward() 函数中执行。经过路由判断后,所有目标地址不是本机的接收数据包到达此 hook 触发点。
- NF_INET_LOCAL_OUT: 这个 hook 在 IPv4 协议栈的 __ip_local_out() 函数或 IPv6 协议栈的 __ip6_local_out() 函数中执行。所有本机产生的准备发出的数据包,在进入网络栈后首先到达此 hook 触发点。
- NF_INET_POST_ROUTING: 这个 hook 在 IPv4 协议栈的 ip_output() 函数或 IPv6 协议栈的 ip6_finish_output2() 函数中执行。本机产生的准备发出的数据包或者转发的数据包,在经过路由判断之后, 将到达此 hook 触发点。
3、iptables四表五链
通过iptables下发的规则,最终都会与上图中标注的5个hook点位关联。iptables将这些规则,按照不同的作用划分到不同的表中,按照划分常用的有raw、mangle、filter、nat四张表,即为四表。而关联在5个hook点位的有优先级顺序的规则链,即为五链。这种配置管理逻辑,也就是使用iptables的人都最为熟知的“四表五链”。
每个链上的每个表都可以挂载多条规则:
表之间也是有优先级的:
raw > mangle > nat > filter
3、netfilter hook点
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,
}
hook点优先级:数值越低,优先级越高
enum nf_ip_hook_priorities {
NF_IP_PRI_FIRST = INT_MIN,
NF_IP_PRI_CONNTRACK_DEFRAG = -400,
NF_IP_PRI_RAW = -300,
NF_IP_PRI_SELINUX_FIRST = -225,
NF_IP_PRI_CONNTRACK = -200,
NF_IP_PRI_MANGLE = -150,
NF_IP_PRI_NAT_DST = -100,
NF_IP_PRI_FILTER = 0,
NF_IP_PRI_SECURITY = 50,
NF_IP_PRI_NAT_SRC = 100,
NF_IP_PRI_SELINUX_LAST = 225,
NF_IP_PRI_CONNTRACK_HELPER = 300,
NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
NF_IP_PRI_LAST = INT_MAX,
};
Hook点和Hook函数的优先级整合:
hook函数执行过程:
同一个HP(HookPoint)可以注册多个hook函数,并且这些hook函数的优先级并不相同,会按照优先级先后顺序来依次执行,同时系统要保证每一个hook函数都会被执行,那么就要求每一个hook函数在处理完数据包后,都必须向Netfilter报告处理结果。如果数据包在前面某个hook函数处理完后,被永久性的借走了,该hook函数又没有向Netfilter报告,那么后面的hook函数将不知道该如何处理。所有hook函数的返回值将只能是以下几个之一,这些值定义
#define NF_DROP 0 //丢弃
#define NF_ACCEPT 1 //保留
#define NF_STOLEN 2 //忘掉,报文不再往上传递,区别于NF_DROP,它没有调用kfree_skb()来释放skb
#define NF_QUEUE 3 //插入用户空间(队列中)
#define NF_REPEAT 4 //再次调用本hook函数
#define NF_STOP 5 //停止后面的hook函数的执行,直接返回
#define NF_MAX_VERDICT NF_STOP
4、netfilter与Iptables关系
5、每个链中的表执行关系
当一个包触发 netfilter hook 时,处理过程将沿着列从上向下执行:
注意:
特定事件会导致 table 的 chain 被跳过。例如,只有每个连接的第一个包会去匹配 NAT 规则,对这个包的动作会应用于此连接后面的所有包。到这个连接的应答包会被自动应用反 方向的 NAT 规则,因为内核的conntrack机制。
6、ipvs hook点与iptables hook点的先后关系
netfilter中hook点优先级:数值越低,优先级越高
enum nf_ip_hook_priorities {
NF_IP_PRI_FIRST = INT_MIN,
NF_IP_PRI_RAW_BEFORE_DEFRAG = -450,
NF_IP_PRI_CONNTRACK_DEFRAG = -400,
NF_IP_PRI_RAW = -300,
NF_IP_PRI_SELINUX_FIRST = -225,
NF_IP_PRI_CONNTRACK = -200,
NF_IP_PRI_MANGLE = -150,
NF_IP_PRI_NAT_DST = -100,
NF_IP_PRI_FILTER = 0,
NF_IP_PRI_SECURITY = 50,
NF_IP_PRI_NAT_SRC = 100,
NF_IP_PRI_SELINUX_LAST = 225,
NF_IP_PRI_CONNTRACK_HELPER = 300,
NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
NF_IP_PRI_LAST = INT_MAX,
};
绘图:
为什么默认情况下,ipvs会跳过conntrack:
看看iptables hook点的优先级,具体数值查询上面netfilter优先级表:
iptables_filter表优先级
...
/** filter表挂载在LOCAL_IN、FORWORD、LOCAL_OUT hook点上 **/
#define FILTER_VALID_HOOKS ((1 << NF_INET_LOCAL_IN) | \
(1 << NF_INET_FORWARD) | \
(1 << NF_INET_LOCAL_OUT))
...
static const struct xt_table packet_filter = {
.name = "filter",
.valid_hooks = FILTER_VALID_HOOKS,
.me = THIS_MODULE,
.af = NFPROTO_IPV4,
.priority = NF_IP_PRI_FILTER,
.table_init = iptable_filter_table_init,
};
...
mangle表优先级
...
/** mangle表挂载在所有的hook点上 **/
#define MANGLE_VALID_HOOKS ((1 << NF_INET_PRE_ROUTING) | \
(1 << NF_INET_LOCAL_IN) | \
(1 << NF_INET_FORWARD) | \
(1 << NF_INET_LOCAL_OUT) | \
(1 << NF_INET_POST_ROUTING))
...
static const struct xt_table packet_mangler = {
.name = "mangle",
.valid_hooks = MANGLE_VALID_HOOKS,
.me = THIS_MODULE,
.af = NFPROTO_IPV4,
.priority = NF_IP_PRI_MANGLE,
.table_init = iptable_mangle_table_init,
};
...
nat表优先级
...
/** nat表挂载在PRE_ROUTING、POST_ROUTING、LOCAL_OUT和LOCAL_IN hook点上**/
static const struct xt_table nf_nat_ipv4_table = {
.name = "nat",
.valid_hooks = (1 << NF_INET_PRE_ROUTING) |
(1 << NF_INET_POST_ROUTING) |
(1 << NF_INET_LOCAL_OUT) |
(1 << NF_INET_LOCAL_IN),
.me = THIS_MODULE,
.af = NFPROTO_IPV4,
.table_init = iptable_nat_table_init,
};
...
/**
nat表又细分为dnat和snat,具备不同的优先级,
dnat挂载在PRE_ROUTING和OUTPUT hook点上
snat挂载在INPUT和POSTROUTING hook点上
**/
static const struct nf_hook_ops nf_nat_ipv4_ops[] = {
{
.hook = iptable_nat_do_chain,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_PRE_ROUTING,
.priority = NF_IP_PRI_NAT_DST,
},
{
.hook = iptable_nat_do_chain,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_POST_ROUTING,
.priority = NF_IP_PRI_NAT_SRC,
},
{
.hook = iptable_nat_do_chain,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_OUT,
.priority = NF_IP_PRI_NAT_DST,
},
{
.hook = iptable_nat_do_chain,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_NAT_SRC,
},
};
raw表优先级
...
/** raw表挂载在PRE_ROUTING和LOCAL_OUT hook点上 **/
#define RAW_VALID_HOOKS ((1 << NF_INET_PRE_ROUTING) | (1 << NF_INET_LOCAL_OUT))
...
static const struct xt_table packet_raw = {
.name = "raw",
.valid_hooks = RAW_VALID_HOOKS,
.me = THIS_MODULE,
.af = NFPROTO_IPV4,
.priority = NF_IP_PRI_RAW,
.table_init = iptable_raw_table_init,
};
static const struct xt_table packet_raw_before_defrag = {
.name = "raw",
.valid_hooks = RAW_VALID_HOOKS,
.me = THIS_MODULE,
.af = NFPROTO_IPV4,
.priority = NF_IP_PRI_RAW_BEFORE_DEFRAG,
.table_init = iptable_raw_table_init,
};
security优先级
...
/** security表挂载在LOCAL_IN、FORWARD和LOCAL_OUT hook点上 **/
#define SECURITY_VALID_HOOKS (1 << NF_INET_LOCAL_IN) | \
(1 << NF_INET_FORWARD) | \
(1 << NF_INET_LOCAL_OUT)
...
static const struct xt_table security_table = {
.name = "security",
.valid_hooks = SECURITY_VALID_HOOKS,
.me = THIS_MODULE,
.af = NFPROTO_IPV4,
.priority = NF_IP_PRI_SECURITY,
.table_init = iptable_security_table_init,
};
再看看ipvs hook点的优先级,具体数值查询上面netfilter优先级表::
static const struct nf_hook_ops ip_vs_ops4[] = {
/* After packet filtering, change source only for VS/NAT */
{
.hook = ip_vs_reply4,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_NAT_SRC - 2,
},
/* After packet filtering, forward packet through VS/DR, VS/TUN,
* or VS/NAT(change destination), so that filtering rules can be
* applied to IPVS. */
{
.hook = ip_vs_remote_request4,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_NAT_SRC - 1,
},
/* Before ip_vs_in, change source only for VS/NAT */
{
.hook = ip_vs_local_reply4,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_OUT,
.priority = NF_IP_PRI_NAT_DST + 1,
},
/* After mangle, schedule and forward local requests */
{
.hook = ip_vs_local_request4,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_OUT,
.priority = NF_IP_PRI_NAT_DST + 2,
},
/* After packet filtering (but before ip_vs_out_icmp), catch icmp
* destined for 0.0.0.0/0, which is for incoming IPVS connections */
{
.hook = ip_vs_forward_icmp,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_FORWARD,
.priority = 99,
},
/* After packet filtering, change source only for VS/NAT */
{
.hook = ip_vs_reply4,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_FORWARD,
.priority = 100,
},
};
有了ipvs hook优先级关系和iptables hook优先级后,就可以比较ipvs hook和iptables rule hook的优先级关系,以input链分析为例,其它链按照这个方式去比较:
input链上,ipvs的2个hook的优先级如下,查上述表:
NF_IP_PRI_NAT_SRC - 2 = 100 - 2 = 98
NF_IP_PRI_NAT_SRC - 1 = 100 - 1 = 99
iptables hook在每个表的优先级如下,查上述表:
NF_IP_PRI_FILTER = 0
NF_IP_PRI_MANGLE = -150
NF_IP_PRI_NAT_SRC = 100
NF_IP_PRI_SECURITY = 50
得到input链中执行顺序:mangle hook > filter hook > ipvs hook > nat hook
给出iptables表的优先级数值关系,以方便查询对比:
总结优先级关系:
根据定义,ipvs挂载在LOCAL_IN、FORWORD和LOCAL_OUT的hook点上了,根据优先级,我们得到以下结论:
1、INPUT:ipvs的hook会在Filter TABLE 后执行,然后转到NAT TABLE
2、OUTPUT:ipvs的hook会在NAT TABLE ,然后转到Filter TABLE
3、FORWARD:ipvs的hookFilter TABLE后执行,然后结束整个FORWARD,包往POSTROUTING走
我们在iptables流程图上新增了ipvs的位置,图中的流转顺序就是执行顺序,比如:FORWARD表中mangle,filter,ipvs三个是按顺序排好的从mangle到filter,再到ipvs: