netfilter连接跟踪(conntrack)详述

1 连接跟踪来龙去脉

1.1 什么是连接跟踪

连接跟踪顾名思义是对网络连接跟踪,如果将网络比作 高速路 ,连接跟踪就像是高速路里的路上的检查站、摄像头一起的组合,可以记录下你在什么地方进的高速、什么地方下的高速、是谁开了一个什么车等信息。类似连接跟踪会记录下网络中的连接信息,如源ip、目的ip、目前报文的状态、是不是和其他网络连接有关联关系等信息,所以连接跟踪是对报文进行检查并做 记录标记的一个工具

你可能听说过会话机制,连接跟踪其实就是实现了一种会话机制,当然这里的会话和连接和tcp的会话、连接是有区别的,tcp的会话和连接是协议的一部分,这里的会话和连接是一种标记方法。

1.2 为什么需要连接跟踪

没有连接跟踪有很多问题是不好解决的:

  1. conntrack是属于netfilter的一部分,netfilter主要功能就是对数据包的过滤及更改,没有连接跟踪只能对单个数据包进行过滤,比如之前的 无状态防火墙 ,有了连接跟踪后,则可以把报文的生命周期延长了,从第一个包到最后一个包都可以关联到一起,就从对 “点” 的防护做到了对 “线” 的防护,就是后来的状态防火墙。
  2. 有些协议如ftp、sip、tftp等有控制连接和数据连接,两个连接是有从属关系的,如果需要对此类流量进行过滤,没有连接跟踪是不好搞的。
  3. 最主要的就是NAT方案,可以解决ipv4地址不够,内网外网连接问题,如果要实现NAT,就需要让内核跟踪会话,设置了CONFIG_NF_CONNTRACK_IPV4就可以构建ipv4 NAT,设置了CONFIG_NF_CONNTRACK_IPV6就可以构建ipv6 NAT.

1.3 连接跟踪的应用场景

应用场景:NAT、状态防火墙、四层负载均衡

状态防火墙
无状态、有状态:最简单的包过滤防火墙是无状态的,而更复杂的防火墙是有状态的
无状态的包过滤防火墙:单独处理每一个数据报
有状态的防火墙能够:通过关联已经或者即将到达的数据包来推断流或者数据报的信息,即那些属于同一个传输关联 (transport association)的数据包或构成同一个IP数据报的IP分片

四层负载均衡:可以根据四层信息(例如 src/dst ip, src/dst port, proto)做流量分发,DNAT实现。
负载均衡说明
通过负载均衡c1、c2由s1响应服务,c3由s2响应服务。

2 内核中的连接跟踪

2.1 netfilter连接跟踪框架

内核中连接跟踪是在netfilter框架中建立的,netfilter的框架图(来源:wikipedia)如下:
在这里插入图片描述
这张图乍一看比较乱,但是这张图非常好的说明了数据流在内核中的过程。
在图中的灰色框标记了conntrack的位置,这个是连接跟踪的起始点,其实在netfilter的所有HOOK点都可以更改**流(flow)**跟踪的信息。

有一个小问题,iptables规则会影响tcpdump抓包吗?
答案是不会,tcpdump抓包是libpcap实现的,libpcap是用bpf(Berkeley Packet Filter)实现的,bpf位于netfilter前,所以不会影响抓包。
在这里插入图片描述

如上图所示,更加清楚的标记了连接跟踪的起始位置,Netfilter 会在四个 Hook 点对包进行跟踪:

PRE_ROUTING 和 LOCAL_OUT:调用 nf_conntrack_in() 开始连接跟踪, 正常情况下会创建一条新连接记录,然后将 conntrack entry 放到 unconfirmed list。
为什么是这两个 hook 点呢?因为它们都是新连接的第一个包最先达到的地方,
PRE_ROUTING 是外部主动和本机建连时包最先到达的地方
LOCAL_OUT 是本机主动和外部建连时包最先到达的地方

POST_ROUTING 和 LOCAL_IN:调用 nf_conntrack_confirm() 将 nf_conntrack_in() 创建的连接移到 confirmed list。

同样要问,为什么在这两个 hook 点呢?因为如果新连接的第一个包没有被丢弃,那这 是它们离开 netfilter 之前的最后 hook 点:

外部主动和本机建连的包,如果在中间处理中没有被丢弃,LOCAL_IN 是其被送到应用之前最后的 hook 点
本机主动和外部建连的包,如果在中间处理中没有被丢弃,POST_ROUTING 是其离开主机时的最后 hook 点

