碎片、MTU、MSS夹紧和隧道(2014)

2020-09-12 02:59:49

碎片是一件非常严重的事情,在使用IPsec时更是如此,因为它可能导致严重的性能下降。在这篇文章中,我们将首先回顾什么是碎片化以及为什么会出现碎片化。然后讨论最大数据段大小、隧道和您可以部署的一些潜在解决方案。

IPv4数据包的最大有效负载大小为16位,允许您创建大约65千字节的数据包。如果我们假设每个数据包的处理都涉及一定数量的开销,那么使用较大的数据包应该可以减少一定数据量的总处理负担。例如,发送一个65kB的分组而不是100个650字节的分组。也存在潜在的缺点,例如,如果低带宽链路必须在接收器能够处理其数据之前传输大包,则会增加延迟。另一个潜在问题是,分组中的错误导致必须重新发送该分组,如果该分组更大,则意味着该重传的成本更高。

假设我们有一条高带宽、无错误的链路,为什么看不到16位IPv4数据包?它与最大传输单位(MTU)有关,MTU是以太网等链路层技术的一项功能。虽然IP数据包是一种软件结构,理论上可以是任何大小,但当需要将数据包从链路发送出去时,会施加硬件限制。传统以太网的最大传输单位为1500字节。如果您尝试通过链路发送大于此值的数据包,它将被丢弃。

知道IP数据包可能非常大,但链路层有硬件限制,无法传输超过一定大小的数据包,我们如何确保仍然可以传输“太大”的数据包?这个问题的答案称为碎片。虽然理想情况是每个数据包都可以放入一个帧中,但最好将较大的数据包分成较小的数据包,而不是简单地丢弃所有超过MTU的数据包。

在几乎每个人都在使用以太网的今天,当你看到整个碎片问题时,你会发现它似乎有点愚蠢。乍一看,您似乎可以通过简单地说主机不允许发送太接近以太网的MTU的数据包(比如最大1300字节)来避免这个问题,然后就到此为止。这种观点有几个问题。首先,以太网过去只是另一种链路层技术,并不像现在那样无处不在;还有其他技术具有更小和更大的MTU。如果不存在分段,并且基于以太网的主机发送了理论上最大大小为1300字节的帧,则它将无法到达连接到具有较小MTU的不同底层技术的主机。

其次,TCP/IP堆栈和网络总体上的一个基本想法是,我们应该尽可能地将功能分成多个层,如果更高层协议被迫制作特定大小的数据包,而不管它们连接到哪条链路,如果部署具有更大(或更小)MTU的新的第2层技术(例如,具有巨型帧的以太网),则将更难改变这种行为。在这种情况下,如果部署具有更大(或更小)MTU的新的第2层技术(例如,具有巨型帧的以太网),将更难改变这种行为。

第三个问题是,如果主机在不知道实际MTU的情况下创建固定最大大小的数据包,则可能会遇到数据包在传输过程中由于额外封装而变得过大的问题。

取而代之的是,主机能够创建各种大小的帧,从非常小到它所连接的链路的MTU。它还应该能够接收最大MTU的帧。这可能导致碎片的典型情况是:

主机连接到MTU为1500的以太网,路由器有一个以太网接口和一个MTU为620的未知接口。由于路径上MTU的变化,存在碎片的可能性。

IPv4和IPv6处理碎片的方式完全不同。使用IPv4时,路由器会将大小超过其传出接口的数据包分段。在上图中,如果主机向其中一台路由器发送800字节的数据包,路由器将在通过MTU 620链路发送现在为两个数据包之前将其分段。在IPv6中,路由器不允许分割数据包,而是丢弃太大的数据包,然后将ICMP错误消息发送回主机。从这一点开始,我们将讨论如何应用IPv4。

当IPv4路由器对数据包进行分段时,它会对数据包的数据进行分段,并附加在报头的标志字段中设置了某些位的旧报头的副本。设置一位以指示该分组是片段,如果在当前片段之后有更多片段,则设置另一位。偏移量字段用于指示片段在原始分组中属于何处,第一个分组具有偏移量0,而下一个分组具有等于第一个片段大小的偏移量。

