连接跟踪nf_conntrack与NAT和状态防火墙

本文主要记录对于连接跟踪以及其主要应用的NAT和状态iptables的学习内容

连接跟踪

什么是连接跟踪?

连接跟踪是Linux内核中引入的nf_conntrack 模块所实现的功能,同时支持IPv4 和 IPv6,取代只支持 IPv4 的 ip_connktrack,用于跟踪连接的状态,供其他模块使用。顾名思义,就是跟踪并且记录连接状态。Linux为每一个经过网络堆栈的数据包都会记录其状态,生成一个新的连接记录,并将后续的数据包都分配给对应的连接,并更新连接的状态。连接跟踪主要用于Linux的NAT以及状态防火墙。

认识连接跟踪

通常情况下,连接跟踪模块会为每一个连接记录多个信息,用以区分不同的连接。多条连接记录组成一个连接跟踪表。

首先查看一条连接跟踪记录,通常使用下面两种命令(不同系统可能有所区别)

conntrack -L
cat /proc/net/nf_conntrack

可以得到下面这样的连接跟踪记录

tcp 6 117 SYN_SENT src=192.168.1.6 dst=192.168.1.9 sport=32775 dport=22 [UNREPLIED] src=192.168.1.9 dst=192.168.1.6 sport=22 dport=32775 use=2

  • 其中第一项为该记录的协议,即TCP。

  • 第二项为协议号,TCP协议号为6。

  • 第三项是该条记录的剩余生存时间,该时间会持续减少直到有新的数据包到达来更新该记录。

  • 第四项是协议过程中的状态,表明当前该记录处在TCP的SYN_SENT状态,即发出了syn包,等待应答。

  • 之后便是维护当前连接数据包的源IP、目的IP、源端口、目的端口。

  • 中括号内的是连接的状态,[UNREPLIED]表示只有单向有数据传输,没有应答。当收到回复,数据变为双向传输后,连接跟踪变为删除该标记。当出现[ASSURED]时,表明该记录在两个方向上都没有数据传输,存在该标记的记录在连接跟踪表满时不会被删除。

  • 中括号后是该连接所期望收到的数据包的源IP、目的IP、源端口和目的端口号,可见于之前的前面的一条记录是完全相反的

连接跟踪的工作原理

如上所见,连接跟踪通过记录多种数据包的信息来确定每个数据包应该匹配到哪一个连接,通常我们把这些信息叫做连接跟踪的五元组,当然针对不同的协议,可能还有四元组、七元组等等,也存在为了特殊需要而修改连接跟踪模块使其记录更多信息情况。对于一般情况下,X元组(tuple)指的是:

四元组:源IP地址、目的IP地址、源端口、目的端口

五元组:源IP地址、目的IP地址、协议号、源端口、目的端口

七元组:源IP地址、目的IP地址、协议号、源端口、目的端口,服务类型以及接口索引

nf_conntrack模块跟踪所有网络连接,首先根据数据包的元组信息计算出哈希值,分配一个桶位置。如果有冲突就在链表上遍历,直到找到一个精确匹配的key值,如果没有匹配的则新建一条记录。因此实际上存储连接跟踪表的是一个类似于哈希桶的结构。

连接跟踪的具体实现

基础知识

首先要说明的是Linux内核中对于网络数据包处理的netfilter框架,该部分内容在后面状态防火墙也会提到。

对于数据包的处理,netfilter有五个链(chain),分别表示数据包在处理的五个不同过程:

  • PRE_ROUTING:数据包进入路由表之前

  • LOCAL_IN:通过路由表后目的地为本机

  • FORWARD:通过路由表后目的地不为本机

  • LOCAL_OUT:由本机产生向外转发

  • POST_ROUTING:发送到网卡接口之前。

Linux的防火墙iptables有四个表(table),分别记录不同过程中对于数据包的处理方式:

  • filter:一般的过滤功能,默认使用该表
  • nat:用于nat功能(端口映射,地址映射等)
  • mangle: 用于对特定数据包的修改
  • raw:优先级最高,设置raw时一般是为了不再让iptables做数据包的链接跟踪处理,提高性能

表和链的关系(数据包的处理流程):

img

连接跟踪作用点

用于实现连接跟踪入口的hook函数ip_conntrack被置在PRE_ROUTING和LOCAL_OUT,而用于实现连接跟踪出口的hook函数help和confirm被置在LOCAL_IN和POST_ROUTING。

img

实际上PRE_ROUTING和LOCAL_IN可以看作是整个收包流程的入口和出口,而LOCAL_OUT和POST_ROUTING可以看作是出包流程的入口和出口。在只考虑连接跟踪的情况下,一个数据包无外乎有以下三种流程可以走:

  • 发送给本机的数据包 img流程:PRE_ROUTING—-LOCAL_IN—本地进程

  • 需要本机转发的数据包 img 流程:PRE_ROUTING—FORWARD—POST_ROUTING—外出

  • 从本机发出的数据包 img 流程:LOCAL_OUT—-POST_ROUTING—外出

而连接跟踪模块要做的便是在入口时创建连接跟踪记录,出口时将该记录加入到连接跟踪表中

入口:

img

对于每个到来的skb,连接跟踪都将其转换成一个tuple结构(元祖),然后用根据tuple计算出对应的哈希值,再去查连接跟踪表。如果该类型的数据包没有被记录过,将为其在连接跟踪的哈希表里新建一条连接记录,对于已经跟踪过了的数据包则不做此操作。紧接着,调用该报文所属协议的连接跟踪模块的所提供的packet()回调函数,最后根据状态改变连接跟踪记录的状态。