确认机制的原因
确认机制可以确保创建了跟踪表的报文真实被接受或转发了,而没有在netfilter中被丢弃。若没有确认机制,包在被netfilter丢弃的情况下,会存在半跟踪状态的数据,浪费了内存和性能。

2.2 重要函数和结构体

重要结构体
struct nf_hook_ops {}: 在HOOK点上注册的连接跟踪信息,通过nf_register_hooks()注册
struct nf_conntrack_tuple {}: 连接跟踪的基本元素,表示特定方向的流。
struct nf_conn {}:连接跟踪条目,定义一个 flow。
在nf_conn中有重要成员:如ct_general、status、master、tuplehash、timeout等。

struct nf_conn {
	struct nf_conntrack ct_general;
	spinlock_t	lock;
	u16		cpu;
	/* XXX should I move this to the tail ? - Y.K */
	/* These are my tuples; original and reply */
	struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];
	/* Have we seen traffic both ways yet? (bitset) */
	unsigned long status;
	/* Timer function; drops refcnt when it goes off. */
	struct timer_list timeout;
	possible_net_t ct_net;
	/* all members below initialized via memset */
	u8 __nfct_init_offset[0];
	/* If we were expected by an expectation, this will be it */
	struct nf_conn *master;
#if defined(CONFIG_NF_CONNTRACK_MARK)
	u_int32_t mark;
#endif
#ifdef CONFIG_NF_CONNTRACK_SECMARK
	u_int32_t secmark;
#endif
	/* Extensions */
	struct nf_ct_ext *ext;
	/* Storage reserved for other modules, must be the last member */
	union nf_conntrack_proto proto;
};

重要函数
hash_conntrack_raw():根据 tuple 计算出一个 32 位的哈希值(hash key)。
nf_conntrack_in():连接跟踪模块的核心,包进入连接跟踪的地方。在此函数中包含下边的步骤:
resolve_normal_ct() -> nf_ct_timeout_lookup()
在resolve_normal_ct() 中会计算元组的散列值,进行匹配,没有就创建nf_conntrack_tuple_hash,将其加入未确认tuplehash列表中,已经创建则判断状态是否超时。

nf_conntrack_confirm():确认前面通过 nf_conntrack_in() 创建的新连接(是否被丢弃),将元组从未确认tuplehash列表中删除。
nf_ct_get(skb, ctinfo):获取连接跟踪数据,没有建立返回null

具体细节可以在内核代码中查看
代码路径:/net/netfilter 一个在线linux内核代码的网站

2.3 连接跟踪流程概览

在这里插入图片描述
可以在流程中看到ipv4_helper(),在这里可以调用辅助连接跟踪,实现对ftp、sip等协议的跟踪,具体见下文。

2.4 连接跟踪ftp实例

ftp连接跟踪的实现依赖于netfilter中的辅助方法期望连接
辅助方法
对于ftp、sip这种数据流和控制流不同的协议,netfilter需要知道哪些流是彼此相关的,就是确定主连接和从属连接,于是在之前的基础上加入了辅助方法,可以找到关联的流。在具体实现中需要对主连接的包进行做关键字匹配,从而找到从属连接信息。

期望连接
就是在匹配关键字确定了从属连接信息后,提前建立的连接跟踪信息(就是连接未到跟踪先行,避免从属连接被当成普通连接处理)
内核知道两条连接的关系后,netfilter就可以在主连接上生成适用于相关连接的规则,如:

#接受跟踪状态为RELATED的数据包
iptables -A INPUT -m conntrack --ctstate RELATED -j ACCEPT

FTP的辅助连接跟踪是在net/netfilter/nf_conntrack_ftp.c实现的,核心函数是help() ,它最终作为struct nf_conntrack_helper ftp的一部分,通过nf_conntrack_ftp_init(void)nf_conntrack_helper_register() 注册到了连接跟踪方法散列表nf_ct_helper_hash 中,被上文提到的 ipv4_helper() 调用。

static int help(struct sk_buff *skb, unsigned int protoff, struct nf_conn *ct,
		enum ip_conntrack_info ctinfo)
{
	...
	//上面介绍的序号缓存信息
	struct nf_ct_ftp_master *ct_ftp_info = &nfct_help(ct)->help.ct_ftp_info;
	struct nf_conntrack_expect *exp;
	...
	//略去部分代码
	...
	//从连接跟踪信息块中找到skb传输方向上的地址信息
	cmd.l3num = ct->tuplehash[dir].tuple.src.l3num;
	memcpy(cmd.u3.all, &ct->tuplehash[dir].tuple.src.u3.all, sizeof(cmd.u3.all));
	
