【协议森林】详解Netfilter(二)----连接跟踪(CONNTRACK)

1、何为连接跟踪

顾名思义,连接跟踪(CONNTRACK)就是跟踪并且记录连接状态。Linux为每一个经过网络协议栈的数据包,根据5元组信息(源IP、目的IP、源端口、目的端口和协议号)来生成一个新的连接记录项(Connectionentry)。此后,所有属于此连接的数据包都被唯一地分配给这个连接,并标识连接的状态。连接跟踪是防火墙模块的状态检测的基础,同时也是地址转换中实现SNAT和DNAT的前提。

2、连接跟踪表

用于实现连接跟踪的hook函数分别注册到了NF_IP_PRE_ROUTING、NF_IP_LOCAL_OUT、NF_IP_LOCAL_IN和NF_IP_POST_ROUTING四个hook上。入口函数以比较高的优先级在PRE_ROUTING和LOCAL_OUT上,而出口函数以最低的优先级处在LOCAL_IN和POST_ROUTING上。
【协议森林】详解Netfilter(二)----连接跟踪(CONNTRACK)_第1张图片
数据包的流向无非三种:
发给本地(网关)的数据包:流程PRE_ROUTING->LOCAL_IN;经过本地的数据包(转发包),流程是PRE_ROUTING->FORWARD->POST_ROUTING;从本地(网关)发出的数据包,流程是LOCAL_OUT->POST_ROUTING。
【协议森林】详解Netfilter(二)----连接跟踪(CONNTRACK)_第2张图片
对于一个连接来说,使用struct nf_conn来记录。一个内核数据skb的结构体中有一个成员指针nfct其类型也是struct nf_conn,该指针指向一个连接。换句话说,Netfilter框架使用结构体nf_conn来记录一个数据包和其连接的关系。
在连接跟踪内部,收到的每个skb首先被转换成一个nf_conntrack_tuple{}结构,也就是说nf_conntrack_tuple{}结构才是连接跟踪系统所“认识”的数据包。那么skb和nf_conntrack_tuple{}结构之间是如何转换的呢?这个问题没有一个统一的答案,与具体的协议息息相关。例如,对于TCP/UDP协议,根据“源、目的IP+源、目的端口”再加协议号就可以唯一的标识一个数据包了;对于ICMP协议,根据“源、目的IP+类型+代号”再加序列号才可以唯一确定一个ICMP报文等等。对于诸如像FTP这种应用层的“活动”协议来说情况就更复杂了。现在从连接跟踪大的流程上,以简短的方式给以简单的概括。
在这里插入图片描述
对于每个到来的skb,连接跟踪都将其转换成一个tuple结构,然后用该tuple去查连接跟踪表。如果该类型的数据包没有被跟踪过,将为其在连接跟踪的hash表里建立一个连接记录项,对于已经跟踪过了的数据包则不用此操作。紧接着,调用该报文所属协议的连接跟踪模块的所提供的packet()回调函数,最后根据状态改变连接跟踪记录的状态。
连接跟踪表是一个用于记录所有数据包连接信息的hash散列表,其实连接跟踪表就是一个以数据包的hash值组成的一个双向循环链表数组,每条链表中的每个节点都是nf_conntrack_tuple_hash{}类型的一个对象。连接跟踪表是由一个全局的双向链表指针变量net->ct.hash[]来表示。根据来的包skb生成tuple,然后根据哈希函数对tuple进行计算得到哈希值,把哈希值作为连接跟踪数组的下标可以得到哈希的头指针,从而可以插入、删除、查询等操作。
【协议森林】详解Netfilter(二)----连接跟踪(CONNTRACK)_第3张图片
net->ct.hash数组是使用的哈希链表,这里内核巧妙地使用了一个辨别‘null’的方法,由于指针都是4字节对齐(取决于CPU,也有8字节对齐的),所以最后一位被占用来标识是不是’null’,当最后一位是1的时候,就被认为是’null’。可以看下初始化的时候,遍历net->ct.hash数组,然后调用宏:

