工作上,平时上网,都用到了 OpenVPN,事实上我比较喜欢用 TCP 模式而不用 UDP(直到发现了重传叠加),因为自从上学时代,我就对 UDP 不感冒,它除了多路复用,分解的作用外,和 IP 几乎一样,而且根据我们的习惯,越复杂的东西越好,UDP 简单,所以它不好!我不知道我们为何会有这样的想法,如果德国人和英国人也这样想,那很多好东西就都不会出现了,甚至都不会发生工业革命... 后来,我发现,事情慢慢起了变化,不是想的那么简单,确实是这样,TCP 有好处,也会带来问题,UDP 是不好,同样也会带来问题,也会有一些优势,本文就针对我挖掘出的一些问题进行总结,不很全面,仅仅作为一个小结。

1. 一些词汇和短语的解释

正常传输队列:从应用层来一个数据,该数据就会被 TCP 封装,然后加入正常传输队列末尾。

重传队列:在正常传输队列中每发送一个 TCP 分段,该分段就会加入重传队列末尾。

正常传输:TCP 会视发送窗口的大小以及窗口的可用性从正常传输队列头取出一个 TCP 分段,然后传输。

重传

超时重传:从重传队列头取出一个 TCP 分段,重传之;

快速重传:从重传队列头依次取出一个个可能丢失的 TCP 分段,重传之;

避免不必要重传:在快速重传中,如果已经重传了可能丢失的 TCP 分段,理论上讲,按照标准,接下来会依次重传重传队列后面的所有 TCP 分段,然而如果是由于 ACK 丢失,或者接收端已经缓存了乱序的分段,这样的重传就是冗余的,由于正常传输队列和重传队列是分开的 (重传队列和发送队列分离设计),此时发送正常传输队列中的当前可以发送 (没有窗口限制和 Nagel 限制) 的 TCP 分段可以有效地“带回一些 ACK”这些 ACK 会清除重传队列里的 TCP 分段。因此根据这个策略,不必要的重传实际上是很少的。 背景流量:如果一条 IP 层链路上封装了一个隧道,所有非隧道的流量都是背景流量。

2. TCP 隧道的问题

2.1. TCP 拥塞控制的意义

注意,TCP 本来是不包含拥塞控制的,但是因为它是端到端的协议,并没有运行于任何网络节点,因此裸 TCP 对于带宽是极其贪婪的,和 UDP 一样,当网络拥堵以后,TCP 就加入了拥塞控制。然而端到端的协议并不仅仅就是 TCP,虽然 TCP 加入了拥塞控制,可是 UDP 并没有,UDP 本来就不关心丢包,网络拥塞了,丢包就是了。然而,UDP 是情愿丢包也不进行拥塞控制,这确实是连累了 TCP,结果就是 TCP 频繁的进入拥塞状态,不断的慢启动或者快速重传,导致 TCP 流量的颠簸,虽然诸多 TCP 流量都不断的徘徊在锯齿的峰谷之间,显得很公平,然而对于总体的带宽,在存在没有拥塞控制机制比如 UDP 流量的情况下,TCP 的性能将急剧下降。

因此整个带宽对各个端到端协议流量的分配是不公平的。

2.2. TCP 公平性

TCP 的拥塞控制内在的实现了公平性,大家都会对拥塞快速反应。如果所有流量都是 TCP,那再好不过了,带宽可以得到完全公平分配。

2.3. 并不是所有的流量承载于 TCP

然而,还有很多协议是没有拥塞控制的,比如 UDP,大量贪婪的流量瞬间挤满整个通路,虽然大多数贪婪流量被丢弃了,然而发送端并不在乎,仍然继续快速大量发送,结果导致对丢包很在乎的 TCP 进入拥塞状态,TCP 的退出,导致 UDP 流量不再被丢弃,结果,整个通路长期保持拥堵状态,TCP 流量很难有机会再获得公平带宽,直到 UDP 流量的退出。

2.4. 需要做的工作

现在怎么办?难道为 UDP 加入拥塞控制吗?这样不妥,那样对实时要求很高的 UDP 应用影响太大,因此需要一种更为温和的方式解决这个带宽在不同类型端到端协议之间的不公平分配问题。

这种温和的解决方式就是 TCP 隧道,简而言之,就是在核心网络的容易拥堵的三层链路的两个路由器之间部署一条 TCP 隧道,封装所有的流量,包括 UDP 的流量,这样,起码在该段通路上,带宽分配公平了,特别是,如果仅仅部署一条 TCP 隧道 (所有流量通过该隧道) 的话,排除中间路由器排队 / 路由处理的影响,拥堵是不会发生的。

