linux内核协议栈 netfilter 之连接跟踪子系统AF_INET钩子函数

本文详细介绍了Linux内核协议栈中netfilter的连接跟踪子系统,特别是AF_INET钩子函数的工作流程。内容涉及数据包流向、分片重组、连接跟踪、连接确认等关键步骤,解析了ipv4_conntrack_defrag、ipv4_conntrack_in、ipv4_conntrack_help、ipv4_confirm等核心函数的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1 连接跟踪子系统钩子函数概述

1.1 数据包流向

1.1.1 发往本地的数据

1.1.2 本地转发的数据

1.1.3 本机发出的数据

2 数据包重组 ipv4_conntrack_defrag()

2.1 nf_ct_ipv4_gather_frags()

3 ipv4_conntrack_in()

3.1 nf_conntrack_in()【核心中的核心】

4 ipv4_conntrack_local()

4.1 nf_conntrack_in()

5 ipv4_conntrack_help()/ipv4_helper()

6 连接确认 ipv4_confirm()

6.1 nf_conntrack_confirm()【核心】


1 连接跟踪子系统钩子函数概述

《linux内核协议栈 netfilter 之 ip 层的 hook 注册概述以及 hook 的调用场景》中的1.4小结所述,为了支持连接跟踪子系统,AF_INET协议族在4个HOOK点共注册了8个钩子函数,每个HOOK点有两个。这些钩子函数可分为入口(PRE_ROUTING和LOCAL_IN)和出口(LOCAL_OUT和POST_ROUTING)两大类。

1.1 数据包流向

在分析之前,我们再次回顾一下数据包在连接跟踪模块中的走向。

1.1.1 发往本地的数据

对于连接跟踪来说,只在hook点NF_IP_PRE_ROUTING、NF_IP_LOCAL_IN两个hook点进入到连接跟踪模块,按优先级顺序,依次调用

ipv4_conntrack_defrag->ipv4_conntrack_in->ipv4_conntrack_help->ipv4_confirm,其中ipv4_conntrack_defrag主要是对分段数据包进行重组操作;接着调用ipv4_conntrack_in为数据包的nfct指针进行赋值以及更新连接跟踪项的状态等;然后调用ipv4_conntrack_help [高版本改为ipv4_help] 执行数据包对应的连接跟踪项的helper指针,实现创建期望连接或者ALG等功能;最后调用ipv4_confirm将一个未确认的连接跟踪项进行确认操作,并加入确认链表中。

 

1.1.2 本地转发的数据

对于连接跟踪来说,只在hook点NF_IP_PRE_ROUTING、NF_IP_POST_ROUTING两个hook点进入到连接跟踪模块,按优先级顺序,依次调用

ipv4_conntrack_defrag->ipv4_conntrack_in->ipv4_conntrack_help [高版本改为ipv4_help] ->ipv4_confirm,我们发现调用的函数与发往本机的数据包调用的函数及顺序是一样的。

 

1.1.3 本机发出的数据

对于连接跟踪来说,只在hook点NF_IP_LOCAL、NF_IP_POST_ROUTING两个hook点进入到连接跟踪模块,按优先级顺序,依次调用

ipv4_conntrack_defrag->ipv4_conntrack_local->ipv4_conntrack_help [高版本改为ipv4_help] ->ipv4_confirm,我们发现调用的函数与发往本机的数据包调用的函数及顺序相比,只有ipv4_conntrack_local与ipv4_conntrack_in不一样,相比于ipv4_conntrack_in,ipv4_conntrack_local增加了对数据包大小的判断。

 

2 数据包重组 ipv4_conntrack_defrag()

连接跟踪子系统的两个入口处(PRE_ROUTING和LOCAL_IN)都注册了该钩子函数。ipv4_conntrack_defrag()完成的工作就是重组IP报文,该钩子函数以较高优先级工作在连接跟踪子系统的入口处,这样可以保证连接跟踪子系统后面见到的报文都是完整的IP报文,也就是说连接跟踪子系统不处理IP分片。

static unsigned int ipv4_conntrack_defrag(unsigned int hooknum,
					  struct sk_buff *skb,
					  const struct net_device *in,
					  const struct net_device *out,
					  int (*okfn)(struct sk_buff *))
{
	struct sock *sk = skb->sk;
	struct inet_sock *inet = inet_sk(skb->sk);

	if (sk && (sk->sk_family == PF_INET) &&
	    inet->nodefrag)
		return NF_ACCEPT;

#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
#if !defined(CONFIG_NF_NAT) && !defined(CONFIG_NF_NAT_MODULE)
	/* Previously seen (loopback)?  Ignore.  Do this before
	   fragment check. */
	//对于loopback这样的环回设备,连接跟踪模块在LOCAL_OUT点已经处理过了该skb   
	if (skb->nfct && !nf_ct_is_template((struct nf_conn *)skb->nfct))
		return NF_ACCEPT;
#endif
#endif
	/* Gather fragments. */
	//输入的skb是一个IP报文的分片
	if (ip_is_fragment(ip_hdr(skb))) {
		//尝试将skb和已缓存skb进行IP片段重组
		enum ip_defrag_users user = nf_ct_defrag_user(hooknum, skb);
		if (nf_ct_ipv4_gather_frags(skb, user))
			//重组失败,给Netfilter框架返回NF_STOLEN,表示该skb被钩子内部消耗了,
			//无需协议族进一步处理,这会导致Netfilter框架结束整个HOOK点的遍历
			return NF_STOLEN;
	}
	//skb是一个完整的IP报文或者重组成功,返回NF_ACCEPT
	return NF_ACCEPT;
}

2.1 nf_ct_ipv4_gather_frags()

调用 ip_defrag() 函数实现分段重组的