	//搜索数据包的内容,从中寻找是否有特定的关键字(PORT和PASV信息)
	/********************关键部分****************/
	for (i = 0; i < ARRAY_SIZE(search[dir]); i++) {
		found = find_pattern(fb_ptr, datalen, search[dir][i].pattern,
					search[dir][i].plen, search[dir][i].skip, search[dir][i].term,
				    &matchoff, &matchlen, &cmd, search[dir][i].getnum);
		if (found)
			break;
	}
	
	//虽然连接跟踪本意上不应该丢包,但是遇到这种部分匹配的异常情况,
	//说明传输确实是有问题的,必须丢包才能更准确的跟踪
	if (found == -1) {
		ret = NF_DROP;
		goto out;
	} else if (found == 0) {
		//没有包含关注的关键字,尝试更新序列号缓存信息后结束		
		ret = NF_ACCEPT;
		goto out_update_nl;
	}
	//找到了我们关心的信息,说明即将要建立一个数据连接(即期望连接),
	//所以分配一个期望并对其进行初始化,然后将其加入期望连接表中,这样
	//等期望连接的数据包到达时就可以匹配到了
	exp = nf_ct_expect_alloc(ct);
	if (exp == NULL) {
		ret = NF_DROP;
		goto out;
	}
	//计算期望连接的tuple信息
	...
	/*略去部分代码*/
	...
	//初始化期望连接结构
	nf_ct_expect_init(exp, cmd.l3num, &ct->tuplehash[!dir].tuple.src.u3, daddr,
			  IPPROTO_TCP, NULL, &cmd.u.tcp.port);

	/* Now, NAT might want to mangle the packet, and register the
	 * (possibly changed) expectation itself. */
	//如果NAT会处理该数据包,则交给NAT处理,否则将其加入到期望连接跟踪表中
	nf_nat_ftp = rcu_dereference(nf_nat_ftp_hook);
	if (nf_nat_ftp && ct->status & IPS_NAT_MASK)
		ret = nf_nat_ftp(skb, ctinfo, search[dir][i].ftptype, matchoff, matchlen, exp);
	else {
		/* Can't expect this?  Best to drop packet now. */
		if (nf_ct_expect_related(exp) != 0)
			ret = NF_DROP;
		else
			ret = NF_ACCEPT;
	}
out_put_expect:
	nf_ct_expect_put(exp);
out_update_nl:
	//只有当前数据包是以'\n'结尾时才更新序号缓存数组
	if (ends_in_nl)
		update_nl_seq(seq, ct_ftp_info, dir, skb);
 out:
	spin_unlock_bh(&nf_ftp_lock);
	return ret;
}

还有一个关键的结构体,用来做关键字匹配

static struct ftp_search {
	const char *pattern;
	size_t plen;
	char skip;
	char term;
	enum nf_ct_ftp_type ftptype;
	int (*getnum)(const char *, size_t, struct nf_conntrack_man *, char, unsigned int *);
} search[IP_CT_DIR_MAX][2] = {
	[IP_CT_DIR_ORIGINAL] = {
		{
			.pattern	= "PORT",
			.plen		= sizeof("PORT") - 1,
			.skip		= ' ',
			.term		= '\r',
			.ftptype	= NF_CT_FTP_PORT,
			.getnum		= try_rfc959,
		},
		{
			.pattern	= "EPRT",
			.plen		= sizeof("EPRT") - 1,
			.skip		= ' ',
			.term		= '\r',
			.ftptype	= NF_CT_FTP_EPRT,
			.getnum		= try_eprt,
		},
	},
	[IP_CT_DIR_REPLY] = {
		{
			.pattern	= "227 ",
			.plen		= sizeof("227 ") - 1,
			.ftptype	= NF_CT_FTP_PASV,
			.getnum		= try_rfc1123,
		},
		{
			.pattern	= "229 ",
			.plen		= sizeof("229 ") - 1,
			.skip		= '(',
			.term		= ')',
			.ftptype	= NF_CT_FTP_EPSV,
			.getnum		= try_epsv_response,
		},
	},
};

