Netfilter概念与HOOK点(1)

1.框架概念

Netfiler是一套融入到linux内核网络协议栈的框架。
在报文流经的关键位置处,基于不同协议(ipv4/ipv6)的hook方式,使用hook列表中对应的钩子函数匹配处理,以实现过滤、修改报文、跟踪等功能;最后处理过的报文会被决定以“放行”或者“丢弃”行为。

1.1 hook注册的钩子函数具有优先级顺序的执行, 值越小,优先级越高
enum nf_ip_hook_priorities {
	...
	NF_IP_PRI_RAW = -300,     /*对应 raw table*/
	...
	NF_IP_PRI_MANGLE = -150,  /*对应  mangle table*/
	NF_IP_PRI_NAT_DST = -100, /*对应  nat table*/
	NF_IP_PRI_FILTER = 0,     /*对应  filter table*/
	...
	NF_IP_PRI_NAT_SRC = 100,  /*对应  nat table*/
	...
};
1.2 hook被划分成5个关键位置处:PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING
enum nf_inet_hooks {
	NF_INET_PRE_ROUTING, /*对应 PREROUTING*/
	NF_INET_LOCAL_IN,    /*对应 INPUT*/
	NF_INET_FORWARD,     /*对应 FORWARD*/
	NF_INET_LOCAL_OUT,   /*对应 OUTPUT*/
	NF_INET_POST_ROUTING,/*对应 POSTROUTING*/
	NF_INET_NUMHOOKS
};
1.3 hook与chain(链)是一个东西,linux系统环境的iptables命令帮助信息(iptables -h),使用术语为chain(链)
iptables v1.4.21

Usage: iptables -[ACD] chain rule-specification [options]
       iptables -I chain [rulenum] rule-specification [options]
       iptables -R chain rulenum rule-specification [options]
       iptables -D chain rulenum [options]
	   ...
1.4 一个报文会经过的路径:


PRE ROUTING->INPUT:报文会在路由选择之前,简单的健全性检查,然后先经过PRE ROUTING点,再进行路由选择,若是本地的报文,途经INPUT点,然后交由上层处理。当然路由选择时可以丢弃不可达的报文

PRE ROUTING->FORWARD->POST ROUTING:报文会在路由选择之前,简单的健全性检查,然后先经过PRE ROUTING点,再进行路由选择,若不是本地的报文,则进入FORWARD点,然后途经最后一个POST ROUTING点,选择对应子网的出口发送出去。

OUTPUT->POST ROUTING:上层处理完后,创建报文,先经过OUTPUT点,然后途经最后一个POST ROUTING点,根据子网选择对应网口发送出去。

注意:网络上有文章描述OUTPUT点之前或者之后会调用”路由选择“行为,刚接触的同学们会被整懵;小生通过代码阅读以及找到netfiler官方文档也作了描述(https://netfilter.org/documentation/HOWTO/netfilter-hacking-HOWTO-3.html))。实际上,报文先处理路由选择,计算源地址和IP选项 ,然后才进入该hook点。如下的流程图关注的是Hook点的经过,因此没有把 “本地发送->route ->OUTPUT”流程描述出来。
Netfilter-Hook
netfilter官方文档描述

2. hook点和table的关系

每个hook点对应的功能繁多,需要配置N条规则,并且有序的执行;如果过多规则被配置的话,不好维护,所以table就诞生了。table一方面细分功能,另一方面把不同的规则划分到不同table里,加以限定执行的顺序。

比如:用户基于PRE ROUTING点,在RAW表、MANGLE表配置多条规则;然后当报文经过PRE ROUTING点时,因RAW表优先级比MANGLE表高(hook优先级列表),所以RAW表的规则先执行,再执行MANGLE表的规则。

因此所谓的”四表五链“,就是基于4张table和5个hook的关系,来管理诸多规则。
4张表神奇的作用:raw表用于连接跟踪、 mangle表用于修改报文信息、filter表用于过滤报文信息、nat表用于映射地址信息。基于PRE ROUTING点的优先级table规则的执行顺序

3. hook的数据结构、注册钩子列表、调用关键函数

如下hook定义的结构体信息,可以看出它存在于不同net namespace的 struct net结构中,由于net namespace方面可以在同一个系统中建立不同的网络环境,因此hook上的table规则是独立于net namespace中的。

它们之间的包含关系,以及使用struct nf_hook_entries记录不同协议族的不同hook钩子函数信息(ipv4/ipv6协议族的hook点信息)),
struct net ->struct netns_nf -> struct nf_hook_entries-> struct nf_hook_entry[] -> struct nf_hook_ops

