状态机制是 iptables 中特殊的一部分,其实它不应该叫状态机制,因为它只是一种连接跟踪机制。连接跟踪可以让 Netfilter 知道某个特定连接的状态。运行连接跟踪的防火墙称作带有状态机制的防火墙,以下简称为状态防火墙。状态防火墙比非状态防火墙要安全,因为它允许我们编写更严密的规则。在 iptables 里,包是和被跟踪连接的四种不同状态有关的。它们是NEW,ESTABLISHED,
RELATED 和INVALID。所有在内核中由 Netfilter 的特定框架做的连接跟踪称作 conntrack。conntrack 中有许多用来处理 TCP,
UDP 或 ICMP 协议的部件。这些模块从数据包中提取详细的、唯一的信息,因此能保持对每一个数据流的跟踪。这些信息也告知 conntrack 流当前的状态。例如,UDP 流一般由他们的目的地址、源地址、目的端口和源端口唯一确定。
除了本地产生的包由 OUTPUT 链处理外,所有连接跟踪都是在 PREROUTING 链里进行处理的,意思就是, iptables 会在 PREROUTING 链里从新计算所有的状态。如果我们发送一个流的初始化包,状态就会在 OUTPUT 链里被设置为 NEW,当我们收到回应的包时,状态就会在 PREROUTING 链里被设置为 ESTABLISHED。如果第一个包不是本地产生的,那就会在PREROUTING 链里被设置为 NEW 状态。综上,所有状态的改变和计算都是在 nat 表中的 PREROUTING 链和 OUTPUT 链里完成的。
查看文件/proc/net/ip_conntrack 里的 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
conntrack 模块维护的所有信息都包含在这个例子中了,通过它们就可以知道某个特定的连接处于什么状态。首先显示的是协议,这里是 tcp,接着是十进制的6(tcp 的协议类型代码是 6)。之后的 117 是这条 conntrack 记录的生存时间,它会有规律地被消耗,直到收到这个连接的更多的包。那时,这个值就会被设为当时那个状态的缺省值。接下来的是这个连接在当前时间点的状态。上面的例子说明这个包处在状态 SYN_SENT,这个值是 iptables 显示的,以便我们好理解,而内部用的值稍有不同。SYN_SENT 说明我们正在观察的这个连接只在一个方向发送了一 TCP SYN 包。再下面是源地址、目的地址、源端口和目的端口。其中有个特殊的词 UNREPLIED,说明这个连接还没有收到任何回应。最后,是希望接收的应答包的信息,他们的地址和端口和前面是相反的。
当一个连接在两个方向上都有传输时,conntrack 记录就删除[UNREPLIED]标志,然后重置。在末尾有 [ASSURED]的记录说明两个方向已没有流量。这样的记录是确定的,在连接跟踪表满时,是不会被删除的,没有[ASSURED]的记录就要被删
除。连接跟踪表能容纳多少记录是被一个变量控制的,它可由内核中的 ip-sysctl 函数设置。默认值取决于你的内存大小,128MB 可以包含 8192 条目录,256MB 是 16376 条。你也可以在 /proc/sys/net/ipv4/ip_conntrack_max 里查看/设置。
包的状态依据 IP 所包含的协议不同而不同,但在内核外部,也就是用户空间里,只有 4 种状态:NEW,ESTABLISHED,RELATED 和 INVALID。它们主要是和状态匹配一起使用。下面就简要地介绍以下这几种状态:
State(状态) | Explanation(注释) |
---|---|
NEW | 说明这个包是我们看到的第一个包。意思就是,这是 conntrack模块看到的某个连接第一个包,它即将被匹配了。比如,我们看到一个 SYN 包,是我们所留意的连接的第一个包,就要匹配它。第一个包也可能不是 SYN 包,但它仍会被认为是 NEW 状态。这样做有时会导致一些问题,但对某些情况是有非常大的帮助的。例如,在我们想恢复某条从其他的防火墙丢失的连接时,或者某个连接已经超时,但实际上并未关闭时。 |
ESTABLISHED | 已经注意到两个方向上的数据传输,而且会继续匹配这个连接的包。处于 ESTABLISHED 状态的连接是非常容易理解的。只要发送并接到应答,连接就是 ESTABLISHED 的了。一个连接要从NEW 变为 ESTABLISHED,只需要接到应答包即可,不管这个包是发往防火墙的,还是要由防火墙转发的。ICMP 的错误和重定向等信息包也被看作是 ESTABLISHED,只要它们是我们所发出的信息的应答。 |
RELATED | 是个比较麻烦的状态。当一个连接和某个已处于ESTABLISHED 状态的连接有关系时,就被认为是 RELATED 的了。换句话说,一个连接要想是 RELATED 的,首先要有一个 ESTABLISHED的连接。这个 ESTABLISHED 连接再产生一个主连接之外的连接,这个新的连接就是 RELATED 的了,当然前提是 conntrack 模块要能理解 RELATED。ftp 是个很好的例子,FTP-data 连接就是和FTP-control 有 RELATED 的。还有其他的例子,比如,通过 IRC 的DCC 连接。有了这个状态,ICMP 应答、FTP 传输、DCC 等才能穿过防火墙正常工作。注意,大部分还有一些 UDP 协议都依赖这个机制。这些协议是很复杂的,它们把连接信息放在数据包里,并且要求这些信息能被正确理解。 |
INVALID | 说明数据包不能被识别属于哪个连接或没有任何状态。有几个原因可以产生这种情况,比如,内存溢出,收到不知属于哪个连接的 ICMP 错误信息。一般地,我们 DROP 这个状态的任何东西。 |
这些状态可以一起使用,以便匹配数据包。这可以使我们的防火墙非常强壮和有效。以前,我们经常打开 1024 以上的所有端口来放行应答的数据。现在,有了状态机制,就不需再这样了。因为我们可以只开放那些有应答数据的端口,其他的都可以关闭。这样就安全多了。
一个 TCP 连接是经过三次握手协商连接信息才建立起来的。整个会话由一个 SYN包开始,然后是一个 SYN/ACK 包,最后是一个 ACK 包,此时,会话才建立成功,能够发送数据。最大的问题在于连接跟踪怎样控制这个过程。其实非常简单。
默认情况下,连接跟踪基本上对所有的连接类型做同样的操作。看看下面的图片,我们就能明白在连接的不同阶段,流是处于什么状态的。就如你看到的,连接跟踪的代码不是从用户的观点来看待 TCP 连接建立的流程的。连接跟踪一看到 SYN包,就认为这个连接是 NEW 状态,一看到返回的 SYN/ACK 包,就认为连接是ESTABLISHED 状态。如果你仔细想想第二步,应该能理解为什么。有了这个特殊处理,NEW 和 ESTABLISHED 包就可以发送出本地网络,且只有 ESTABLISHED 的连接才能有回应信息。如果把整个建立连接的过程中传输的数据包都看作 NEW,那么三次握手所用的包都是 NEW 状态的,这样我们就不能阻塞从外部到本地网络的连接了。因为即使连接是从外向内的,但它使用的包也是 NEW 状态的,而且为了其他连接能正常传输,我们不得不允许 NEW 状态的包返回并进入防火墙。
tcp 6 117 SYN_SENT src=192.168.1.5 dst=192.168.1.35 sport=1031 dport=23 [UNREPLIED] src=192.168.1.35 dst=192.168.1.5 sport=23 dport=1031 use=1
从上面的记录可以看出,SYN_SENT 状态被设置了,这说明连接已经发出一个SYN包,但应答还没发送过来,这可从[UNREPLIED]标志看出。
tcp 6 57 SYN_RECV src=192.168.1.5 dst=192.168.1.35 sport=1031 dport=23 src=192.168.1.35 dst=192.168.1.5 sport=23 dport=1031 use=1
现在我们已经收到了相应的 SYN/ACK 包,状态也变为 SYN_RECV,这说明最初发出的 SYN 包已正确传输,并且 SYN/ACK 包也到达了防火墙。 这就意味着在连接的两方都有数据传输,因此可以认为两个方向都有相应的回应。当然,这是假设的。
tcp 6 431999 ESTABLISHED src=192.168.1.5 dst=192.168.1.35 sport=1031 dport=23 src=192.168.1.35 dst=192.168.1.5 sport=23 dport=1031 use=1
现在我们发出了三步握手的最后一个包,即 ACK 包,连接也就进入 ESTABLISHED状态了。再传输几个数据包,连接就是[ASSURED]的了。
下面介绍 TCP 连接在关闭连接过程中的状态:
在发出最后一个 ACK 包之前,连接(指两个方向)是不会关闭的。注意,这只是针对一般的情况。连接也可以通过发送关闭,这用在拒绝一个连接的时候。在 RST 包发送之后,要经过预先设定的一段时间,连接才能断掉。
连接关闭后,进入 TIME_WAIT 状态,缺省时间是 2 分钟。之所以留这个时间,是为了让数据包能完全通过各种规则的检查,也是为了数据包能通过拥挤的路由器,从而到达目的地。
如果连接是被 RST 包重置的,就直接变为 CLOSE 了。这意味着在关闭之前只有10 秒的默认时间。RST 包是不需要确认的,它会直接关闭连接。针对 TCP 连接,还有其他一些状态我们没有谈到。下面给出一个完整的状态列表和超时值。内部状态如下:
State | Timeout value |
---|---|
NONE | 30 minutes |
ESTABLISHED | 5 days |
SYN_SENT | 2 minutes |
SYN_RECV | 60 seconds |
FIN_WAIT | 2 minutes |
TIME_WAIT | 2 minutes |
CLOSE | 10 seconds |
CLOSE_WAIT | 12 hours |
LAST_ACK | 30 seconds |
LISTEN> | 2 minutes |
这些值不是绝对的,可以随着内核的修订而变化,也可以通过/proc/sys/net/ipv4/netfilter/ip_ct_tcp_*的变量更改。这些默认值都是经过实践检验的。它们的单位是 jiffies(百分之一秒),所以 3000 就代表 30 秒。
UDP 连接是无状态的,因为它没有任何的连接建立和关闭过程,而且大部分是无序列号的。以某个顺序收到的两个数据包是无法确定它们的发出顺序的。但内核仍然可以对 UDP 连接设置状态。我们来看看是如何跟踪 UDP 连接的,以及conntrack 的相关记录。
UDP 连接的建立几乎与 TCP 的一样。虽然conntrack 信息看起来有点儿不同,但本质上是一样的。下面我们先来看看第一个 UDP 包发出后的 conntrack 记录。
udp 17 20 src=192.168.1.2 dst=192.168.1.5 sport=137 dport=1025 [UNREPLIED] src=192.168.1.5 dst=192.168.1.2 sport=1025 dport=137 use=1
从前两个值可知,这是一个 UDP 包。第一个是协议名称,第二个是协议号,第三个是此状态的生存时间,默认是 30 秒。接下来是包的源、目地址和端口,还有期待之中回应包的源、目地址和端口。[UNREPLIED]标记说明还未收到回应。
udp 17 170 src=192.168.1.2 dst=192.168.1.5 sport=137 dport=1025 src=192.168.1.5 dst=192.168.1.2 sport=1025 dport=137 use=1
一旦收到第一个包的回应,[UNREPLIED]标记就会被删除,连接就被认为是ESTABLISHED 的,但在记录里并不显示 ESTABLISHED 标记。相应地,状态的超时时间也变为 180 秒了。在本例中,只剩 170 秒了,10 秒后,就会减少为 160 秒。有个东西是不可少的,虽然它可能会有些变化,就是前面提过的[ASSURED]。要想变为 [ASSURED]状态,连接上必须要再有些流量。
udp 17 175 src=192.168.1.5 dst=195.22.79.2 sport=1025 dport=53 src=195.22.79.2 dst=192.168.1.5 sport=53 dport=1025 [ASSURED] use=1
可以看出来,[ASSURED]状态的记录和前面的没有多大差别,除了标记由[UNREPLIED]变成[ASSURED]。如果这个连接持续不了 180 秒,那就要被中断。180秒是短了点儿,但对大部分应用足够了。只要遇到这个连接的包穿过防火墙,超时值就会被重置为默认值,所有的状态都是这样的。
ICMP 也是一种无状态协议,它只是用来控制而不是建立连接。常用的回显请求和应答(Echo request and reply),有两种状态 NEW 和 ESTABLISHED 。
比如 ping 命令。
主机向目标发送一个回显请求,防火墙就认为这个包处于 NEW 状态。目标回应一个回显应答,防火墙就认为包处于 ESTABLISHED 了。当回显请求被发送时,ip_conntrack 里就有这样的记录了:
icmp 1 25 src=192.168.1.6 dst=192.168.1.10 type=8 code=0 id=33029 [UNREPLIED] src=192.168.1.10 dst=192.168.1.6 type=0 code=0 id=33029 use=1
可以看到,ICMP 的记录和 TCP、UDP 的有点区别,协议名称、超时时间和源、目地址都一样,不同之处在于没有了端口,而新增了三个新的字段:type,code和 id。字段 type 说明 ICMP 的类型。code 说明 ICMP 的代码,这些代码在附录ICMP 类型里有说明。id 是 ICMP 包的 ID。每个 ICMP 包被发送时都被分配一个 ID,接受方把同样的 ID 分配给应答包,这样发送方能认出是哪个请求的应答。[UNREPLIED]的含义和前面一样,说明数的传输只发生在一个方向上,也就是说未收到应答。再往后,是应答包的源、目地址,还有相应的三个新字段,要注意的是 type 和 code 是随着应答包的不同而变化的,id 和请求包的一样。和前面一样,应答包被认为是 ESTABLISHED 的。然而,在应答包之后,这个 ICMP连接就不再有数据传输了。所以,一旦应答包穿过防火墙,ICMP 的连接跟踪记录就被销毁了。
以上各种情况,请求被认为 NEW,应答是 ESTABLISHED。换句话说,就是当防火墙看到一个请求包时,就认为连接处于 NEW 状态,当有应答时,就是 ESTABLISHED状态。
ICMP 的缺省超时是 30 秒,可以在/proc/sys/net/ipv4/netfilter/ip_ct_icmp_timeout 中修改。这个值是比较合适的,适合于大多数情况。
ICMP 的另一个非常重要的作用是,告诉 UDP、TCP 连接或正在努力建立的连接发生了什么,这时 ICMP 应答被认为是 RELATED 的。主机不可达和网络不可达就是这样的例子。当试图连接某台机子不成功时(可能那台机子被关上了),数据包所到达的最后一台路由器就会返回以上的 ICMP 信息,它们就是 RELATED 的,如下图:
我们发送了一个 SYN 包到某一地址,防火墙认为它的状态是 NEW。但是,目标网络有问题不可达,路由器就会返回网络不可达的信息,这是 RELATED 的。连接跟踪会认出这个错误信息是哪个连接的,连接会中断,同时相应的记录删除会被删除。
有时,conntrack 机制并不知道如何处理某个特殊的协议,尤其是在它不了解这
个协议或不知道协议如何工作时,比如,NETBLT,MUX 还有 EGP。这种情况下,conntrack 使用缺省的操作。这种操作很象对 UDP 连接的操作,就是第一个包被认作 NEW,其后的应答包等等数据都是 ESTABLISHED。 使用缺省操作的包的超时值都是一样的,600 秒,也就是 10 分钟。当然,这个值可以通过/proc/sys/net/ipv4/netfilter/ip_ct_generic_timeout 更改,以便适应你的通信量,尤其是在耗时较多、流量巨大的情况下,比如使用卫星等
有些协议比其他协议更复杂,这里复杂的意思是指连接跟踪机制很难正确地跟踪它们,比如,ICQ、IRC 和 FTP,它们都在数据包的数据域里携带某些信息,这些信息用于建立其他的连接。
温馨提示:
以上文章描述如有不清晰之处,欢迎在评论区评论,如有时间,会第一时间回复,谢谢!