netfilter框架是Linux操作系统中内置的,通过向内核模块提供对协议栈中的网络数据包进行修改和操作的能力,来实现流量过滤,网络地址转换等高级功能。
1) netfilter如何工作
对于网络数据报文,网络设备驱动通过将二层的以太网数据报文按照Linux内核定义的网络设备驱动规范,以sk_buff结构体的方式进行接收或者发送,即通常我们所描述的报文的最小单元skb。
● 内核通过将网络设备缓冲区环形队列中的skb取出,并按照以太网层,网络层,传输层的顺序处理后,将报文数据放置到对应的Socket缓冲区中,通知用户程序进行读取,从而完成收包。
● 内核为Socket缓冲区的待发送数据封装为skb,经过传输层,网络层和以太网层依次填充对应的报头后,调用网络设备驱动的方法将skb发送到网络上,从而完成发包。
netfilter工作的核心原理则是在网络层,通过在五个不同的内核处理skb数据包的位置,执行注册到netfilter框架中的回调函数,并根据回调函数的返回来选择下一步的处理,以实现复杂的功能。
netfilter触发的时机
netfilter在内核网络数据包的处理流程中,注册了5个可以触发的时机,详情如下:
/* IP Hooks */ /* After promisc drops, checksum checks. */ #define NF_IP_PRE_ROUTING0 /* If the packet is destined for this box. */ #define NF_IP_LOCAL_IN1 /* If the packet is destined for another interface. */ #define NF_IP_FORWARD2 /* Packets coming from a local process. */ #define NF_IP_LOCAL_OUT3 /* Packets about to hit the wire. */ #define NF_IP_POST_ROUTING4 #define NF_IP_NUMHOOKS5 #endif /* ! __KERNEL__ */
根据源代码中的定义,我们可以参考下图:
从上面的代码定义和示意图中可以知道,在以下几个地方会调用netfilter定义好的方法对数据包进行处理:
● 数据包从网卡进入协议栈后,所有的报文都会走到NF_IP_PRE_ROUTING。
● 数据包经过入向的路由选择后,确认是由本机的传输层进行处理的,会进入到NF_IP_LOCAL_IN。
● 数据包经过入向的路由选择后,不是由本机处理,需要转发给其他机器的,会进入到NF_IP_FORWARD。
● 由本机的传输层发出的报文,都会经过NF_IP_LOCAL_OUT。
● 经过出向的路由选择后的报文,会经过NF_IP_POST_ROUTING,包括从本机发出的和需要本机转发的。
在以上五个时机,当数据包skb到达时,netfilter框架定义的回调方法就会被内核执行。
netfilter如何操作网络数据
使用netfilter框架的模块,需要按照netfilter定义的结构体来实现自己的行为,才能正确注册到netfilter框架中,并被内核调用,netfilter约束的注册结构体的定义如下:
struct nf_hook_ops { // 这是真正被内核执行的函数,会对skb进行读取和修改 nf_hookfn*hook; struct net_device*dev; void*priv; u_int8_tpf; // hooknum定义了回调函数生效的位置,从上文可知有五个位置可以选择 unsigned inthooknum; // priority定义了回调函数的优先级,通过每个hook时机都会有多个回调函数需要生效 intpriority; };
以ipvlan模块为例:
static const struct nf_hook_ops ipvl_nfops[] = { { .hook = ipvlan_nf_input, .pf = NFPROTO_IPV4, .hooknum = NF_INET_LOCAL_IN, .priority = INT_MAX, }, #if IS_ENABLED(CONFIG_IPV6) { .hook = ipvlan_nf_input, .pf = NFPROTO_IPV6, .hooknum = NF_INET_LOCAL_IN, .priority = INT_MAX, }, #endif };
我们可以看到,ipvlan模块根据IP协议的版本定义了两个hook结构体,其中:
● hookfn是核心的处理逻辑,ipvlan模块注册了ipvlan_nf_input方法。
● pf是netfilter的协议版本,ipvlan模块分别注册了IPv6和IPv4对应的方法。
● hooknum定义了这个hook在netfilter中生效的位置,ipvlan模块将自己的回调方法注册到了NF_INET_LOCAL_IN,也就是完成路由之后,进入传输层之前。
● priotity定义了hook的优先级。
那么,hookfn作为真正核心的处理逻辑,他是如何工作的呢?
以ipvlan注册在NF_INET_LOCAL_IN上的hook方法ipvlan_nf_input为例:
unsigned int ipvlan_nf_input(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { // 参数中可以看到,有前一个生效的hook结构体,当前处理的skb报文本身以及当前netfilter框架的上下文信息 struct ipvl_addr *addr; unsigned int len; addr = ipvlan_skb_to_addr(skb, skb->dev); if (!addr) goto out; skb->dev = addr->master->dev; len = skb->len + ETH_HLEN; ipvlan_count_rx(addr->master, len, true, false); out: // 这里返回了netfilter框架规定的返回码 return NF_ACCEPT; }
从ipvlan_nf_input可以看到,注册到netfilter中的回调函数的核心在于两个约束:
● 回调函数的入参定义,可以接受协议栈中真正的报文结构体skb以及netfilter的上下文状态为参数。
● 回调函数的返回值,需要是netfilter规定好的返回值,他们的定义如下:
/* Responses from hook functions. */ #define NF_DROP 0 #define NF_ACCEPT 1 #define NF_STOLEN 2 #define NF_QUEUE 3 #define NF_REPEAT 4 #define NF_STOP 5/* Deprecated, for userspace nf_queue compatibility. */ #define NF_MAX_VERDICT NF_STOP
从以上的分析不难看出,只要满足netfilter的注册条件,就可以在回调函数中直接对数据报文skb内容进行读取和修改操作,而通过返回值的定义,则可以借助netfilter框架完成对数据包的处理,比如丢弃,接收或者传递到用户态进行处理。
netfilter的内核实现
在负责对所有hook方法进行遍历处理的函数中,可以看到,netfilter核心的工作流程就是在相应的触发时机调用这个时机上注册的所有方法,按照优先级进行处理,并根据每一次处理的结果进行操作,包括丢弃,传输给用户态等等:
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++) { // 在这里调用对应的hook时机上的所有注册的hook回调方法,然后根据结果选择是不是继续循环 verdict = nf_hook_entry_hookfn(&e->hooks[s], skb, state); 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;
更多精彩内容,欢迎观看:
《云原生网络数据面可观测性最佳实践》—— 一、容器网络内核原理———2.netfilter框架(下):https://developer.aliyun.com/article/1221729?spm=a2c6h.13148508.setting.16.15f94f0e18Oqpt