Netfilter 是如何工作的(六):连接跟踪信息的入口创建(in)和出口确认(confirm)

Articles (gitee.io)

IPtables-朱双印博客 (zsythink.net)

在 Netfilter 是如何工作的(五) 中连接跟踪信息使用的创建-确认机制的

Netfilter在报文进入系统的入口处,将连接跟踪信息记录在报文上,在出口进行confirm.确认后的连接信息

本文以一个本机上送过程中的TCP/IPv4SYN握手报文为例,详细分析连接跟踪机制的工作流程。

入口 & 出口

由于是本机上送流程,因此SYN报文的入口是 PRE_ROUTING,而出口则是 LOCAL_IN。Netfilter 在初始化时,会在这两个 HOOK 点注册连接跟踪相关的处理函数。

static struct nf_hook_ops ipv4_conntrack_ops[] = {
	{
		.hook		= ipv4_conntrack_in,           // 入口回调函数
		.pf		    = NFPROTO_IPV4,
		.hooknum	= NF_INET_PRE_ROUTING,         //  PRE_ROUTING HOOK 点
		.priority	= NF_IP_PRI_CONNTRACK,         //  优先级
	},
	......
	{
		.hook		= ipv4_confirm,                // 出口的回调函数
		.pf		    = NFPROTO_IPV4,
		.hooknum	= NF_INET_LOCAL_IN,            // LOCAL_IN HOOK 点 
		.priority	= NF_IP_PRI_CONNTRACK_CONFIRM, // 优先级
	},
}
入口

SYN 报文进入连接跟踪的入口是 ipv4_conntrack_in(),然后调用nf_conntrack_in, 后面这个函数较长,因此我们这里分段来看

ipv4_conntrack_in
    |
    |-- nf_conntrack_in

nf_conntrack_in(struct net *net, u_int8_t pf, unsigned int hooknum, struct sk_buff *skb)
{
    // ......
	if (skb->nfct) {
		/* Previously seen (loopback or untracked)?  Ignore. */
		tmpl = (struct nf_conn *)skb->nfct;
		if (!nf_ct_is_template(tmpl)) {
			NF_CT_STAT_INC_ATOMIC(net, ignore);
			return NF_ACCEPT;
		}
		skb->nfct = NULL;
	}
    // ......
}

首先是检查 skb 上是否已经关联了连接跟踪信息。这里不是入口吗?为什么 skb 上会由连接跟踪信息 ?! 注释写了,如果这个包是从 loopback 口收到的话,这时它就有可能已经关联的链接跟踪信息了,这时就会把已关联的连接跟踪信息作为模板(tmpl)。而在我们的情境中,SYN 报文从外部网卡收到,所以显然这里 skb 是不会有连接跟踪信息的。

接着往下看:

	l3proto = __nf_ct_l3proto_find(pf);
	ret = l3proto->get_l4proto(skb, skb_network_offset(skb),
				   &dataoff, &protonum);
    l4proto = __nf_ct_l4proto_find(pf, protonum);                  

这一段代码片段完成了两件事

  • 将协议族 pf 转换得到 L3 协议对应的函数操作集(operations),在我们的例子中,入参pf = AF_INET,也就是 IP 协议,因此这里会得到l3proto = nf_conntrack_l3proto_ipv4
  • 进一步解析报文 L4 对应的协议号,这里会调用ipv4_get_l4proto,在我们的例子中,会得到 TCP 对应的协议号,protonum = 6,进而得到l4proto = nf_conntrack_l4proto_tcp4

接下来就是入口函数的核心流程resolve_normal_ct(), 正常情况下,这个函数会返回连接跟踪信息的指针ct和简要信息ctinfo,并将它们分别设置到skb->nfcfskb->ctinfo

    struct nf_conn *ct;
    enum ip_conntrack_info ctinfo;
    int set_reply = 0;
    
	ct = resolve_normal_ct(net, tmpl, skb, dataoff, pf, protonum,
			       l3proto, l4proto, &set_reply, &ctinfo);

下面将 resolve_normal_ct 展开

第一步调用nf_ct_get_tuple, 将报文的五元组信息填充到struct nf_conntrack_tuple结构的变量中