/* Returns new sk_buff, or NULL */
static int nf_ct_ipv4_gather_frags(struct sk_buff *skb, u_int32_t user)
{
	int err;

	skb_orphan(skb);

	local_bh_disable();
	err = ip_defrag(skb, user);
	local_bh_enable();

	if (!err)
		ip_send_check(ip_hdr(skb));

	return err;
}

3 ipv4_conntrack_in()

该钩子函数是连接跟踪子系统在 PRE_ROUTING 处注册的第二个钩子,它的优先级比 ipv4_conntrack_defrag() 低,它将完成AF_INET协议族数据包在连接跟踪子系统入口处要做的所有事情。该函数只是简单的调用nf_conntrack_in()实现创建连接跟踪项。

  • ①当是一个新的数据包时,则为该数据包创建对应的连接跟踪项,创建成果后进入 ③
  • ②当已经为传递过来的数据包创建了连接跟踪项,则进入c
  • ③更新连接跟踪项的状态
  • ④为数据包的nfct指针赋值
static unsigned int ipv4_conntrack_in(unsigned int hooknum, struct sk_buff *skb,
		const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *))
{
	//调用连接跟踪子系统框架的输入处理接口
	return nf_conntrack_in(PF_INET, hooknum, skb);
}

3.1 nf_conntrack_in()【核心中的核心】

这里要做的事情简单来说就是对该数据包进行跟踪,使其属于某个“连接”。参见xxxxx。

4 ipv4_conntrack_local()

该钩子函数是连接跟踪子系统在LOCAL_OUT处注册的第二个钩子。

static unsigned int ipv4_conntrack_local(unsigned int hooknum, struct sk_buff *skb,
		const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *))
{
	//skb长度比IP首部还小,这应该属于RAW socket,不跟踪这种数据包
	if (skb->len < sizeof(struct iphdr) ||
	    ip_hdrlen(skb) < sizeof(struct iphdr)) {
		if (net_ratelimit())
			printk("ipt_hook: happy cracking.\n");
		return NF_ACCEPT;
	}
	//同样调用连接跟踪子系统框架的入口处理函数对数据包进行跟踪
	return nf_conntrack_in(PF_INET, hooknum, skb);
}

4.1 nf_conntrack_in()

同 3.1 小结一样

5 ipv4_conntrack_help()/ipv4_helper()

该钩子函数是连接跟踪子系统在出口处注册的第一个钩子函数,两个出口 LOCAL_OUT 和 POST_ROUTING 都注册了该钩子。这个函数还是比较简单的,首先根据传入的数据包;然后找到该数据包对应的数据连接跟踪项;接着若该数据连接跟踪项的 helper 指针不为空,则调用该helper结构对应的help函数,实现创建期望连接或者相关协议的ALG功能(即对应用层协议中的数据部分如果有ip地址,且该数据连接跟踪项对应的数据流有开启NAT机制时,则将应用层协议中的数据部分的ip地址也进行NAT操作)。

static unsigned int ipv4_conntrack_help(unsigned int hooknum, struct sk_buff *skb,
		const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *))
{
	struct nf_conn *ct;
	enum ip_conntrack_info ctinfo;
	const struct nf_conn_help *help;
	const struct nf_conntrack_helper *helper;

	//在入口处,skb应该已经找到了它的连接跟踪信息块
	ct = nf_ct_get(skb, &ctinfo);

	//没有ct,说明该skb没有被跟踪或者不属于任何一个连接。或者skb属于一个期望连接。
	//这两种情况,不会对该skb执行help()回调
	if (!ct || ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY)
		return NF_ACCEPT;

	//获取skb的help信息,该信息在连接跟踪的入口处就已经指定了(如果有的话)
	help = nfct_help(ct);
	if (!help)
		return NF_ACCEPT;

	/* rcu_read_lock()ed by nf_hook_slow */
	helper = rcu_dereference(help->helper);
	if (!helper)
		return NF_ACCEPT;

	//执行help()回调,回调函数的返回值将作为给Netfilter框架的返回值
    //该3函数用来创建期望连接或者对数据包的应用层携带的ip地址信息进行nat操作
	return helper->help(skb, skb_network_offset(skb) + ip_hdrlen(skb), ct, ctinfo);
}

从实现上可以看出,help()回调是可选的,可以有也可以没有。关于连接跟踪子系统给的helper机制,参见xxxxx。

6 连接确认 ipv4_confirm()

该钩子函数是连接跟踪子系统在出口处注册的第二个钩子函数,两个出口LOCAL_OUT和POST_ROUTING都注册了该钩子。进入到这个函数里的数据包,说明其连接跟踪项均已创建,此时需要做的就是对于还没有进行确认的连接跟踪项,实现确认操作,该函数直接调用nf_conntrack_confirm() 实现。

static unsigned int ipv4_confirm(unsigned int hooknum, struct sk_buff *skb,
		const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *))
{
	//直接调用连接跟踪子系统框架的出口确认函数处理
	return nf_conntrack_confirm(skb);
}

6.1 nf_conntrack_confirm()【核心】

nf_conntrack_confirm()的具体分析参见xxx。简单来说,这里要做的事情就是将“新连接”的连接跟踪信息,即是将该连接跟踪项对应的原始方向与应答方向的nf_conntrack_tuple_hash 变量插入到网络命令空间的net->ct.hash链表中,之所以需要这一步是因为新连接的识别是在PRE_ROUTING和LOCAL_OUT处,并且这两处的钩子函数优先级较高,此时数据包还没有过防火墙,而确认钩子函数以较低优先级位于防火墙之后,到了这里,说明数据包不会被丢弃了,此时再将其连接加入全局的连接跟踪哈希表是最合适的。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值