2.5. TCP 隧道导致的重传叠加

现在考虑一个问题,那就是 TCP 隧道之上的载荷也是 TCP 流量,这样的话,恰好 TCP 隧道上发生拥堵而丢包,这种情况下,载荷 TCP 超时也在预料之中,因此会发生重传叠加,事实上,我们知道,载荷的重传是不必要的,因为隧道是 TCP 的,所有的丢包都会得到重传。

我们可以把 TCP 隧道封装的一条网络通路想象成一个路由器,在该路由器中,只要得到排队的数据包都会安全可靠到达输出端口,在路由处理过程中不会丢包。这也许不太符合真实的路由器,因为真实的路由器中在路由处理的过程中很有可能发生错误的。既然是这样的话,我们就知道,一旦在这个虚拟的路由器中发生丢包,端 TCP 系统是不需要重传的,然而事实是,端到端的 TCP 协议永远都不知道是什么原因导致了丢包,并且更不知道丢包发生在哪里。

部署 TCP 隧道的时候需要做什么?很简单,就是尽量避免隧道的丢包,可以理解为不要让隧道和其它流量竞争带宽,那么可选的方案只有一个,就是部署 TCP 隧道的通路最好不要再有其它的流量,也就是不要拥有背景流量!

2.5.1. 设计的意义:分层模型的层间功能不能重合

TCP 是一个端到端的有连接的可靠传输的协议,它作为尽力而为的 IP 协议的载荷而存在,如果它跑在 TCP 之上的话,就会造成重传叠加,这样的话很多重传将是不必要的,因此从设计的角度看来,分层模型之间的功能最好不要重合。

2.6. TCP 隧道导致的延迟增加

部署良好的 TCP 隧道虽然对于带宽的公平性有很好的促进作用,然而却会导致单个数据包延迟的增加,这个延迟的增加具体是什么原因导致的呢?其实很简单,那就是在 TCP 隧道的入口处,TCP 分段要作为 TCP 载荷被封装,这个时间实际上完全是 CPU 完成的,这是延迟增加的原因。当然延迟虽然增加了,也不完全是坏事,后面我们会看到,通过 TCP 隧道的速率匹配,可能做到最大化吞吐量,毕竟,吞吐量和延迟总是满足反比律的。

3. 有效利用 TCP 隧道

事情并不是想象的那么坏到了极点。这是因为还有比 TCP 更坏的,几乎所有的端到端协议都比 TCP 更可恶,因为它们都比 TCP 更贪心,所有的端到端协议都以为整个带宽都是自己的,而 TCP 起码还做了拥塞控制使得整个网络用户更加公平。既然如此,我们需要有效的利用 TCP 的这点优点。

3.1. 有效利用公平性

载入拥塞控制机制的 TCP 的优点就是公平性,然而 UDP 和 TCP 们共享统一网络通路,却没有对拥塞进行反应,这样导致了网络带宽分配的严重不公,大量 UDP 流量会瞬间吃掉所有带宽。因此一个好的解决方案就是将 UDP(以及其它所有的没有拥塞控制的端到端协议数据) 封装在 TCP 隧道中。 3.2.TCP 隧道在中间路由器上增加了容量适配功能 我们知道,在单一连接的链路上,延迟和带宽的乘积表示了该链路的容量,如果能始终保持链路的满载,那无疑是对网络带宽的最有效的利用,可惜的是,端到端的 TCP 并不能做到这一点,因为端到端之间要经过无数的二层链路,经过无数的路由器,往往中间的核心网络带宽是很大的,然而端系统的 TCP 却不可能认识到这一点,最有效的措施是在核心网最容易拥堵的链路上部署一个 TCP 隧道,以及在核心网容量差别最大的两条相邻链路之中的大容量链路上部署一条 TCP 隧道,这样该 TCP 隧道就会起到容量匹配的作用。

然而事情并不是这么简单,这个 TCP 隧道的构建是有要求的,事实证明,它的出入口缓存的容量达到该链路的延迟和带宽乘积,那将是最高效的。

3.3. TCP 隧道减少了拥塞控制起作用的频率