#defineINIT_HLIST_NULLS_HEAD(ptr,nulls)\
	((ptr)->first=(structhlist_nulls_node*)(1UL|(((long)nulls)<<1)))

当去判断一个哈希链表指针是否是一个’null’,同样也只是判断最后一位是否是1。源码如下:

staticinlineintis_a_nulls(conststructhlist_nulls_node*ptr){
return((unsignedlong)ptr&1);	/*逻辑与1*/
}

3、数据描述

从下面开始介绍几个关键的数据结构,及数据结构之间的关系。描述关于’tuple’的结构体struct nf_conntrack_tuple:src:表示源相关,ip地址、端口/id/key、3层协议,dst:表示目的相关,ip地址、端口/icmp(type,code)/key、协议号、方向,nf_conntrack_tuple_hash{}仅仅是对nf_conntrack_tuple{}的封装而已,将其组织成了一个双向链表结构。因此,在理解层面上我们可以认为它们是同一个东西。
【协议森林】详解Netfilter(二)----连接跟踪(CONNTRACK)_第4张图片
【协议森林】详解Netfilter(二)----连接跟踪(CONNTRACK)_第5张图片
连接跟踪主要的数据结构structnf_conn见图所示,防火墙、策略等相关安全的扩展都是在这个结构体的基础上进行的扩展。具体每一项的含义如下:
ct_general: 该连接记录被引用的次数
lock: 自旋锁
status: 数据连接的状态,是一个比特位图
master: 该成员指向另外一个ip_conntrack{}。一般用于期望连接场景。即如果当前连接是另外某条连接的期望连接的话,那么该成员就指向那条我们所属的主连接。
timeout: 不同协议的每条连接都有默认超时时间,如果在超过了该时间且没有属于某条连接的数据包来刷新该连接跟踪记录,那么会调用这种协议类型提供的超时函数。
mark:
secmark:
ext: 扩展使用
ct_net:
proto: 为其他模块保留的存储空间
tuple_hash: 该结构是个nf_conntrack_tuple_hash{}类型的数组,大小为2。tuplehash[0]表示一条数据流“初始”方向上的连接情况,tuplehash[1]表示该数据流“应答”方向的响应情况。
对于不同的协议,其连接跟踪的实现是不一样的,所以每种协议如果要开发自己的连接跟踪模块,那么它首先必须实例化一个nf_conntrack_l3proto{}结构体类型的变量,对其进行必要的填充,然后调用nf_conntrack_l3proto_register()函数将该结构进行注册,其实就是根据协议类型将其设置到全局数组nf_ct_l3protos[]中的相应位置上。同时,也存在一个4层协议的全局数组,各个协议保存着对应的处理函数。
【协议森林】详解Netfilter(二)----连接跟踪(CONNTRACK)_第6张图片
【协议森林】详解Netfilter(二)----连接跟踪(CONNTRACK)_第7张图片
pkt_to_tuple(): 把包转换成tuple
invert_tuple(): 根据已知tuple,推出反向tuple
print_tuple(): 打印tuple
get_l4proto(): 获取4层协议号
netlink相关(具体啥作用,还不清楚):
tuple_to_nlattr():
nlattr_tuple_size():
nlattr_to_tuple():
packet(): 判断数据包是否合法,并调整相应连接的信息,也就是实现各协议的状态检测,对于UDP等本身是无连接的协议的判断比较简单,netfilter建立一个虚拟连接,每个新发包都是合法包,只等待回应包到后连接都结束;但对于TCP之类的有状态协议必须检查数据是否符合协议的状态转换过程,这是靠一个状态转换数组实现的。返回数据报的verdict值
new(): 当此协议的一个新连接发生时,调用其指向的这个函数
destroy(): 删除一个连接状态
print_conntrack(): 打印整个连接记录
to_nlattr():
nlattr_size():
from_nlattr():

加入讨论

【协议森林】详解Netfilter(二)----连接跟踪(CONNTRACK)_第8张图片

你可能感兴趣的:(协议森林)