根据最初强制分段的链路的MTU调整每个分段的大小。换句话说,在我们的示例中,800字节的数据包通过620链路,第一个片段的大小为620,第二个片段的大小为180+新报头的大小。这并不完全正确,因为偏移量必须存储为8的倍数。例如,如果数据大小为80,则偏移量字段将显示为10,以节省标头中的空间。我们的想法是使每个片段尽可能大,同时考虑到这8条规则的倍数。

在IPv6中,分段由新的分段扩展报头指示,该报头包含与IPv4分段基本相同的信息,例如指示更多分段的位和偏移量字段。它还有一个标识字段,该字段具有唯一的ID,该ID由源自同一数据包的所有分段报头共享。

数据包一旦分段,在到达目的地之前不会重组。目的主机使用偏移量来正确地对片段进行排序,知道哪些片段是集合中的最后一个片段的比特越多。标识字段用于知道哪些片段属于一起。丢失的片段意味着数据包无法重组,一旦重组计时器超时,其他片段必须被丢弃,并且整个数据包必须由源重新发送。主机的重新组装不一定是灾难性的,因为该主机通常具有及时执行此操作所需的处理能力,但正如我们稍后将看到的,当涉及隧道时,路由器可能必须进行重新组装。当路由器重组碎片时,吞吐量可能会显著降低。

到目前为止,我们已经了解到,如果两台主机之间的路径上的MTU发生更改,则可能会发生碎片。具体地说,如果MTU在某处降低,则可能会强制连接到该较低MTU链路的路由器对数据包进行分段。该解释掩盖了主机本身发生的情况;为什么主机要发送特定大小的数据包?主机上的IP进程获取通过TCP或UDP等传输协议传送的应用层数据。此数据通常称为“数据段”,它携带TCP或UDP报头,然后由IP进程用IP报头进行封装。如果产生的数据包对于主机所连接的链路的MTU来说太大,则在发送之前会将其分段。然后接收器必须重新组装它。理想情况下,您不希望在主机上已经分段,因为这会导致一些问题,比如如果丢失一个分段,则必须重新发送所有分段,但是需要某种信令机制才能告诉传输层适当的分段大小。一旦传输层了解到这一点(特别是TCP),它就应该只向IP进程传送产生大小等于或小于MTU的数据包的数据段。此机制称为TCP最大数据段大小。

在协商TCP会话(您知道的,三次握手)时,SYN包含一个选项字段,主机可以在其中指定要使用的最大数据段大小(MS)。它从它所连接的链路的MTU中得出这个数字,减去40个字节来计算IP和TCP报头。例如,如果MTU为1500,则MSS将为1460。具有最低建议MS的主机将规定会话的MS将是什么。通过遵守MSS,TCP将只向IP进程传送数据段,这些数据段产生的数据包小到足以容纳链路而不会分段,从而解决了主机本身的分段问题。UDP没有类似的机制,相反,通常要做的是限制UDP数据段的大小

此MSS协商本身不足以解决分段问题,因为虽然主机不再分段,但您仍然需要解决路径MTU的问题。当在SYN中设置MSS选项时,主机只能查看其本地链路;在路径更远的位置仍可能存在较低的MTU。

路径MTU发现(PMTUD)旨在解决路由器在两台主机之间的路径中出现的碎片问题。因为它依赖于MSS调整,所以只有TCP支持。它通过利用IP报头中的DF位(“不分段”)来工作。如果设置此位,则丢弃数据包而不是将其分段,并且在路由器丢弃数据包时,会向数据包的源发送ICMP不可达消息,指示由于超过MTU而将其丢弃。ICMP消息还具有“下一跳MTU”字段,其中插入了导致分组丢弃的链路的MTU。此信息存储在主机的高速缓存中,用于调整发往同一目的地的未来流量的MS。如果未使用下一跳MTU字段,主机将使用迭代过程到达适当的MTU。PMTUD连续用于所有数据包,因为路径可能动态更改,并且这是一个单向过程;一个方向上的流量可能发送的数据包太小,甚至无法触发它,或者路由是不对称的,使得MTU仅在两个主机之间的一个方向上相关。

