对Netfilter中conntrack机制的理解
0x 01 状态防火墙和链接追踪系统
认识和熟悉过iptables之后,更加感叹netfilter的磅礴和浩瀚。在netfilter体系中,状态跟踪机制(conntrack)是重要的一部分。它是基于Linux系统的stateful防火墙的基础,也是NAT完成对相关包进行转换的手段。本文尝试对conntrack的机制进行分析和理解。
在基于header信息(IP,端口等)进行过滤的包过滤防火墙发展多年之后,对防火墙的需求也再逐渐丰富,stateless防火墙对探测跟踪以及DoS的防护显得力不从心。当然从历史时间来看,包过滤防火墙是符合当时发展需要,效率也更高,对于更高层的应用当时都很少,自然也不需要太关注防护。
conntrack将信息存在内存结构中,包括IP,端口,协议类型,状态以及超时时间。
而且conntrack不仅可以对TCP这种有状态的会话进行状态跟踪,还可以对UDP进行状态跟踪。
conntrack本身并不会对包进行过滤,而是提供一种基于状态和关系的过滤依据。
0x 02 五种状态
conntrack定义了5种连接状态,分别如下:
NEW:NEW匹配连接的第一个包。意思就是,iptables从连接跟踪表中查到此包是某连接的第一个包。判断此包是某连接的第一个包是依据conntrack当前”只看到一个方向数据包”( [UNREPLIED] ),不关联特定协议,因此NEW并不单指tcp连接的SYN包。
ESTABLISHED ESTABLISHED匹配连接的响应包及后续的包。意思是,iptables从连接跟踪表中查到此包是属于一个已经收到响应的连接(即没有 [UNREPLIED] 字段)。因此在iptables状态中,只要发送并接到响应,连接就认为是ESTABLISHED的了。这个特点使iptables可以控制由谁发起的连接才可以通过,比如A与B通信,A发给B数据包属于NEW状态,B回复给A的数据包就变为ESTABLISHED状态。ICMP的错误和重定向等信息包也被看作是ESTABLISHED,只要它们是我们所发出的信息的应答。
RELATED RELATED匹配那些属于RELATED连接的包,这句话说了跟没说一样。RELATED状态有点复杂,当一个连接与另一个已经是ESTABLISHED的连接有关时,这个连接就被认为是RELATED。这意味着,一个连接要想成为RELATED,必须首先有一个已经是ESTABLISHED的连接存在。这个ESTABLISHED连接再产生一个主连接之外的新连接,这个新连接就是RELATED状态了,当然首先conntrack模块要能”读懂”它是RELATED。拿ftp来说,FTP数据传输连接就是RELATED与先前已创建的FTP控制连接,还有通过IRC的DCC连接。有了RELATED这个状态,ICMP错误消息、FTP传输、DCC等才能穿过防火墙正常工作。有些依赖此机制的TCP协议和UDP协议非常复杂,他们的连接被封装在其它的TCP或UDP包的数据部分(可以了解下overlay/vxlan/gre),这使得conntrack需要借助其它辅助模块才能正确”读懂”这些复杂数据包,比如 nf_conntrack_ftp 这个辅助模块。
INVALID INVALID匹配那些无法识别或没有任何状态的数据包。这可能是由于系统内存不足或收到不属于任何已知连接的ICMP错误消息。一般情况下我们应该DROP此类状态的包。
UNTRACKED UNTRACKED状态比较简单,它匹配那些带有 NOTRACK 标签的数据包。需要注意的一点是,如果你在 raw 表中对某些数据包设置有 NOTRACK 标签,那上面的4种状态将无法匹配这样的数据包,因此你需要单独考虑 NOTRACK 包的放行规则。
0x 03基础结构
conntrack功能从2.6.15的内核版本后,就开始支持状态跟踪。该功能依赖于nf_conntrack模块模块,而该模块基于ip_conn_track模块。
nf_conntrack_ipv4在netfilter的hook点中共注册了4个回调函数。这些回调函数在 nf_conntrack_core.c。这些回调函数可以分为三类,连接跟踪创建以及查询、碎片整理、helpers。
conntrack通过一个hash表去实现高效查询,表结构如下:
每个连接都存在两个hash元组,一个代表是连接的入方向,一个是代表连接的出方向或者叫“reply”方向。
每个元组都包含了连接的相关信息,包括源目IP,以及四层信息。这些元组存在于一个hash元组中。这些结构被定义在nf_conntrack_tuple.h文件中。
Hash计算使用了随机数去放大计算结果,从而避免数据包潜在的丢包风险。
0x 04 conntrack创建以及查询过程
nf_conntrack_in回调函数被注册在PREROUTING的hook上,在包进入路由前,会对包的正确性进行检查。每当收到一个包,都会检查这个包是否与当前的某个conntrack相匹配。如果没有与之匹配的conntrack,就会创建一个新的conntrack,这种机制被写在resolve_normal_ct函数中。
0x 05 碎片处理
碎片处理的功能,被写在了ipv4_conntrack_defrag的回调函数中,这个函数用来收集需要进行整理的碎片。在2.4的kernel中,需要整理的碎片是线性存在的,也就是说他们被复制进一个持续的内存中。不过这种方式的性能消耗比较大,从2.6的kernel开始,碎片不再被复制进线性空间,而是被放入list中。因此,所有的处理必须是可以感知碎片的。例如,如果我们需要一些存储在TCP头的信息,我们就需要先检查这个报文头是否是碎片;如果是碎片的话,只需要将需要的信息复制到栈中。
0x 06 helpers和期望
一些应用层的协议是很难跟踪的,比如FTP协议的被动模式下,使用21号端口去从server获得数据,但是使用1024-65535之间的端口去接收数据。虽然后者的端口是无法预测的,但是这两个过程是相关联的,并非是真正独立的,这就需要防火墙去通过更多的信息去过滤这种包。
conntrack定义了一种被称为helpers的机制,通过这种机制我们就可以确定连接之间是否相关。这种机制被定义在nf_conntrack_core.h文件中的nf_conntrack_expect函数。
以FTP为例,helpers去寻找用于回复passive模式请求的端口类型,如果找到了,一个期望就会被创建,并被嵌入到期望的全局list中。conntrack与期望之间的关系如下图:
每个期望都有存活时间。如果一个conntrack创建后,没有发现与其相匹配的期望,helper将会对这个连接进行再处理。如果与某个主conntrack的期望相匹配,我们就认为这两个连接是相关的。还是以FTP的被动模式为例,21端口的链接就是主conntrack,通过大端口(1024-65535)的conntrack就是与其主conntrack的期望相符合的链接。
0x 07 参考
[1] Miguel Rio et al., “A Map of the Networking Code in Linux Kernel
2.4.20,” Technical Report DataTAG-2004-1, FP5/IST DataTAG Project,
2004
[2]Pablo Neira et al.,“Netfilter’s connection tracking system”