如何理解Netfilter中的链接跟踪机制?linux
本篇我打算以一个问句开头,由于在知识探索的道路上只有多问而后充分调动起思考的机器才能让本身走得更远。链接跟踪定义很简单:用来记录和跟踪链接的状态。数组
问:为何又须要链接跟踪功能呢?服务器
答:由于它是状态防火墙和NAT的实现基础。网络
OK,算是明白了。Neftiler为了实现基于数据链接状态侦测的状态防火墙功能和NAT地址转换功能才开发出了链接跟踪这套机制。那就意思是说:若是编译内核时开启了链接跟踪选项,那么Linux系统就会为它收到的每一个数据包维持一个链接状态用于记录这条数据链接的状态。接下来咱们就来研究一下Netfilter的链接跟踪的设计思想和实现方式。数据结构
以前有一副图,咱们能够很明确的看到:用于实现链接跟踪入口的 hook 函数以较高的优先级分别被注册到了 netfitler 的 NF_IP_PRE_ROUTING 和 NF_IP_LOCAL_OUT 两个 hook 点上;用于实现链接跟踪出口的 hook 函数以很是低的优先级分别被注册到了 netfilter 的 NF_IP_LOCAL_IN 和 NF_IP_POST_ROUTING 两个 hook 点上。其实PRE_ROUTING和LOCAL_OUT点能够看做是整个netfilter的入口,而POST_ROUTING和LOCAL_IN能够看做是其出口。在只考虑链接跟踪的状况下,一个数据包无外乎有如下三种流程能够走:框架
1、发送给本机的数据包函数
流程:PRE_ROUTING----LOCAL_IN---本地进程布局
2、须要本机转发的数据包this
流程:PRE_ROUTING---FORWARD---POST_ROUTING---外出spa
3、从本机发出的数据包
流程:LOCAL_OUT----POST_ROUTING---外出
咱们都知道在INET层用于表示数据包的结构是大名鼎鼎的sk_buff{}(后面简称skb),若是你不幸的没据说过这个东东,那么我强烈的建议你先补一下网络协议栈的基础知识再继续阅读这篇文章。在skb中有个成员指针nfct,类型是struct nf_conntrack{},该结构定义在include/linux/skbuff.h文件中。该结构记录了链接记录被公开应用的计数,也方便其余地方对链接跟踪的引用。链接跟踪在实际应用中通常都经过强制类型转换将nfct转换成指向ip_conntrack{}类型(定义在include/linux/netfilter_ipv4/ip_conntrack.h里)来获取一个数据包所属链接跟踪的状态信息的。即:Neftilter框架用ip_conntrack{}来记录一个数据包与其链接的状态关系。
同时在include/linux/netfilter_ipv4/ip_conntrack.h文件中还提供了一个很是有用的接口:struct ip_conntrack *ip_conntrack_get(skb, ctinfo)用于获取一个skb的nfct指针,从而得知该数据包的链接状态和该链接状态的相关信息ctinfo。从链接跟踪的角度来看,这个ctinfo表示了每一个数据包的几种链接状态:
l IP_CT_ESTABLISHED
Packet是一个已建链接的一部分,在其初始方向。
l IP_CT_RELATED
Packet属于一个已建链接的相关链接,在其初始方向。
l IP_CT_NEW
Packet试图创建新的链接
l IP_CT_ESTABLISHED+IP_CT_IS_REPLY
Packet是一个已建链接的一部分,在其响应方向。
l IP_CT_RELATED+IP_CT_IS_REPLY
Packet属于一个已建链接的相关链接,在其响应方向。
在链接跟踪内部,收到的每一个skb首先被转换成一个ip_conntrack_tuple{}结构,也就是说ip_conntrack_tuple{}结构才是链接跟踪系统所“认识”的数据包。那么skb和ip_conntrack_tuple{}结构之间是如何转换的呢?这个问题没有一个统一的答案,与具体的协议息息相关。例如,对于TCP/UDP协议,根据“源、目的IP+源、目的端口”再加序列号就能够惟一的标识一个数据包了;对于ICMP协议,根据“源、目的IP+类型+代号”再加序列号才能够惟一肯定一个ICMP报文等等。对于诸如像FTP这种应用层的“活动”协议来讲状况就更复杂了。本文不试图去分析某种具体协议的链接跟踪实现,而是探究链接跟踪的设计原理和其工做流程,使你们掌握链接跟踪的精髓。由于如今Linux内核更新的太快的都到3.4.x,变化之大啊。就算是2.6.22和2.6.21在链接跟踪这块仍是有些区别呢。一旦你们理解了链接跟踪的设计思想,掌握了其神韵,它再怎么也万变不离其宗,再看具体的代码实现时就不会犯迷糊了。俗话说“授人一鱼,不如授人一渔”,咱们教给你们的是方法。有了方法再加上本身的勤学苦练,那就成了技能,最后可使得你们在为本身的协议开发链接跟踪功能时内心有数。这也是我写这个系列博文的初衷和目的。与君共勉。
在开始分析链接跟踪以前,咱们仍是站在统帅的角度来俯视一下整个链接跟踪的布局。这里我先用比较粗略的精简流程图为你们作个展现,目的是方便你们理解,好入门。固然,个人理解可能还有不太准确的地方,还请大牛们帮小弟指正。
我仍是重申一下:链接跟踪分入口和出口两个点。谨记:入口时建立链接跟踪记录,出口时将该记录加入到链接跟踪表中。咱们分别来看看。入口:
整个入口的流程简述以下:对于每一个到来的skb,链接跟踪都将其转换成一个tuple结构,而后用该tuple去查链接跟踪表。若是该类型的数据包没有被跟踪过,将为其在链接跟踪的hash表里创建一个链接记录项,对于已经跟踪过了的数据包则不用此操做。紧接着,调用该报文所属协议的链接跟踪模块的所提供的packet()回调函数,最后根据状态改变链接跟踪记录的状态。
出口:
整个出口的流程简述以下:对于每一个即将离开Netfilter框架的数据包,若是用于处理该协议类型报文的链接跟踪模块提供了helper函数,那么该数据包首先会被helper函数处理,而后才去判断,若是该报文已经被跟踪过了,那么其所属链接的状态,决定该包是该被丢弃、或是返回协议栈继续传输,又或者将其加入到链接跟踪表中。
链接跟踪的协议管理:
咱们前面曾说过,不一样协议其链接跟踪的实现是不相同的。每种协议若是要开发本身的链接跟踪模块,那么它首先必须实例化一个ip_conntrack_protocol{}结构体类型的变量,对其进行必要的填充,而后调用ip_conntrack_protocol_register()函数将该结构进行注册,其实就是根据协议类型将其设置到全局数组ip_ct_protos[]中的相应位置上。
ip_ct_protos变量里保存链接跟踪系统当前能够处理的全部协议,协议号做为数组惟一的下标,以下图所示。
结构体ip_conntrack_protocol{}中的每一个成员,内核源码已经作了很详细的注释了,这里我就不一一解释了,在实际开发过程当中咱们用到了哪些函数再具体分析。
链接跟踪的辅助模块:
Netfilter的链接跟踪为咱们提供了一个很是有用的功能模块:helper。该模块可使咱们以很小的代价来完成对链接跟踪功能的扩展。这种应用场景需求通常是,当一个数据包即将离开Netfilter框架以前,咱们能够对数据包再作一些最后的处理。从前面的图咱们也能够看出来,helper模块以较低优先级被注册到了Netfilter的LOCAL_OUT和POST_ROUTING两个hook点上。
每个辅助模块都是一个ip_conntrack_helper{}结构体类型的对象。也就是说,若是你所开发的协议须要链接跟踪辅助模块来完成一些工做的话,那么你必须也去实例化一个ip_conntrack_helper{}对象,对其进行填充,最后调用ip_conntrack_helper_register{}函数将你的辅助模块注册到全局变量helpers里,该结构是个双向链表,里面保存了当前已经注册到链接跟踪系统里的全部协议的辅助模块。
全局helpers变量的定义和初始化在net/netfilter/nf_conntrack_helper.c文件中完成的。
最后,咱们的helpers变量所表示的双向链表通常都是像下图所示的这样子:
由此咱们基本上就能够知道,注册在Netfilter框架里LOCAL_OUT和POST_ROUTING两个hook点上ip_conntrack_help()回调函数所作的事情基本也就很清晰了:那就是经过依次遍历helpers链表,而后调用每一个ip_conntrack_helper{}对象的help()函数。
指望链接:
Netfilter的链接跟踪为支持诸如FTP这样的“活动”链接提供了一个叫作“指望链接”的机制。咱们都知道FTP协议服务端用21端口作命令传输通道,主动模式下服务器用20端口作数据传输通道;被动模式下服务器随机开一个高于1024的端口,而后客户端来链接这个端口开始数据传输。也就是说不管主、被动,都须要两条链接:命令通道的链接和数据通道的链接。链接跟踪在处理这种应用场景时提出了一个“指望链接”的概念,即一条数据链接和另一条数据链接是相关的,而后对于这种有“相关性”的链接给出本身的解决方案。咱们说过,本文不打算分析某种具体协议链接跟踪的实现。接下来咱们就来谈谈指望链接。
每条指望链接都用一个ip_conntrack_expect{}结构体类型的对象来表示,全部的指望链接存储在由全局变量ip_conntrack_expect_list所指向的双向链表中,该链表的结构通常以下:
结构体ip_conntrack_expect{}中的成员及其意义在内核源码中也作了充分的注释,这里我就不逐一介绍了,等到须要的时候再详细探讨。
链接跟踪表:
说了半天终于到咱们链接跟踪表抛头露面的时候了。链接跟踪表是一个用于记录全部数据包链接信息的hash散列表,其实链接跟踪表就是一个以数据包的hash值组成的一个双向循环链表数组,每条链表中的每一个节点都是ip_conntrack_tuple_hash{}类型的一个对象。链接跟踪表是由一个全局的双向链表指针变量ip_conntrack_hash[]来表示。为了使咱们更容易理解ip_conntrack_hash[]这个双向循环链表的数组,咱们将前面提到的几个重要的目前还未介绍的结构ip_conntrack_tuple{}、ip_conntrack{}和ip_conntrack_tuple_hash{}分别介绍一下。
咱们能够看到ip_conntrack_tuple_hash{}仅仅是对ip_conntrack_tuple{}的封装而已,将其组织成了一个双向链表结构。所以,在理解层面上咱们能够认为它们是同一个东西。
在分析ip_conntrack{}结构时,咱们将前面全部和其相关的数据结构都列出来,方便你们对其理解和记忆。
该图但是说是链接跟踪部分的数据核心,接下来咱们来详细说说ip_conntrack{}结构中相关成员的意义。
l ct_general:该结构记录了链接记录被公开应用的计数,也方便其余地方对链接跟踪的引用。
l status:数据包链接的状态,是一个比特位图。
l timeout:不一样协议的每条链接都有默认超时时间,若是在超过了该时间且没有属于某条链接的数据包来刷新该链接跟踪记录,那么会调用这种协议类型提供的超时函数。
l counters:该成员只有在编译内核时打开了CONFIG_IP_NF_CT_ACCT开完才会存在,表明某条链接所记录的字节数和包数。
l master:该成员指向另一个ip_conntrack{}。通常用于指望链接场景。即若是当前链接是另外某条链接的指望链接的话,那么该成员就指向那条咱们所属的主链接。
l helper:若是某种协议提供了扩展模块,就经过该成员来调用扩展模块的功能函数。
l proto:该结构是ip_conntrack_proto{}类型,和咱们前面曾介绍过的用于存储不一样协议链接跟踪的ip_conntrack_protocol{}结构不要混淆了。前者是个枚举类型,后者是个结构体类型。这里的proto表示不一样协议为了实现其自身的链接跟踪功能而须要的一些额外参数信息。目前这个枚举类型以下:
若是未来你的协议在实现链接跟踪时也须要一些额外数据,那么能够对该结构进行扩充。
l help:该成员表明不一样的应用为了实现其自身的链接跟踪功能而须要的一些额外参数信息,也是个枚举类型的ip_conntrack_help{}结构,和咱们前面刚介绍过的结构体类型ip_conntrack_helpers{}容易混淆。ip_conntrack_proto{}是为协议层须要而存在的,而ip_conntrack_help{}是为应用层须要而存在。
l tuplehash:该结构是个ip_conntrack_tuple_hash{}类型的数组,大小为2。tuplehash[0]表示一条数据流“初始”方向上的链接状况,tuplehash[1]表示该数据流“应答”方向的响应状况,见上图所示。
到目前为止,咱们已经了解了链接跟踪设计思想和其工做机制:链接跟踪是Netfilter提供的一套基础框架,不一样的协议能够根据其自身协议的特殊性在链接跟踪机制的指导和约束下来开发本协议的链接跟踪功能,最后将其交给链接跟踪机制来统一管理。
未完,待续…