PMTUD的问题是它不是100%可靠的。最常见的问题是一些中间设备阻止ICMP不可达消息,例如防火墙。每个ICMP消息都有一个类型和几个代码,前者提供有关错误的一些信息,后者用于提供进一步的详细信息。在我们的例子中,“无法到达”的类型是3,代码是4(“数据包太大”)。可以专门允许具有某种类型和代码组合的ICMP,而拒绝其他所有内容。在IOS中,允许在ACL中使用PMTUD中使用的ICMP如下所示:

第一个数字是类型,第二个是代码。在运行配置中,它被更改为SIMPLE,Packet-Too-Large:

但是,破坏PMTUD的设备可能甚至不在您的管理控制之下,因此无法对ICMP筛选器进行此更改。因为DF位是由使用PMTUD的主机在IP报头中设置的,所以您最终可能会陷入无法到达主机的情况,因为数据包被丢弃,错误消息没有返回给发送方,告诉它降低MS(实际上,TCP堆栈可以计算出丢弃数据包是因为它们太大,最终会尝试较小的数据包)。

有几个解决方法可以解决这个问题,第一个方法是简单地从我们控制的路由器上的数据包中删除df位。这样做,我们允许MTU链路较低的路由器沿着路径进一步分段数据包,而不是丢弃它。通常不建议这样做。更常见的解决方案是使用“MSS钳位”功能。它所做的是更改遍历该接口的任何入站或出站TCP SYN中的MSS字段。然后,主机将使用此降低的MSS,而不是它们通常使用的(本地链路-40字节),从而产生足够小的数据包以供传递。重要的是要记住,您不应该在提供商/企业边缘设备上使用MSS钳位,因为这会使其容易受到(D)DoS攻击,即攻击者发送大量的SYN,路由器必须处理和调整其MSS,这可能会熔化控制平面。请确保您不会将自己置于可能发生这种情况的情况下。使用以下命令在接口级别激活该功能:

从某些相对较新的IOS版本开始,它应该同时适用于IPv4和IPv6流量。

每当我们遇到MTU问题时,通常都会涉及到某种类型的隧道传输,因为它会增加额外的开销。例如,GRE会额外添加24字节的报头,如果您想要避免碎片,则可以有效地将数据包的数据部分的大小减少该数量。分段和隧道传输变得有些复杂,因为具有隧道端点的路由器有两个独立的角色。第一个角色是数据包转发器,负责查看传入数据包并确定隧道接口是传出接口。它检查隧道的MTU并将其与数据包的大小进行比较,如果数据包等于或小于MTU,则正常发送数据包。如果它较大并且设置了DF位,则丢弃数据包,并且路由器发送ICMP错误消息。如果它较大,并且未设置DF位,则路由器会进行分段。如果必须先进行分段,然后才能将数据包发送出隧道,您可以先分段,然后添加封装,或者先添加封装,然后再分段。在GRE隧道的情况下,首先会发生分段,一旦分段完成,就会添加封装。

现在事情变得有点棘手了。当具有隧道源的路由器发送数据包时,该路由器从概念上成为主机,因为外部IP报头中的源地址是路由器的隧道端点。然后可能发生的是,隧道分组沿着隧道源和隧道目的地之间的路径遇到MTU问题。如果隧道数据包发生分片,则隧道目的地必须在解封之前对其进行重组。一旦完成,路由器就可以将其转发到实际目的地。正如我们前面提到的,如果实际的主机必须重组数据包,这并不是灾难性的,因为它们通常有可用的处理能力,而不会有太多麻烦。然而,对于路由器来说,必须重组到达隧道接口的数据包是个坏消息,而且会降低性能,特别是在涉及IPsec的情况下。