TCP 隧道显式的减少了流的数量,在构建了两条 TCP 隧道且没有背景流量的情况下,原来 N 个流量的带宽竞争现在成了两个 TCP 的竞争。更可贵的是,原来的 N 个流量中可能还有 UDP 流量,它会吃掉几乎整个带宽的哦!现在公平了,只有两个 TCP,完全履行拥塞公平的原则。可见 TCP 隧道缩小了竞争者的数量,并且消除了贪婪者,是一个化干戈为玉帛的利器。

我们知道,TCP 隧道本身封装了 N 个流量,它们之间的带宽分配是不加权的,要想实现 N 个被封装流量带宽的加权分配,最好的办法是按照协议类型对 TCP 隧道进行一些配置,比如 UDP 流量封装在一个 TCP 隧道中,TCP 流量封装在另一个 TCP 隧道中,然后对排队规则进行配置,比如实时优先级高的 UDP 优先排队,或者同一 TCP 隧道中的统一端到端协议的不同流量按照路由器的配置进行加权排队...

3.4. 强制措施

虽然 TCP 隧道可以解决网络带宽分配不公的问题,然而并不是所有人都能部署 TCP 隧道的,作为一种实验,在实验室可以随意配置,然而作为一种实施,你就必须依赖政府和运营商。ISP 或者政府需要对整个核心网络规划十分了解,这样它们便知道在哪里部署 TCP 隧道了,更为重要的是,只有它们可以触动核心网络的配置,只有它们有权力这么做。

3.5. 部署 TCP 隧道

我们知道,只有一个 TCP 连接的链路 (也没有中间路由器) 是没有拥塞的,因此总是能期望其数据传输最终使网络满载。

因此需要在流量很杂,UDP 特别多的拥堵链路上部署 TCP 隧道,这样所有的流量就进入了一个隧道,为了避免 UDP 撑满整个隧道,有时需要部署两条 TCP 隧道,一条专门用于 UDP,另一条用于别的,这样,UDP 流量也要接受拥塞控制了。如果实现了解不用很惯着 UDP,那么就可以只部署一条 TCP 隧道,这样的话,可以消除大部分的堵塞事件。

4. 性能

4.1. Selected ACK 的意义

选择重传实现了一种机制,可以只重传丢失的 TCP 分段,并且只要不是连续丢包就不用等待超时,只要接收端都到乱序分段就会在 ack 中附带 sack 信息,这样即使 TCP 隧道的丢包也会得到快速的选择重传,这种重传仅仅是 TCP 隧道两端的,远远比端到端的链路距离要短很多,这样端系统感知到的拥塞将会大大减小,减少了端系统 TCP 重传的次数,同时也提升了发送速率,因为进入慢启动的机会少了。

4.2. TCP 隧道的缓冲区大小的意义

带宽,延迟的乘积表示了一个网络链路的容量,如果希望能获得很大的吞吐量和较小的延迟,保持整个链路容量满载是最有效的,然而要想避免拥塞,使 TCP 的拥塞控制不起作用,那么只能保持仅有一个 TCP 连接的状态,这样 TCP 的端到端的流控机制最终会使链路满载的。虽然由于可恶 UDP 流量的存在,不可能只部署一个 TCP 隧道,也就是说,最终 TCP 的拥塞控制还是会起作用,其时间 - 窗口图可能还是会出现经典的锯齿状,然而实验表明,保持 TCP 隧道入口缓冲区大小等于带宽,延迟的乘积的话,还是会得到最大的吞吐量。

可以想象,在这个场景下,TCP 隧道起到了容量适配的作用。否则,仅凭各个 TCP 端用户疯狂在网络上争抢带宽,大量带宽将浪费在大量 TCP 用户拥塞控制导致的锯齿下尖角的位置。

4.3. TCP 隧道对 IP 分片的影响

一旦由于 MTU 的影响,IP 数据报分了片,一旦分片丢失,如果其上的载荷是 TCP,那么将会导致端系统的 TCP 重传,在一条长长的且 MTU 变化很大并且还有点拥堵的三层链路上,IP 分片很容易丢失,只要丢失一个分片,IP 将不能重组,TCP 分段也会玩完,在这种链路上,这很可能发生,如果只有 1 个数据报那么其丢失造成 TCP 分段不能接收的可能为 10%,如果该 IP 数据报分了 10 个片,那么只要有一个分片丢失,那将造成同样的结局,毕竟 IP 是尽力而为的,这个信息最终会反馈到端 TCP 系统,如果在这种链路上的 MTU 突变造成分片的链路上部署一条 TCP 隧道,那么将把到达稳定链路的跳数减少为 1 跳,有效屏蔽了 MTU 突变造成的 IP 分片且丢失的影响。