/*安装在hook点的钩子函数结构体*/
struct nf_hook_ops {
	/* User fills in from here down. */
	nf_hookfn		*hook;     /*钩子函数,报文经过hook点时调用*/
	struct net_device	*dev;  /*对应的网络设备*/
	void			*priv;     /*每个hook点独有的私有数据*/
	u_int8_t		pf;        /*特定的协议族:NFPROTO_IPV4/NFPROTO_IPV6/...*/
	unsigned int		hooknum;/*钩子安装到哪个hook点*/
	/* Hooks are ordered in ascending priority. */
	int			priority;       /*优先级设置,决定该钩子函数的执行位置,优先级高,则先执行*/
};

/*以下结构体基于net namespace方面而定义*/
struct nf_hook_entry {
	nf_hookfn			*hook;/*隐式的指向nf_hook_ops结构体,仅显式的暴露钩子函数*/
	void				*priv;
};
struct nf_hook_entries {
	u16				num_hook_entries;/*hook点的钩子函数注册的数量*/
	/* padding */
	struct nf_hook_entry		hooks[];/*指向一堆hook点对应的钩子函数*/

	/* 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
	 */
};
/*定义包含不同协议族的hook点钩子函数的结构体*/
struct netns_nf {
	...
	struct nf_hook_entries __rcu *hooks_ipv4[NF_INET_NUMHOOKS];
	struct nf_hook_entries __rcu *hooks_ipv6[NF_INET_NUMHOOKS];
#ifdef CONFIG_NETFILTER_FAMILY_ARP
	struct nf_hook_entries __rcu *hooks_arp[NF_ARP_NUMHOOKS];
#endif
#ifdef CONFIG_NETFILTER_FAMILY_BRIDGE
	struct nf_hook_entries __rcu *hooks_bridge[NF_INET_NUMHOOKS];
#endif
#if IS_ENABLED(CONFIG_DECNET)
	struct nf_hook_entries __rcu *hooks_decnet[NF_DN_NUMHOOKS];
#endif
	...
};
/*net namespace的结构体,系统可以有多个不同网络空间,隔离网络资源。*/
struct net {
	...
	struct netns_nf		nf;/*每个net namespace独有的hook点列表*/
	...
}

ipv4协议族的hook函数注册列表:仅把4张表的安装的钩子函数罗列出来,其它的表可自行查看。
4张表的内核源文件:iptable_raw.c、iptable_mangle.c、iptable_nat.c、iptable_filter.c

在这里插入图片描述
nf_hook函数和NF_HOOK宏定义,它们会在所有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 *))
{
	/*pf = 协议族, hook = 使用哪个hook点, skb = 报文*/
	int ret = nf_hook(pf, hook, net, sk, skb, in, out, okfn);
	if (ret == 1)
		ret = okfn(net, sk, skb);
	return ret;
}

/**
 *	nf_hook - call a netfilter hook
 *
 *	Returns 1 if the hook has allowed the packet to pass.  The function
 *	okfn must be invoked by the caller in this case.  Any other return
 *	value indicates the packet has been consumed by the hook.
 */
static inline int nf_hook(u_int8_t pf, unsigned int hook, struct net *net,
			  struct sock *sk, struct sk_buff *skb,
			  struct net_device *indev, struct net_device *outdev,
			  int (*okfn)(struct net *, struct sock *, struct sk_buff *))
{
	struct nf_hook_entries *hook_head = NULL;
	int ret = 1;

	...
	
	/*选择协议族的hook列表*/
	rcu_read_lock();
	switch (pf) {
	case NFPROTO_IPV4:
		hook_head = rcu_dereference(net->nf.hooks_ipv4[hook]);
		break;
	case NFPROTO_IPV6:
		hook_head = rcu_dereference(net->nf.hooks_ipv6[hook]);
		break;
	case NFPROTO_ARP:
#ifdef CONFIG_NETFILTER_FAMILY_ARP
		if (WARN_ON_ONCE(hook >= ARRAY_SIZE(net->nf.hooks_arp)))
			break;
		hook_head = rcu_dereference(net->nf.hooks_arp[hook]);
#endif
		break;
	case NFPROTO_BRIDGE:
#ifdef CONFIG_NETFILTER_FAMILY_BRIDGE
		hook_head = rcu_dereference(net->nf.hooks_bridge[hook]);
#endif
		break;
#if IS_ENABLED(CONFIG_DECNET)
	case NFPROTO_DECNET:
		hook_head = rcu_dereference(net->nf.hooks_decnet[hook]);
		break;
#endif
	default:
		WARN_ON_ONCE(1);
		break;
	}

	if (hook_head) {
		struct nf_hook_state state;
		/*把相关信息,设置到state结构里*/
		nf_hook_state_init(&state, hook, pf, indev, outdev,
				   sk, net, okfn);

		/*找到对应的hook函数列表 hook_head,遍历有序的函数进行匹配*/
		ret = nf_hook_slow(skb, &state, hook_head, 0);
	}
	rcu_read_unlock();

	return ret;
}

以上源代码以及内容基于内核4.19.1产出,不同的内核版本,表定义的hook点会有不同,如:nat表 原来只定义了pre routing/output/post routing,4.19.1中的源码新增input。

—越简单,易接受。在折腾路上…

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值