当主机使用PMTUD时,会在IP报头中设置DF位,但默认情况下不会将其复制到GRE报头。换句话说,如果数据包到达时设置了DF位,则新的外部报头将不会设置DF位。Df-bit将确定是否允许数据包进入隧道本身,但是一旦将其封装,它就可以由中间的某个路由器分段,该路由器只看到在两个隧道端点之间传输的IP数据包。可以通过在隧道接口下启用隧道路径MTU发现来更改此行为。如果启用此功能,则DF位将从原始IP报头复制到用于隧道传输数据包的外部IP报头。如果路径MTU发现工作正常,这应该会导致路径沿途的路由器将ICMP消息发送回隧道源,而不是将数据包分段。一般来说,依赖隧道PMTUD和PMTUD的问题是它增加了延迟,因为在我们到达正确的数据包大小之前需要丢弃几个数据包。这个示例应该会让您了解为什么对于任何类型的时间敏感的应用程序来说依赖PMTUD都不是一个好主意:

左侧的主机将设置了DF位的1500字节数据包发送到R1。

R1用于该数据包的传出接口是隧道,该隧道的默认MTU为1476,因为物理接口为1500,而GRE会增加24字节的开销。由于DF位由主机设置,因此R1别无选择,只能丢弃数据包。

主机从R1收到ICMP错误消息,将数据包大小调整为1476,然后向R1发送该大小的数据包。

R1从主机接收1476大小的数据包,将其从隧道发送出去,并将DF位复制到外部报头,因为启用了隧道路径MTU发现。

R2收到GRE封装的数据包并将其丢弃,因为其到R3的链路上的MTU为1400。DF位被设置,因此R1会被告知这一点,并降低隧道的MTU。

主机发送另一个大小为1476的数据包,但这一次R1丢弃了该数据包,因为隧道MTU已降低。R1通过ICMP将此通知主机。

主机发送第四个数据包,这一次它足够小,可以到达另一端的主机。

这种对数据包大小的长时间协商对于时间不敏感的传输是有效的,但如果时间敏感,显然也不是那么好。

如果我们将IPsec添加到我们的GRE隧道,除了IPsec会在GRE之上增加额外开销之外,我们基本上也会遇到同样的问题。就像仅使用GRE一样,我们可以在数据包进入隧道之前进行分段,并在封装之后进行分组在隧道端点之间传输时的分段。如果我们使用IPsec,第二种类型(即隧道端点之间发生的分段)是最具破坏性的。原因是在将数据包发送到硬件加密引擎之前,隧道目的地必须使用进程交换重新组装这些片段。性能的下降可能非常严重。另一种情况是“双分段”,数据包到达GRE隧道并被分段。片段用GRE报头封装并发送到IPsec进程。IPSec添加了额外的封装,导致数据包现在太大,无法离开物理接口。GRE+IPsec数据包必须再次分段。

建议的MTU隧道问题解决方案是部署多种功能组合,以保护您免受几种不同情况的影响。

您需要做的第一件事是将隧道的MTU调整为1400字节左右。默认GRE MTU为1476,这不考虑IPsec开销。请注意,如果您使用我在上一篇文章中介绍的虚拟隧道接口(VTI)功能,MTU将自动设置为1436,无需手动调整MTU。如果将其调整为1400,则可以确保传入的数据包在IPsec之后不会被分段,因为数据包大小永远不会超过物理接口的1500字节MTU。然而,仍然可能发生的是,传入的分组将大于1400字节的MTU可以容纳的分组。在这种情况下,数据包将在GRE和IPsec之前被丢弃(如果设置了DF位)或分段。尽管如此,这还是比另一种选择更好。

除了隧道MTU调整之外,您还应该使用ip tcp adjust-mss命令部署MSS钳位。这应该可以防止主机在PMTUD启动之前发送过大的数据包,或者当PMTUD由于某些ICMP过滤器而无法工作时出现这种情况。如果隧道MTU设置为1400,则应将调整MSS设置为1360,以考虑40字节的IP和TCP开销。

您还可以使用隧道路径MTU发现。如果您的数据包对于隧道端点之间的底层链路来说太大,这可以保护您的数据包不受碎片的影响。另一方面,如果PMTUD不工作,您的数据包将被无礼地丢弃。但是,丢弃数据包可能比接受重新组装带来的性能损失要好,因为您可以立即得到存在问题的反馈。如果数据包是黑洞的,您可以向下调整隧道MTU和MSS,直到它们不是黑洞。