记住,TCP 隧道两端的路由器和中间的链路组成了一个虚拟路由器,该路由器只要数据排队就不会丢包,是一种名副其实的工业级路由器!TCP 隧道的存在使 N 跳变成了 1 跳,好事!

5. 影响 TCP 隧道性能因素

开门见山指出,就三点

5.1. TCP 隧道的实现

这是一个很重要的因素,因为 TCP 隧道的实现有很多的方案,诸如 OpenVPN 的方式是一种很低效的实现,因为它是基于 socket 的,而 socket 是一个用户接口,它作用于端到端的系统,对于核心网行为的理解是很片面的,因此 OpenVPN 只能面对 TCP 隧道的问题而抛弃它 (但是在 udp 被封堵的时候仍然支持它),OpenVPN 始终没有认识到 TCP 隧道的优势,根据设计需求,它没有必要认识到...

更高效的实现方式是在内核协议栈中直接实现,像 IPsec 一样,对于 Linux 而言,这是很高效的,因为可在软中断中完成一切 TCP 隧道载荷的封装和解封装,避免了端系统进程调度系统带来的悲哀。

5.2. 有没有背景流量

存在背景流量将是可悲的,如果背景流量是 UDP 的话,那将是致命的,本来 N 个 TCP 流量和 UDP 是并列的,然而 N 个 TCP 流量被封装了,即使这样,单独的 UDP 流量也可能吃掉带宽,因此部署 TCP 隧道的时候,一定不要 UDP 背景流量的存在,将它们封装在一个单独的加权值比较低的 TCP 隧道中是一个好方法。只要没有背景流量,tunnel 是可靠的,一旦有背景流量,在 TCP 隧道中丢包,就会出现重复的重传叠加

5.3. 原始链路的性质

如果原始链路本来就不拥堵,那么 TCP 隧道是没有意义的,只有在原始链路很拥堵或者背景流量很大的情况下,才要部署 TCP 隧道。

5.4. OpenVPN 的问题

故事

首先列出一个网址:sites.inka.de/bigred/inde…

它是一个开源 VPN 实现 CIPE 的作者的网站,其中有一篇文章《Why TCP Over TCP Is A Bad Idea》,从其名字可以看出,在实现隧道的时候,用 TCP 封装 TCP 并不是一个好主意。如今,虽然 CIPE 已经风光不再 (它实际上从来都没有风光过,从未流行过),然而它的思想被最大限度的扩展了,其中之一就是“尽量不用 TCP 来封装 IP 隧道”。如今是 OpenVPN 的时代,在其 man 手册里,其 proto 字段的解释中,明确指出了一篇文章,旨在说明最好不用 TCP 作为封装协议,除非 UDP 不可用时才使用 TCP,该文章就是《Why TCP Over TCP Is A Bad Idea》,这篇文章很简洁,没有专业术语,写得也很通俗易懂,不妨看一下

故事的一点解释

TCP 隧道用于 OpenVPN 等用户态的 VPN 实现时,加之部署这些 VPN 的组织并没有强制措施,因此其性能严重依赖于 RTT 的测量算法,,依赖于当前网络的状态,否则网络传输可能将不可用。OpenVPN 的使用者不会部署整个核心网络的,因此他们不可能期望 TCP 隧道带来良好的收益,相反的,如果失去了强制措施,TCP 隧道是弊大于利。

通过“TCP 隧道对 IP 分片的影响”一节可以明白,在 TCP 隧道中传输大数据块 (OpenVPN 下超过 100 字节就算大了,因为虚拟网卡的动态 mtu 在某时就是 100) 的时候,最容易使得 IP 分片,一旦一个分片丢失就会导致重传叠加,就会引起问题,而对于交互式的 TCP 应用,重传叠加发生的几率很小,因此就会出现在拥有背景流量的 TCP 隧道上访问 http 会很慢,而登录 SSH 却没有问题。

记住,TCP 隧道部署的意义:

1.UDP 和 TCP 全部封装在 TCP 隧道,因为 TCP 拥塞控制具有公平性,公平性;

2. 带宽的适配。


reference

  • 安全

    互联网安全是一门涉及计算机科学、网络技术、通信技术、密码技术等多种学科的综合性学科。

    64 引用
感谢    赞同    分享    收藏    关注    反对    举报    ...