出口:

img

对于每个即将离开Netfilter框架的数据包,如果用于处理该协议类型报文的连接跟踪模块提供了helper函数,那么该数据包首先会被helper函数处理,然后才去判断,如果该报文已经被跟踪过了,那么根据其所属连接的状态,决定该包是该被丢弃、或是返回协议栈继续传输,又或者将其加入到连接跟踪表中。

连接跟踪表

之前简单介绍了连接跟踪的原理,也提到过连接跟踪表。连接跟踪表是一个由全局变量ip_conntrack_hash所指向的哈希表,实际上是一个由数据包元组哈希值组成的双向循环链表数组

img

链表中每个节点都是ip_conntrack_tuple_hash类型的结构体。

img

拓展:协议状态

针对不同的协议,在不同过程时能在连接跟踪记录中看到不同的状态,其详细内容可以参考以下两个链接:

TCP协议端口状态说明

连接跟踪详解

常见问题

连接跟踪记录数量限制

连接跟踪表满是经常会遇到的问题,连接跟踪记录最大值是系统默认设置的,可以通过使用下述命令查看与设置

cat /proc/sys/net/netfilter/nf_conntrack_max
sysctl -p

如果遇到了需要优化连接跟踪限制性能的问题,可以参考这篇博文,讲述的方法比较详细

如何统计连接跟踪

可以通过查看文件直接获取当前的连接跟踪记录数,也可以通过查看全部记录后grep出ESTABLISHED的连接来统计出当前已经建立的记录数

cat /proc/sys/net/netfilter/nf_conntrack_count

NAT

借助于连接跟踪模块,netfilter实现了NAT(Network Address Translation)功能,也就是网络地址转换。NAT主要包含两种:SNAT(源地址转换)和DNAT(目的地址转换)

SNAT是修改数据包的源地址和端口,主要用于内网访问互联网的情况,将内网IP地址转换为公网IP地址。在iptables规则中,源地址转换分为SNAT和MASQUERADE两种,SNAT即为静态源地址转换,将制定的网段一对一的转换为设定的目标网段,而MASQUERADE是将指定的网段转换为从出口网卡上获取到的IP网段地址,实现一种动态地址转换

DNAT是修改数据包中的目的地址和端口,通常用于内网开放服务给互联网访问的情况,例如端口映射。将设备公网接口上唯一的公网IP对应的转换到内网服务的内网IP

在防火墙将数据包的地址转换修改后,必须要记录数据包的修改转换过程,否则对应连接的包再到达时就无法知道将数据包发送给谁了,因此这就是NAT功能利用连接跟踪的地方,通过基于数据包状态建立的连接跟踪表,就能找到对应数据包所属的链接,从而维护NAT的状态。

源地址转换的主要步骤大致如下:

  1. 数据包进入Hook(例如ip_nat_out)函数后,进行规则匹配
  2. 如果所有match都匹备,则进行SNAT模块的动作,即snat的target模块
  3. 源地址转换的规则一般是…… -j SNAT –to X.X.X.X,SNAT用规则中预设的转换后地址X.X.X.X,修改连接跟踪表中的replay tuple(原因:当数据包进入连接跟踪后,会建立一个tuple以及相应的replay tuple,而应答的数据包,会查找与之匹配的repaly tuple,——对于源地址转换而言,应答包中的目的地址,将是转换后的地址,而不是真实的地址,所以,为了让应答的数据包能找到对应的replay tuple,很自然地,NAT模块应该修改replaly tuple中的目的地址,以使应答数据包能找到属于自己的replay)
  4. 接着,修改数据包的来源的地址,将它替换成replay tuple中的相应地址,即规则中预设的地址,将其发送出去
  5. 对于回来的数据包,应该能在状态跟踪表中,查找与之对应的replay tuple,也就能顺藤摸瓜地找到原始的tuple中的信息,将应答包中的目的地址改回来,这样,整个数据传送就得以顺利转发了

状态防火墙

iptables是Linux中netfilter框架的防火墙配置模块,通过iptables我们可以设定netfilter采用何种规则来处理数据包。而通过使用nf_conntrack模块,防火墙可以加入对于数据包状态的处理。

通常我们会在iptables的配置中看到类似于下面这种,带有-m参数且指定了状态,这就是采用了状态防火墙的拓展,筛选出匹配状态的数据包进行下一步处理

iptables -A OUTPUT 1 -m state --state ESTABLISHED -j ACCEPT

在状态防火墙中,一共有四种状态:

  • NEW — 请求新连接的分组,如 HTTP 请求、TCP的握手等。
  • ESTABLISHED — 开始正常传输数据,属于当前连接的一部分的分组,叫做已经建立的连接分组。
  • RELATED — 请求新连接的分组,但是它也是当前连接的一部分,如消极 FTP 连接,其连接端口是 20,但是其传输端口却是 1024 以上的未使用端口。
  • INVALID — 不属于连接跟踪表内任何连接的分组,无法识别的数据包。

通过使用状态防火墙,我们可以实现很多功能,比如只针对已经建立连接的数据包进行转发,或者对无效数据包进行丢弃以防止内网NAT泄露等等

参考链接

洞悉linux下的Netfilter&iptables:如何理解连接跟踪机制?【上】

netfilter 链接跟踪机制与NAT原理