static inline struct nf_conn *
resolve_normal_ct(struct net *net, struct nf_conn *tmpl,
		  struct sk_buff *skb,
		  unsigned int dataoff,
		  u_int16_t l3num,
		  u_int8_t protonum,
		  struct nf_conntrack_l3proto *l3proto,
		  struct nf_conntrack_l4proto *l4proto,
		  int *set_reply,
		  enum ip_conntrack_info *ctinfo)
{          
    const struct nf_conntrack_zone *zone;
	struct nf_conntrack_tuple tuple;
	struct nf_conntrack_tuple_hash *h;
    
    //  @tuple 是出参, 对tcp来说是 tcp_pkt_to_tuple, 只是填了dport和sport
	if (!nf_ct_get_tuple(skb, skb_network_offset(skb),
			     dataoff, l3num, protonum, net, &tuple, l3proto,
			     l4proto)) {
		pr_debug("resolve_normal_ct: Can't get tuple\n");
		return NULL;
	}
    // ...... 
}

第二步,计算tuple的哈希值,然后根据此哈希值查找连接跟踪信息表中是否已存在这个哈希值的记录,将其值赋予h

    ......
    zone = nf_ct_zone_tmpl(tmpl, skb, &tmp);
	hash = hash_conntrack_raw(&tuple);                    // 计算hash
	h = __nf_conntrack_find_get(net, zone, &tuple, hash); // 查找该tuple的元组信息是否已经存在

在我们的例子中,假设原本没有这样的记录,于是这里会使用init_conntrack创建一条新的连接跟踪信息

	if (!h) {
		h = init_conntrack(net, tmpl, &tuple, l3proto, l4proto,
				   skb, dataoff, hash);

最后,将连接跟踪信息设置到报文skb上。特别注意,这里只会将连接信息保存在skb上,并不会将这条信息插入到连接跟踪信息表中,插入到表中这是出口 confirm 阶段的工作。

    ct = nf_ct_tuplehash_to_ctrack(h);
	......
	} else {
		......
		} else {
			pr_debug("nf_conntrack_in: new packet for %p\n", ct);
			*ctinfo = IP_CT_NEW;
		}
		*set_reply = 0;
	}
	skb->nfct = &ct->ct_general;
	skb->nfctinfo = *ctinfo;
出口

出口处,Netfilter 将连接跟踪信息设置到报文 skb 上,这样在出口的地方就可以将其取出,再插入到连接信息的哈希表中,完成确认

ipv4_confirm
    |
    |-- nf_conntrack_confirm
        |
        |-- __nf_conntrack_confirm
        
/* Confirm a connection given skb; places it in hash table */
int
__nf_conntrack_confirm(struct sk_buff *skb)
{
	struct nf_conn *ct;
	enum ip_conntrack_info ctinfo;
    // ......
	ct = nf_ct_get(skb, &ctinfo);
    // ......
	__nf_conntrack_hash_insert(ct, hash, reply_hash);
    // ......
	return NF_ACCEPT;
}        
相关推荐
长安11082 小时前
前后端、网关、协议方面补充
网络
舞动CPU4 小时前
linux c/c++最高效的计时方法
linux·运维·服务器
皮锤打乌龟4 小时前
(干货)Jenkins使用kubernetes插件连接k8s的认证方式
运维·kubernetes·jenkins
钰@5 小时前
小程序开发者工具的network选项卡中有某域名的接口请求,但是在charles中抓不到该接口
运维·服务器·小程序
wanhengwangluo5 小时前
云服务器和物理服务器的区别有哪些?
运维·服务器
hzyyyyyyyu5 小时前
隧道技术-tcp封装icmp出网
网络·网络协议·tcp/ip
南猿北者5 小时前
docker Network(网络)
网络·docker·容器
秦jh_6 小时前
【Linux】多线程(概念,控制)
linux·运维·前端
yaosheng_VALVE6 小时前
稀硫酸介质中 V 型球阀的材质选择与选型要点-耀圣
运维·spring cloud·自动化·intellij-idea·材质·1024程序员节
Hacker_Nightrain6 小时前
网络安全CTF比赛规则
网络·安全·web安全