现有一ftp报文如下:
在这里插入图片描述
具体匹配过程是,ftp报文进入netfilter,通过HOOK点的help函数处理时,id为35的报文通过 .pattern = 227 成功匹配,应为此报文的reponse code 为227,然后调用解析函数try_rfc1123() 具体如下:

static int try_rfc1123(const char *data, size_t dlen,
		       struct nf_conntrack_man *cmd, char term,
		       unsigned int *offset)
{
	int i;
	for (i = 0; i < dlen; i++)
		if (isdigit(data[i]))
			break;
	if (i == dlen)
		return 0;
	*offset += i;
	return try_rfc959(data + i, dlen - i, cmd, 0, offset);
}

然后调用try_rfc959() 解析,函数如下:

static int try_rfc959(const char *data, size_t dlen,
		      struct nf_conntrack_man *cmd, char term,
		      unsigned int *offset)
{
	int length;
	u_int32_t array[6];
	length = try_number(data, dlen, array, 6, ',', term);
	if (length == 0)
		return 0;
	cmd->u3.ip =  htonl((array[0] << 24) | (array[1] << 16) |
				    (array[2] << 8) | array[3]);
	cmd->u.tcp.port = htons((array[4] << 8) | array[5]);
	return length;
}

try_number() 函数是将字符转为数字,因为ftp的data传递的均为字符,所以查找端口需要转换为int
报文的端口信息是 100,85 ,100<<8 | 85 = 25685 即为数据传输协程的端口号。由ip和端口可以创建期望连接 ,当数据流到来时,在调用方法init_conntrack() 创建连接时,会检查是否有期望,发现期望的话,就将IPS_EXPECTED_BIT置位。

还有一个问题,就是内核如何知道哪些连接需要使用ftp辅助方法?
辅助方法会侦听预先定义ftp的端口,发现此端口的连接,则会调用辅助方法。ftp端口可以设置:

modprobe nf_conntrack_ftp ports=2121,2120,2310
或者使用CT目标
iptables -A PREROUTING -t raw -p tcp --dport 2121 -j CT --helper ftp

3 扩展连接跟踪

在某些情况下,比如为了增加性能,做自定义功能时,需要使用拓展连接跟踪,有些拓展需要由sysctrls开启,或者是用iptables规则触发,可以根据mark标志位写iptables规则来触发拓展连接跟踪的处理。步骤如下:
首先要定义自己的拓展id
位于net/netfilter/nf_conntrack_extend.h中

enum nf_ct_ext_id {
	NF_CT_EXT_HELPER,
	NF_CT_EXT_NAT,
	NF_CT_EXT_SEQADJ,
	NF_CT_EXT_ACCT,
	NF_CT_EXT_ECACHE,
	NF_CT_EXT_ZONE,
	NF_CT_EXT_TSTAMP,
	NF_CT_EXT_TIMEOUT,
	NF_CT_EXT_LABELS,
	NF_CT_EXT_SYNPROXY,
	/*在此添加自己的拓展id如下*/
	NF_CT_EXT_FASTPASS,

	NF_CT_EXT_NUM,
};

然后需要定义自己的nf_ct_ext_type对象

static struct nf_ct_ext_type dpi_extend __read_mostly = {
	.len    = sizeof(struct fastpass_extend_info),
	.align  = __alignof__(struct fastpass_extend_info),
	.id     = NF_CT_EXT_FASTPASS,	
	.destroy = NULL,
	.flags   = NF_CT_EXT_F_PREALLOC,	
};

其中,fastpass_extend_info结构如下:

struct dpi_extend_info{
	unsigned int fast_status;
	unsigned int (*fastpass_process)(struct sk_buff *skb, const struct xt_action_param *par);
};

此结构可以调用方法nf_ct_ext_add(ct,NF_CT_EXT_FASTPASS, GFP_ATOMIC) 写到nf_conn的struct nf_ct_ext *ext中,可以通过**nf_ct_ext_find()**查找是否有nf_ct_ext的存在。

最后通过
nf_ct_extend_register(&dpi_extend); 注册拓展连接跟踪
nf_ct_extend_unregister(&dpi_extend); 注销拓展连接跟踪

具体可以参考/net/netfilter下的nf_conntrack_timestamp.c等。

4 工具

常用命令

查看nf_conntrack表当前连接数    
cat /proc/sys/net/netfilter/nf_conntrack_count       

查看nf_conntrack表最大连接数    
cat /proc/sys/net/netfilter/nf_conntrack_max    

