本节我们将讨论信息包如何遍历不同的链(chains)以及以什么样的顺序。我们也会讨论表(tables)的遍历顺序,以及研究与内核相关的某些其它组件所涉及的问题,也就是说不同的路由决策等等。当我们想要编写可以改变数据包路由模式/规则iptables规则时所必须做的。
当包第一次进入防火墙时,它会到达硬件,然后被传递到内核中的适当设备驱动程序。然后,包开始在内核中经历一系列步骤,然后它被发送到正确的应用程序(本地),或转发到另一个主机——或发生任何事情。
让我们看一看发送给我们自己本地主机的数据包。在实际交付到接收它的应用程序之前,它将通过以下步骤:
上图中的信息包是通过INPUT链而不是FORWARD链传递的,这完全符合逻辑。下图我们将看看外发数据包的流程:
下图我们展示的是数据包的目的地是另一个网络上的主机,它的遍历流程是:
从上面几个流程我们可以学习到,数据包可以停在任何iptables链上,或者说如果它不规范,可以停在任一步骤中。当然,我们主要关注iptables。注意,不同的接口或类似的东西没有特定的链或表。FORWARD总是通过这个防火墙/路由器转发所有的数据包。
注意:不要使用INPUT链进行过滤!INPUT仅用于发送不需要路由到任何其它目的只发送到本地主机的数据包
下面一张图可以涵盖上面三种场景的数据包遍历流程:
从上面的图我们可以分析出:如果我们将一个信息包放入第一个路由决策中,而这个信息包的目的地不是本地机器本身,那么它将通过FORWARD链进行路由。另一方面,如果信息包的目的地是本地机器正在监听的IP地址,我们将通过INPUT链将信息包发送到本地机器。
同样值得注意的是,信息包的目的地可能是本地机器,但是通过做NAT,目的地地址可能在PREROUTING链中被改变。因为这发生在第一次路由决定之前,信息包将在这个改变之后被查看。因此,路由可能会在路由决策完成之前被更改。请注意,所有数据包都将通过这个图像中的一个或另一个路径。如果你DNAT一个数据包回到它原本发出的同一网络,它仍然会通过其余的链,直到它回到网络上。
通过rc.test-iptables.txt脚本可以更加直观地理解如何遍历表和链。
正如我们已经注意到的,该表主要用于分割数据包。换句话说,您可以自由地使用这个表中的mangle目标,以更改TOS (Type Of Service)字段等。强烈建议不要使用此表进行任何过滤;任何DNAT、SNAT或Masquerading也不会出现在该表中。
以下这些目标只有在分割表中才有效,无法在其它表中使用:
该表只能用于不同报文的NAT (Network Address Translation)。换句话说,它应该只用于转换信息包的源字段或目的字段。注意,正如我们前面所说的,只有流中的第一个包才会命中这个表。在此之后,对其他数据包将自动采取与第一个数据包相同的操作。使用NAT表的目标有:
原始表主要只用于一件事,那就是在数据包上设置一个标记,表明它们不应该由连接跟踪系统处理。这是通过在包上使用NOTRACK目标实现的。如果使用NOTRACK目标命中了一个连接,那么conntrack将不会跟踪该连接。如果不添加一个新表,这是不可能解决的,因为只有在数据包上实际运行了conntrack之后,其他表才会被调用,并被添加到conntrack表中,或者匹配一个已经可用的连接。
Raw table只有‘PREROUTING’和’OUTPUT’链。因为只有这些位置才可以在数据包真正进入链接跟踪之前处理它们。
要想Raw table运行,必须加载iptable_raw. 如果使用-t raw关键字运行iptables,并且该模块可用,则会自动加载该模块。
原始表是iptables和内核中新增的一个相对较新的表。除非打了补丁,否则在早期的2.6和2.4内核中可能无法使用它。
过滤表主要用于对报文进行过滤。我们可以匹配数据包并以任何我们想要的方式过滤它们。这是我们实际对数据包采取行动的地方,查看它们包含什么,并根据它们的内容删除或接受它们。当然,我们也可以做先验过滤;然而,这个特殊的表是过滤设计的地方。在这个表中几乎所有的目标都是可用的。我们将在这里更多样化地使用过滤器表
如果一个信息包进入了一个链,比如过滤表中的INPUT链,我们可以指定一个跳转规则到同一个表中的另一个链。新链必须由用户指定,它可能不是内置链,例如INPUT或FORWARD链。如果我们考虑一个指向要执行的链中的规则的指针,该指针将从上到下,从一个规则到另一个规则,直到链遍历结束于目标或主链(如FORWARD、INPUT等)结束。一旦发生这种情况,将应用内置链的默认策略。
如果匹配的规则之一指向跳转规范中用户指定的另一个链,则指针将跳转到该链,然后开始从上到下遍历该链。例如,查看上图中的规则执行如何从规则3跳转到链2。报文匹配规则3中包含的匹配项,并设置跳转/目标规范,将报文发送到链2中进行进一步检查。
用户指定的链在链的末端不能有默认策略。只有内建的链条才能有这个。这可以通过在链的末尾添加一个没有匹配的规则来避免,因此它将作为默认策略。如果在用户指定的链中没有匹配到任何规则,默认行为是跳回原始链。如上图所示,规则执行从链2跳转回链1规则4,位于将规则执行发送到链2的规则下方。
遍历用户指定的链中的每个规则,直到其中一个规则匹配为止——然后目标指定遍历应该结束还是继续——或者到达链的末端。如果到达用户指定的链的末端,则将信息包发送回调用链。调用链可以是用户指定的链,也可以是内置链。
状态机其实是连接跟踪机制,连接跟踪是为了让Netfilter框架知道特定连接的状态,实现此功能的防火墙通常比无状态的防火墙安全得多,因为它允许我们编写更紧密得规则集。
再iptables中,数据包可以在四种不同得所谓状态中与被跟踪得连接相关。它们被称为NEW、ESTABLISHED、RELATED、INVALID。通过与–state匹配,我么可以轻松控制允许谁或什么来启动新会话。
所有连接跟踪都是由内核中称为conntrack的特殊框架完成的。Conntrack可以作为模块加载,也可以作为内核本身的内部部分加载。大多数时候,我们需要并希望比默认的conntrack引擎能够维护的更具体的连接跟踪。正因为如此,conntrack中还有一些更具体的部分来处理TCP、UDP或ICMP协议。这些模块从数据包中获取特定的、唯一的信息,以便能够跟踪每个数据流。然后使用conntrack收集的信息来告诉conntrack流当前的状态。例如,UDP流通常通过目的IP地址、源IP地址、目的端口和源端口来唯一标识。
在以前得内核中,我们可以打开和关闭碎片整理。但是,由于引入了iptables和Netfilter,特别是连接跟踪,所以去掉了这个选项。这样做的原因是,如果不整理数据包,连接跟踪就无法正常工作,因此整理已经被合并到conntrack中,并自动执行。它不能关闭,除非关闭连接跟踪。如果打开连接跟踪,则始终进行碎片整理。
所有的连接跟踪都在PREROUTING链中处理,除了在OUTPUT链中处理本地生成的包。这意味着iptables将在PREROUTING链中重新计算状态等等。如果我们在流中发送初始包,状态在OUTPUT链中被设置为NEW,当我们收到一个返回包时,状态在PREROUTING链中被更改为ESTABLISHED,依此类推。如果第一个包不是由我们自己发起的,那么NEW状态当然会在PREROUTING链中设置。因此,所有状态更改和计算都在nat表的PREROUTING和OUTPUT链中完成。
先来简单看下什么是一条跟踪条目,以及在"/proc/net/ip_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 [ASSURED] use=2
在这个示例包含conntrack模块维护的所有信息,这些信息用于了解特定连接的状态。
第一个字段很明显是一个协议名称:TCP ,用数值6来表示。117代表TTL,这个值现在被设置为117秒,并定期减少直到我们看到更多的流量。然后,该值被重置为该在相关时间点处特定状态时的默认值。所以SYN_SENT表示该条目在当前时间点所处的实际状态。一个连接的内部值与外部使用iptables略有不同。SYN_SENT表示我们正在查看的数据包是一个单向的TCP SYN包。接下来很明显是源IP地址、目的IP地址、源端口和目的端口。随后我们看到一个特定的关键字"UNREPLIED",它告诉我们没有看到此连接的返回流量。最后,我们看到了对返回数据包的期望。这些信息详细说明了源IP地址和目的IP地址(这两个地址都是反向的,因为数据包将被定向回给我们)。连接的源端口和目的端口也是如此。所以,跟踪条目里的数值包含很多我们需要关注的信息。
连接跟踪条目可以采用一系列不同的值,所有的值都在linux/include/netfilter-ipv4/ip_conntrack*.h文件中可用的conntrack头文件中指定。这些值取决于我们使用的IP子协议。TCP、UDP或ICMP协议使用特定的默认值,比如linux/include/netfilter-ipv4/ip_conntrack.h中指定的。
可以通过在/proc/sys/net/ipv4/netfilter目录中使用特定的系统调用来改变它们,需要特别关注/proc/sys/net/ipv4/netfilter/ip_ct_*变量。
当一个连接在两个方向上都看到流量时,conntrack条目将擦除[unreply]标志,然后重置它。告诉我们连接在两个方向上都没有看到任何流量的条目将被替换为[ASSURED](确信的)标志,在条目的末尾可以找到。[ASSURED]标志告诉我们,该连接已得到保证,如果我们达到最大可能的跟踪连接,该连接不会被擦除。因此,与非保证连接(未标记为[被保证]的连接)相反,标记为[被保证]的连接将不会被擦除。连接跟踪表可以保存多少连接取决于一个变量,这个变量可以通过最近内核中的ip-sysctl函数来设置。这个条目所持有的默认值很大程度上取决于您有多少内存。在128 MB的内存中,你会得到8192个可能的条目,而在256 MB的内存中,你会得到16376个条目。您可以通过/proc/sys/net/ipv4/ip_conntrack_max设置读取和设置您的设置
另一种更有效的方法是在加载ip_conntrack模块,设置hashsize选项。在正常情况下,ip_conntrack_max等于8 * hashsize。换句话说,将散列大小设置为4096将导致ip_conntrack_max设置为32768个conntrack条目。示例:
work3:/home/blueflux# modprobe ip_conntrack hashsize=4096
work3:/home/blueflux# cat /proc/sys/net/ipv4/ip_conntrack_max
32768
work3:/home/blueflux#
包在内核本身中可能有几种不同的状态,这取决于我们讨论的是什么协议。然而,在内核之外,我们只有前面描述的4种状态。这些状态主要可以与状态匹配结合使用,然后状态匹配将能够基于其当前连接跟踪状态匹配数据包。有效状态包括NEW、ESTABLISHED、RELATED和INVALID。下表将简要解释每种可能的状态。
状态 | 解释 |
---|---|
NEW | NEW状态是说这个包是我们看到的第一个包,这意味着conntrack模块在一个特定的连接中看到的第一个数据包将被匹配。例如,如果我们看到一个SYN包,并且它是我们在连接中看到的第一个包,那么它将匹配。然而,该包也可能不是SYN包,仍然被认为是新的。在某些情况下,这可能会导致某些问题,但当我们需要从其他防火墙恢复丢失的连接时,或者当一个连接已经超时,但实际上还没有关闭时,它也可能非常有用。 |
ESTABLISHED | ESTABLISHED状态在两个方向上都看到了流量,然后将持续匹配这些数据包。已经建立的关系是相当容易理解的。进入ESTABLISHED状态的唯一要求是,一台主机发送一个包,然后它从另一台主机得到一个回复。当收到发送或通过防火墙的应答报文时,NEW状态将变为ESTABLISHED状态。ICMP应答消息也可以被认为是ESTABLISHED,如果我们创建了一个包,该包反过来生成了应答ICMP消息。 |
RELATED | RELATED状态是比较棘手的状态之一。当一个连接与另一个已经建立的连接相关时,该连接被认为是RELATED。这意味着,对于一个被认为是RELATED的连接,我们必须首先有一个被认为是ESTABLISHED的连接。ESTABLISHED连接将在主连接之外生成一个连接。如果conntrack模块能够理解它是相关的,那么新生成的连接将被认为是相关的。一些可以被认为是RELATED的连接的好例子是被认为与FTP控制端口相关的FTP-data连接,以及通过IRC发出的DCC连接。这可以用于允许ICMP错误消息、FTP传输和DCC通过防火墙正常工作。请注意,依赖于此机制的大多数TCP协议和一些UDP协议非常复杂,并且在TCP或UDP数据段的有效载荷内发送连接信息,因此需要特殊的帮助模块来正确理解。 |
INVALID | INVALID状态表示无法识别报文,或者报文没有任何状态。这可能是由于以下几个原因造成的,例如系统内存耗尽或ICMP错误消息没有响应任何已知的连接。一般来说,这种状态的包一律丢弃 |
UNTRACKED | 如果在原始表中使用NOTRACK目标标记了一个包,那么该包将在状态机中显示为UNTRACKED。这也意味着所有相关的连接都不会被看到,因此在处理UNTRACKED连接时必须谨慎一些,因为状态机将无法看到相关的ICMP消息等等。 |
这些状态可以与–state匹配一起使用,以根据数据包的连接跟踪状态来匹配它们。正因为此,状态机如此强大和高效。以前,我们常常不得不打开所有1024以上的端口,让所有流量重新回到我们的本地网络。有了状态机之后,这就不再需要了,因为我们现在可以为返回的流量而不是所有其他类型的流量打开防火墙。
在本节和接下来的几节中,我们将仔细研究状态,以及如何处理三种基本协议TCP、UDP和ICMP。此外,我们还将仔细研究在默认情况下如何处理连接,如果它们不能被归为这三种协议中的任何一种。我们选择从TCP协议开始,因为它本身就是一个有状态协议,并且有很多关于iptables中状态机的有趣细节。
TCP连接总是通过三次握手启动的,握手建立并协商将发送数据的实际连接。整个会话从一个SYN报文开始,然后是一个SYN/ACK报文,最后是一个ACK报文来确认整个会话的建立。此时,连接已经建立并能够开始发送数据。最大的问题是,连接跟踪是如何连接到这里的?
就用户而言,连接跟踪对所有连接类型基本上都是一样的。看看下面的图片,看看在连接的不同阶段,流究竟进入了什么状态。可以看到,从用户的角度来看,连接跟踪代码并没有真正遵循TCP连接的流程。一旦它看到一个包(SYN),它就认为连接是NEW。一旦它看到返回的数据包(SYN/ACK),它就认为连接已经建立。如果你仔细想想,你就会明白为什么。通过这种特殊的实现,可以允许NEW和ESTABLISHED数据包离开本地网络,只允许返回ESTABLISHED连接,这样就可以完美地工作。相反,如果连接跟踪机器将整个连接建立视为NEW,那么我们将永远无法真正停止外部到我们本地网络的连接,因为我们将不得不再次允许NEW包返回。让事情变得更复杂的是,内核中还有许多用于TCP连接的其他内部状态,但在User-land中这些状态是不可用的。大致上,它们遵循RFC 793 - Transmission Control Protocol 第21-23页中规定的国家标准。我们将在本节中进一步详细地考虑这些问题。
上图的流程从用户的角度来看非常简单,但是从内核的角度来看整个构造,就有点困难了。我们来看一个例子,仔细思考/proc/net/ip_conntrack表中连接状态的变化。当接收到连接中的第一个SYN包时,将报告第一个状态。
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包有一个精确的状态(设置了SYN_SENT标志)。当我们看到另一个方向的数据包时,就会达到下一个内部状态。
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_RECV告诉我们,原始的SYN被正确地发送了,并且SYN/ACK返回数据包也正确地通过了防火墙。此外,这个连接跟踪条目现在已经看到了两个方向的流量,因此被认为已经回复。这不是明确的,而是假设的,就像上面的[unreply]标志一样。当我们看到3次握手中的最后一个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 [ASSURED] use=1
在上一个示例中,我们在3次握手中获得了最后一个ACK,只要iptables的内部机制是可感知的,连接就会进入ESTABLISHED状态。正常情况下,数据流现在应该是确信的。
连接也可以进入ESTABLISHED状态,但不是[ASSURED]。当我们打开了连接拾取,就会发生这种情况(需要tcp-window-tracking补丁,以及ip_conntrack_tcp_loose设置为1或更高)
TCP关闭连接的流程以及包的状态如下图所示:
可以看到,在发送最后一个ACK之前,连接从未真正关闭。请注意,此图片仅描述在正常情况下如何关闭它。例如,如果连接被拒绝,也可以通过发送RST(重置)来关闭连接。在这种情况下,连接将立即关闭。
当TCP连接关闭后,连接进入TIME_WAIT状态,该状态默认设置为2分钟。这样,即使连接已经关闭,所有乱序的数据包仍然可以通过我们的规则集。这被用作一种缓冲时间,以便被卡在一个或另一个拥塞的路由器上的数据包仍然可以到达防火墙或连接的另一端。
如果有RST报文重置连接,则状态变为CLOSE。这意味着在整个连接绝对关闭之前,每个连接有默认10秒的时间。RST报文不会被任何意义上的确认,会直接断开连接。除了我们已经讲过的状态,还有其他的状态,下面是TCP流可能出现的状态的完整列表,以及它们的超时值。
状态 | 超时值 |
---|---|
NONE | 30分钟 |
ESTABLISHED | 5天 |
SYN_SENT | 2分钟 |
SYN_RECV | 60秒 |
FIN_WAIT | 2分 |
TIME_WAIT | 2分 |
CLOSE | 10秒 |
CLOSE_WAIT | 12小时 |
LAST_ACK | 30秒 |
LISTEN | 2分 |
这些时间参数都可以在 /proc/sys/net/ipv4/netfilter/ip_ct_tcp_* 设置。 |
还要注意,状态机的User-land端不会查看TCP数据包中设置的TCP标志(例如,RST、ACK和SYN是标志)。这通常是不好的,因为您可能希望允许处于NEW状态的包通过防火墙,但是当您指定NEW标志时,在大多数情况下意味着SYN包。这不是当前状态实现所发生的情况;相反,即使一个没有设置位或ACK标志的包,也将被视为NEW。这可以用于冗余防火墙等,但在只有一个防火墙的家庭网络中,这通常是非常糟糕的。为了避免这种行为,我们可以用State NEW packets but no SYN bit set。另外一种方法是安装"tcp-window-tracking"扩展插件,以及设置/proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_loose为0,这样防火墙就会丢弃所有除SYN标志的NEW状态包。
UDP连接本身不是有状态连接,而是无状态连接。主要是因为它们不包含任何连接的建立或关闭,最重要的是它们不是序列化连接。以特定的顺序接收两个UDP数据包不能表示它们发送的顺序。但是,仍然可以在内核中的连接上设置状态。让我们看看如何跟踪连接,以及它在conntrack中是什么样。
从用户角度来看看到UDP连接建立的方式跟TCP连接几乎是一样的,在内部conntrack信息看起来有点不同,但本质上细节是相同的。首先,让我们看看初始UDP包发送后的条目。
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包。第一个字段显示协议名称,第二个字段是协议数字。这跟TCP是一样的。第三个字段表示该状态的生存时间为20s。在此之后,我们将获得我们所看到的数据包的值,以及从发起数据包的数据包发送方那里获得的通过该连接的数据包的未来预期值。它们是源、目标、源端口和目标端口。【UNREPLIED】标志告诉我们目前为止没有对该数据包的响应。最后,我们得到对返回包的期望的一个简短列表。请注意,后面的条目与前面的那些值的顺序相反。默认情况下,此时的超时设置为30秒。
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 [ASSURED] use=1
服务器已经看到了已发送出去的第一个包的应答,现在的连接被认为是"ESTABLISHED"。这些没有在连接跟踪中显示。主要区别是【UNRPLIED】标志位消失,此外默认超时180被改成了170,再减去10秒,它将是160s的时间。最后,要在被跟踪的连接上设置【ASSURED】标志,必须已经有一个合法的应答数据包到NEW数据包。
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
此时,连接已经确定。该连接看起来与前面的示例完全相同。如果180秒内未使用此连接,则超时。180秒是一个相对较低的值,但对于大多数使用来说应该足够了。对于匹配相同条目并通过防火墙的每个包,该值将重置为其完整值,就像对于所有内部状态一样。
ICMP报文远不是有状态的流,因为它们只用于控制,不应该建立任何连接。有四种ICMP类型会生成返回报文,它们有两种不同的状态。这些ICMP消息可以采用NEW和ESTABLISHED状态。我们讨论的ICMP类型有:Echo请求和应答,Timestamp请求和应答,Information请求和应答,最后是Address mask请求和应答。其中,时间戳请求和信息请求是过时的,很可能被丢弃。但是,Echo消息在一些设置中使用,比如ping主机。地址掩码请求并不经常使用,但有时会很有用。要了解具体是怎样的,请看下面的图片。
在上面的图片中我们可以看到,主机会向目标主机发送一个echo请求,这个包会被防火墙定义为NEW状态。目标主机回复这个请求后,防火墙会把它设置为ESTABLISHED状态。当防火墙看到第一个echo请求时,会将下面的状态条目放入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
上面的条目看起来和TCP和UDP有些区别,其实他们大差不差,我们同样可以很清晰的看到协议名称、TTL、源IP地址、目的IP地址。有区别的地方就是ICMP条目有3个新字段: type、code、id。type字段包含ICMP类型,code字段包含ICMP代码。这些都可以在ICMP_types。最后一个ID字段包含ICMP ID。每个ICMP数据包发送出去的时候都配一个ID。当接收方收到这个ICMP消息的时候,它也会设置成同一个ICMP ID,那样发送方就会识别这个回复并且连接正确的ICMP请求。
接下来的字段【UNREPLIED】标志,表示我们正在看的这个跟踪连接是一个单向连接。最后的字段是我们期待的回复,很容易就发现大部分内容和前面的那段相反,而type和code字段就被设置成返回包中对应的值,这样一个echo请求就被设置成了echo回复,当然,ICMP ID是不变的。
回复的数据包就会被设置成ESTABLISHED状态,至此当ICMP回复后,该链接就不会有其它合法流量产生了,因为ICMP是控制,那么,当该ICMP回复包遍历完防火墙后,该链接跟踪就会被摧毁。
请注意,应答包必须匹配连接跟踪条目给出的标准,才被认为是已建立的,就像所有其他流量类型一样。
ICMP请求默认超时30s,这个值我们可以在/proc/sys/net/ipv4/netfilter/ip_ct_icmp_timeout中修改。30s是一个比较合适的值
ICMP一个很重要的应用就是用来告诉主机一个特定的UDP或TCP链接发生了什么,或者有链接尝试发生。就因为这个,ICMP回复通常被设置成RELATED状态,与原始链接或链接尝试进行关联。一个简单的例子是ICMP主机不可达或ICMP网络不可达。
如果我们的主机尝试与其他主机连接失败,但网络或主机可能发生故障,那么这些将总是生成回我们的主机,因此,最后一个试图到达该站点的路由器将返回一个ICMP消息,告诉我们它的情况。此时将ICMP应答报文视为RELATED报文。下面的图片可以解释它会是什么样子。
在上面的例子我们可以看到,主机发送了一个SYN包到一个特定的地址,防火墙会把这个包设置成NEW,然而这个包所有到达的网络不可达,所以路由器会返回一个网络不可达的ICMP错误给主机。这时候连接跟踪就会把这个包设置成RELATED。
由于已经添加了跟踪条目,所以ICMP应答被正确地发送到客户端,然后期望连接中止。同时,防火墙已经销毁了连接跟踪条目,因为它知道这是一个错误消息。
如果UDP连接遇到任何类似的问题,则会出现上述相同的行为。所有回应UDP连接的ICMP消息都被设置成RELATED,如下图所示:
上图的流程大概是这样:一个UDP包被发送到主机。该UDP连接被认为是NEW。然而,网络被一些在路上的防火墙或路由器禁止。因此,我们的防火墙收到一个ICMP网络禁止回复。防火墙知道此ICMP错误消息与已经打开的UDP连接相关,并将其作为相关报文发送给客户端。此时,防火墙会破坏连接跟踪条目,客户机收到ICMP消息后应该会中止
在某些情况下,conntrack机器不知道如何处理特定的协议。如果它不知道特定的协议,或者不知道它是如何工作的,就会发生这种情况。在这些情况下,它会回到默认行为。默认行为用于,例如NETBLT, MUX和EGP。这种行为看起来与UDP连接跟踪非常相似。第一个数据包被设置成NEW,有应答流量后设置成ESTABLISHED。
当使用默认行为的时候,所有这些包都是同样的超时值。通过变量/proc/sys/net/ipv4/netfilter/ip_ct_generic_timeout来设置。默认值是600秒,这个值根据我们尝试连接时使用的跟踪行为来变化。特别是我们使用卫星来跟踪流量,就需要很长的时间。
对于Linux中的连接跟踪,UNTRACKED是一个相当特殊的关键字。基本上,它用于匹配在原始表中标记为不被跟踪的信息包。原始表是为此而专门创建的。在这个表中,我们在netfilter中不希望跟踪的数据包上设置了一个NOTRACK标记。
注意:这里说的是包,而不是连接,因为我们实际上是对每个进来的包设置了标记。不然,我们仍然需要对链接进行某种跟踪,才能知道不应该跟踪它
链接跟踪和状态机很耗CPU性能,所以我们经常关掉跟踪连接和状态机
比如,如果我们有一个流量很大的路由器,您希望监控所有传入和传出的流量,而不是路由转发的流量。然后,我们可以通过在原始表中接受所有以我们的主机为目的地的数据包,在所有没有发送到防火墙本身的数据包上设置NOTRACK标记,然后为所有其他流量设置NOTRACK。这将允许您对路由器本身的传入流量进行有状态匹配,但同时节省了不处理所有交叉流量的处理能力。
另一个可以使用NOTRACK的例子是,如果你有一个流量已经很大的网络服务器,想要进行有状态跟踪,但又不想浪费处理能力来跟踪网络流量。然后,您可以设置一个规则,跟踪所有本地拥有的IP地址上的80端口,或者实际上服务于web流量的IP地址。然后,您可以享受对所有其他服务的状态跟踪,除了网络流量,这可能会节省一些处理能力。
另外,您必须考虑NOTRACK带来的一些问题。如果使用NOTRACK设置了整个连接,那么你将无法跟踪相关的连接,conntrack和nat helper将不能用于未跟踪的连接,相关的ICMP错误也不能。换句话说,你必须手动打开这些文件。当涉及到FTP和SCTP等复杂协议时,这可能很难管理,意识到这一点,会对你有帮助。
某些协议比其他协议更复杂。当涉及到连接跟踪时,这意味着这样的协议可能更难正确跟踪。ICQ、IRC和FTP协议就是很好的例子。这些协议中的每一个都在数据包的实际数据负载中携带信息,因此需要特殊的连接跟踪助手来使其正确运行。
下表是linux内核中支持的复杂协议的列表,以及引入该协议的内核版本。
协议名称 | 内核版本 |
---|---|
FTP | 2.3 |
IRC | 2.3 |
TFTP | 2.5 |
Amanda | 2.5 |
让我们以FTP协议为第一个例子。FTP协议首先打开一个称为FTP控制会话的连接。当我们通过此会话发出命令时,将打开其他端口,以携带与该特定命令相关的其余数据。这些联系可以通过两种方式进行,既可以是主动的,也可以是被动的。当一个连接主动完成时,FTP客户端会向服务器发送一个连接的端口和IP地址。在此之后,FTP客户端打开端口,服务器从一个随机的非特权端口(>1024)连接到指定的端口,并通过它发送数据。
这里的问题是防火墙不知道这些额外的连接,因为它们是在协议数据的实际有效载荷内协商的。因此,防火墙将无法知道它应该让服务器通过这些特定端口连接到客户机。
解决这个问题的方法是向连接跟踪模块添加一个特殊的助手,它将扫描控制连接中的数据,以获取特定的语法和信息。当它运行到正确的信息时,它将把特定的信息添加为RELATED,服务器将能够跟踪连接,这多亏了RELATED条目。下图展示了FTP服务器与客户端建立连接时的大致流程:
被动FTP的工作方式正好相反。FTP客户端告诉服务器它需要一些特定的数据,服务器根据这些数据回复一个IP地址和端口。客户端收到数据后,将从自己的端口20(ftp数据端口)连接到特定的端口,并获取相关数据。如果您的防火墙后面有一个FTP服务器,那么除了标准的iptables模块外,您还需要这个模块来让Internet上的客户机正确地连接到FTP服务器。同样的,如果你对你的用户非常严格,只希望让他们访问Internet上的HTTP和FTP服务器,并阻止所有其他端口。考虑下面的映像及其与被动FTP的关系。
内核本身已经提供了一些conntrack helper。更具体地说,在撰写本文时,FTP和IRC协议都有conntrack助手。如果您在内核本身中找不到所需的conntrack帮助程序,那么应该查看用户所在的iptables中的patch-o-matic树。patch-o-matic树可能包含更多的conntrack帮助程序,例如ntalk或H.323协议。如果patch-o-matic树中没有这些选项,可以看看其它选项。您可以查看iptables的CVS源代码(如果它最近已经进入到该树中),或者您可以联系Netfilter-devel邮件列表,询问它是否可用。如果没有,也没有添加它的计划,你就只能靠自己的设备了,你很可能想要阅读Rusty Russell的不可靠的网络过滤器黑客操作指南Rusty Russell’s Unreliable Netfilter Hacking HOW-TO ,它在其他资源和链接附录中有链接。
Conntrack helper可以静态编译到内核中,也可以作为模块。如果它们被编译为模块,我们可以使用以下命令加载它们
modprobe ip_conntrack_ftp
modprobe ip_conntrack_irc
modprobe ip_conntrack_tftp
modprobe ip_conntrack_amanda
请注意,连接跟踪与NAT无关,因此,如果你也在进行NAT连接,你可能需要更多的模块。例如,如果你想要进行NAT和跟踪FTP连接,你也需要NAT模块。所有的NAT helper都以ip_nat_开始,并遵循命名约定;例如,FTP NAT助手将被命名为ip_nat_ftp, IRC模块将被命名为ip_nat_irc。conntrack helper遵循相同的命名约定,因此IRC conntrack helper将被命名为ip_conntrack_irc,而FTP conntrack helper将被命名为ip_conntrack_ftp。
本章讨论了netfilter中的状态机是如何工作的,以及它如何保持不同连接的状态。本章还讨论了它是如何呈现给你和终端用户的,以及你可以做什么来改变它的行为,以及不同的协议,这些协议对连接跟踪来说更复杂,以及不同的连接跟踪助手如何命名运作等。