通过dmesg可以查看nf_conntrack的状况:
dmesg |grep nf_conntrack

查看存储conntrack条目的哈希表大小,此为只读文件
cat /proc/sys/net/netfilter/nf_conntrack_buckets

查看nf_conntrack的TCP连接记录时间
cat /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established

通过内核参数查看命令,查看所有参数配置
sysctl -a | grep nf_conntrack

通过conntrack命令行工具查看conntrack的内容
yum install -y conntrack  
conntrack -L  

nf_conntrack相关内核参数和解释

5 总结

连接跟踪的作用有很多,当然有时也会有副作用,比如会降低系统的性能,当对性能要求较高,且确定不需要此功能时可以将其关掉。

$ sysctl -a | grep nf_conntrack
net.netfilter.nf_conntrack_acct = 0
net.netfilter.nf_conntrack_buckets = 262144                 # hashsize = nf_conntrack_max/nf_conntrack_buckets
net.netfilter.nf_conntrack_checksum = 1
net.netfilter.nf_conntrack_count = 2148
... # DCCP options
net.netfilter.nf_conntrack_events = 1
net.netfilter.nf_conntrack_expect_max = 1024
... # IPv6 options
net.netfilter.nf_conntrack_generic_timeout = 600
net.netfilter.nf_conntrack_helper = 0
net.netfilter.nf_conntrack_icmp_timeout = 30
net.netfilter.nf_conntrack_log_invalid = 0
net.netfilter.nf_conntrack_max = 1048576                    # conntrack table size
... # SCTP options
net.netfilter.nf_conntrack_tcp_be_liberal = 0
net.netfilter.nf_conntrack_tcp_loose = 1
net.netfilter.nf_conntrack_tcp_max_retrans = 3
net.netfilter.nf_conntrack_tcp_timeout_close = 10
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 60
net.netfilter.nf_conntrack_tcp_timeout_established = 21600
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 120
net.netfilter.nf_conntrack_tcp_timeout_last_ack = 30
net.netfilter.nf_conntrack_tcp_timeout_max_retrans = 300
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 60
net.netfilter.nf_conntrack_tcp_timeout_syn_sent = 120
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120
net.netfilter.nf_conntrack_tcp_timeout_unacknowledged = 300
net.netfilter.nf_conntrack_timestamp = 0
net.netfilter.nf_conntrack_udp_timeout = 30
net.netfilter.nf_conntrack_udp_timeout_stream = 180

参考:
http://arthurchiao.art/blog/conntrack-design-and-implementation-zh/
https://blog.csdn.net/whatday/article/details/105251137
https://blog.csdn.net/qq_41453285/article/details/98944149
https://blog.csdn.net/xiaoyu_750516366/article/details/89076015
精通linux内核网络
https://my.oschina.net/u/4269090/blog/3328896

  • 8
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在Linux系统中,Conntrack是通过Netfilter框架实现的,Netfilter是Linux内核中的一个网络包过滤架构,可以用于实现网络包的过滤、修改、重定向等操作。Conntrack模块通过Netfilter框架对网络连接进行跟踪和管理。 具体来说,当一个网络连接被创建时,Conntrack模块会在内核中创建一个连接跟踪条目(Connection Tracking Entry),并将其存储在一个连接跟踪表中。该表中包含了所有被跟踪连接的信息,包括源IP地址、目标IP地址、端口号等信息,同时还包括连接状态、连接计时器等辅助信息。 当一个网络数据包到达Linux内核时,Netfilter框架会将其传递给Conntrack模块进行处理。Conntrack模块会根据数据包的源IP地址、目标IP地址、端口号等信息,查找连接跟踪表中对应的连接跟踪条目,然后更新该条目的状态、计时器等信息。如果该数据包无法匹配到任何一个连接跟踪条目,那么该数据包将被认为是无效的,从而被丢弃或者被拒绝。 通过Conntrack模块,可以实现一些网络连接管理的功能,例如: 1. 连接管理:可以通过Conntrack模块对网络连接进行管理,包括创建、删除、修改等操作。 2. 连接跟踪:可以对网络连接进行跟踪,记录连接的状态、流量等信息。 3. 连接控制:可以通过Conntrack模块对网络连接进行控制,例如限制连接数、限制连接速率等。 4. 安全检查:可以通过Conntrack模块对网络连接进行安全检查,例如检测恶意连接、防止DDoS攻击等。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值