计算机网络最伟大的变革:多路径TCP
http://network.51cto.com/art/201507/484206.htm
TCP 是网络协议集中的核心协议。它将底层 IP 协议提供的不可靠数据包传输服务变为一种可靠的数据流协议。它无疑是计算机网络进化历程中最伟大的变革。
- 作者:来源:oschina|2015-07-15 11:14
TCP 是网络协议集中的核心协议。它将底层 IP 协议提供的不可靠数据包传输服务变为一种可靠的数据流协议。它无疑是计算机网络进化历程中最伟大的变革。
TCP 出现之前,计算机网络协议期望计算机可以通过网络得到一种无损可靠的服务,并一直致力于对它的研究。DECnet 的 DDCMP 就是一种无损的数据连接控制协议。X.25 是 telsaur world 为连接到的电脑所提供的一种可靠的流式协议。我还记得在 Ethernet 问世之初因它缺乏可靠的确认机制而受人指责。是 TCP 的出现改变了这一切。TCP 将所有提供可靠数据传输的功能从底层网络转移到 TCP 会话两端的电脑的共享状态中。TCP 体现了网络架构的端到端原则,即将可以由会话终端提供的功能放在底层网络中并没什么好处。TCP 需要的是一种更加简单的网络服务,在这种网络上数据包可以无序传输或丢弃,但 TCP 协议能够检测到并修复这些问题,以保证传送到终端程序的比特流与传入 TCP socket 的原始数据完全相同。TCP 协议到现在已经有
40年历史了,但这并不意味着它将在近些年被冻结。
TCP 不仅是一中可靠的数据传送流协议,而且一种采用自适应速率控制的协议。TCP 可以以一种允许协议将尽可能多的数据通过网络的模式工作。一个常见的运作模式是:一个单独的 TCP 会话会不断探索最高的可传输数据率,通过分析丢包来降低发送速率并重复探索。TCP 的这方面的是一个不断被研究的领域,在流动控制领域已经做了许多的工作,目前有许多不同的 TCP 尝试来优化各种网络环境下的流量。
其他的工作瞄准在 TCP 的数据确认上,尝试在各种各样的条件下提高算法效率。SACK 允许接收端发送回更多的信息来响应发送端的丢失。FACK 指示在慢启动时数据丢失的问题。
提高数据传输路径也是一种尝试方法,相比同时打开多个 TCP 会话,这种方式将数据分成多个部分,然后每个会话发送其中的部分。有效开放多个并行的 TCP 会话。一个 TCP 的变体,MulTCP,在一个 TCP 会话模拟多个并行的 TCP 会话的行为。这些行为为并行的 TCP 会话假设相同的端点几相同的端到端网络路径。一个使用多个并行会话的 TCP 进化,但试图通过网络以多种路径传输这些会话,这就是多路径 TCP。
多路径 TC P有过一个短暂的曝光,据透露,苹果 iOS 7为其 Siri 应用包含一个多径TCP的实现,但它有可能在移动互联网中发挥更大的作用。在这篇文章中我将更详细地探讨这个 TCP 选项,看它是如何工作,看它如何在今天的移动网络中发挥作用。
IP多址
首先我们要回到一个基本的网络概念,即寻址和地址。IP 协议里的地址与 1970 年和 1980 年通常使用的其他计算机通信协议有细微的不同。当时许多其他协议将其所使用的通信协议层地址作为主机地址,IP 协议小心的选择联想到了连接到网络接口的 IP 地址。这是在大多数情况下这是一个相对不重要的区别,当时的计算机通常只有一个单一的网络接口。但当计算机有两个或两个以上的接口连接到两个或两个以上的网络时,这却是一个重要的区别。一台 IP 主机有两个网络接口有两个 IP 协议地址,一个接口一个。在 IP 协议里它是设备间的接口,在网络里它是通信的地址端点。IP 主机会接受网络接口中接收到数据包中与自己 IP 地址匹配的包,而发送数据包时,发送数据包的源地址是其用于把数据包从主机发送到网络接口的IP地址。
这个模型的网络寻址尽可能的简单,但也存在一些操作上的问题。这种寻址的一个含义是:当一个主机有多个接口,使用 TCP 协议的应用层会话有一定“粘性”。比如,当一个网络接口已经开启一个 TCP 会话时,主机网络栈不能立马地迁移到另一个接口,因为这个会话正保持活动状态。试图通过“结束”的一个 TCP 会话而更改会话对端为另一个IP地址通常不会在原会话对端被承认。因此多接口和多个地址不会增加 TCP 连接的额外弹性。
只是简单地给每个网络接口配置唯一的 IP 地址并不满足所有应用场景的需求,而且没过多久就有了“辅助地址”了。给一台主机赋予多个地址的一种方法就是给一个网络接口配置多个 IP 地址。传输层可以给流出的数据包指定源 IP 地址,这样就不会采取默认的动作,即源 IP 地址为流出数据包所在接口的主地址。辅助地址有自己适用的场合,特别是你试图在单一平台上实现多个应用时尤其如此,不过,在 IPV4 环境下,辅助地址是针对特定需求而提出的特定解决方案,而不是通用方案。
使用 TCP 的应用还与初始化 TCP 握手时所采用的 IP 地址“紧密关联”,因此会话不可能在同一接口上的辅助地址间相互转换。
IPv6的寻址方式稍有不同。IPv6协议从诞生那一刻起就允许对单个接口指定多个IPv6单播地址,而且没有主次之分。IPv6协议还引入了“地址适用范围”这一理念,这样可以确保一个地址要么在本地连接网络中是唯一的,要么是一个全局地址。基于隐私方面的考虑还引入了永久地址和临时地址,为了支持移动性还引入了“最终地址”和“转发地址”。
IPv6从某种程度来说只是对原来IPv4地址模型作了表面上的修改。如果一个IPv6主机有多个接口,那么每个接口都拥有一组IPv6地址。而且如果TCP会话在一对地址上启动后,那么在这个TCP会话期间,TCP就无法转换到另一对地址上。在一个网络接口上启动的TCP会话与这一网络接口紧密相关,不管这个接口配置的IPv4地址还是IPv6地址都是如此。
随着移动互联网的引入,互联网已经发生了显著的变化,多路径的讨论主题也转移成了许多移动相关的问题。移动设备上附着着许多 IP 地址。蜂窝无线接口上也有 IP 地址集。这些“智能”设备中许多也有 WiFi 接口,也有可能有 IPv4 和 IPv6 地址。还可能会有具有 IP 地址的蓝牙接口,兴许还有一些 USB 的网络接口。当启用时每一个网络接口都需要其本地 IP 地址。我们现在所处的互联网中设备具有多个可用 IP 地址是相对较为普遍的。但我们如何利用这些地址呢?
在大多数情况下使用多地址是不值当的。常见的习惯是每一个新的会话是针对特定接口的,给会话的出站地址是通过本地策略决定的。但是,当我们开始考虑应用绑定的位置和标识时是非常多变的,网络连接是瞬态的,连接的成本和容量是不同的,因而现今通常的情况是这样的,移动蜂窝无线电服务和 WIFI 漫游服务会话,拥有一定数量的敏捷性跨网络转换是一个重要的因素。
如果一个端到端的会话能使用多地址,而且以此推理也能使用到多接口。那么应用就可以在移动数据链路和WiFi之间进行无缝数据传输,或者可以同时利用两链路进行传输。假如接口的 IPv4 地址和 IPv6 地址是等同地位的,那么在两种协议之间进行无缝地数据传输也是可实现的。至于在某个时刻使用哪种传输服务不再由移动运营商或者 WiFi 运营者,或者设备,或者运行的操作系统决定。如果应用能够使用到多地址,多协议和多接口,那么应用自身就可以根据各个连接是关闭还是可用状态来决定怎样选择连接才能最好的满足自身需求。同时,由于已分配到频谱的传统的移动运营商和未分配到频谱的WiFi运营商之间就获取未分配频谱的争论将会不断升温,自然“WiFi 传输“到底是由设备还是应用来实现就会随着事态的发展而变化。最终会改变传输功能的控制者。多地址 TCP 和多路径 TCP 是对这种争论的一种备受关注的响应:让应用自身来决定多连接环境下该如何选择。
SHIM6
在IP上利用多地址的第一次尝试就是在IPv6上进行的SHIM6。
在这种情况下,目的就是在多外部连接的端站点的弹性,而且约束是避免对端站点使用独立路由的IPv6地址前缀。因此这就是在没有路由碎片的情况下支持站点多宿主所做的努力。为了理解SHIM6模式,我们需要从不能独自提供独立IPv6地址前缀的端站点开始,但是要连接到两个或以上,上游的运输提供者,它们每个都能对端站点提供地址。在IPv4中,经常看到与网络地址解析器(NATs)相连的场景。IPv4中,站点是内部地址所使用的专用地址前缀,而且接口到每一个能提供NAT的上游提供者。出站的包为了使用地址要重写源地址,这是作为转换NAT提供者前缀的一部分。提供者提供用于朝向各的NAT内部路由策略的情况。虽然在IPv6中使用IPv6ULA作为外部地址以及NAT IPv6对IPv6设备到每一个上游的服务提供商的配置可能相似,一个隐藏在IPv6和大量增长的地址空间背后的概念就是消除NATs。如何才能使IPv6端站点成为多个上游服务提供商的宿主,而不需要在域间路由表或避免任何形式的网络地址转换中通知使用更具体的路由条目?
传统意义上的 IPv6 架构是指,单个站点接收到每个上游服务提供商分发的站点前缀,再由接口路由广播到站点内部。站点内部的主机接收到路由广播,对每个站点前缀完成 UPv6 地址的接口配置。多宿主配置给站点提供了更多的灵活性。假如站点与一个服务提供商的连接失效了,如果站点在每一层都以独立组件的方式实现了多宿主配置,便可以重新建立其他可用的连接。实施了这种路由系统方案之后,如果一个上游服务提供商无法提供解析到一个特定地址的服务,便可以不中断的连接到另外一个可以提供实时端对端会话的上游服务提供商。
SHIM6 尝试以基于主机的方式,使用额外的本地 IPv6 地址建立到达目的地址的潜在链路。如果与远端主机的通讯失效了(接收不到远端主机发送的报文),一个解决方案就是本机上的 ip 层 shim 切换使用另外一个(源/目的地址) 地址对。在一个或多个实时会话中,本地 SHIM 模块包含了一个网络地址转换函数,防止地址的变化影响到上层传输协议层。当电缆上传输的地址对发生变化之后,shim通过网络地址转换函数对上层传输协议层隐藏了地址对的变化,所以对上层传输协议层来说地址对一直是固定的。
这个解决方案本质上就是把 NAT 函数应用到主机的 ip 协议栈上。从设计的角度来说,它避免了对 TCP 和 UDP 的修改,保证了传输会话中使用的 ip 地址保持不变。也就是说,如果需要更换路由链路,又要保证传输层的 ip 地址保持不变,就必定会用到地址转换技术。IPv4 使用基于网络的 NATs 技术发送响应,不同的是,IPv6 在每台主机上都应用了 NAT 技术,通过这种方式 SHIM6 试图把 NAT 技术进一步推向“幕后”。
然而 SHIM6 并不是一个能让所有人都满意的解决方案。
网络运营商对于把决定权交给独立的主机的做法表达了强烈的质疑。网络运营商希望在他们的网络中控制连通结构,进一步来说,就是像路由系统一样提供流量的网络控制服务。SHIM6 的目标是让站点从上游获取 IPv6 地址前缀,避免主机路由表变得更加臃肿,但同时也降低了站点的“独立性”。尽管对于这点表示赞同,运营商还是反对把连接的选择和控制权交还给独立的主机终端系统。
除此之外,SHIM6 还遇到了另外一个多宿主的问题。备用链路不仅在当主链路不可用的时候可以起到作用,更为重要的是,可以通过共享配置来使用备用链路。但是,在这里 SHIM6 遇到了问题。SHIM6 部署在在 IP 层,它不能直接区分数据包的先后顺序。在一个会话中,一端的 SHIM 单元可以把一组数据包通过不同的链路发送出去,在远端对应的 SHIM 单元会把数据包按照抵达的顺序,而不是原始的数据包顺序,传递给上层的传输层。如果 SHIM 使用了多条链路,这种乱序分发会对 TCP 造成严重的问题。最好的解决方法是为每个会话提供一个主备链路方案,每个会话总是使用主链路来传递数据。
最后我们得出一个很严峻的结论,目前在网络中大多数使用不同潜在链路传递流量的地方还只是局限在传输层。我们需要进一步的推进 SHIM6 的发展,重新审视 TCP 的发展前景。
多通道 TCP
与 SHIM6 相比,在传输层合并多个 ip 地址的解决方案在协议栈方面更深入了一层,这是一个端对端的机制,由2个终端主机一起维护共享多样的状态。
Multipath TCP (MTCP) 的基本原理和 SHIM6 类似,初始双方会交换信息来确保双方都支持这个机制,允许双方使用额外的链路或者通道。不同的是,SHIM6 会在主链路不可用的情况下才会使用备用链路,而 MPTCP 在应用允许的前提下,可以立刻使用这些链路来完成通信。
一个关于 MPTCP 的关键猜想是从 SHIM6 借鉴而来,主机上的多重地址有助于实现网络中多重链路技术。一条使用多个地址的端对端链路,大致上和同时开启多个 TCP 对话是等效的。
Multipath TCP 的基本思路是把发送的流量切分为更多的子流量,每个子流量建立一个单独的端对端会话,然后在远端把接收的子流量重新整合成单个流量。如图1所示。
图 1: 标准 TCP 和 MPTCP 协议栈比较
这实质上是在 TCP 模块中插入了一块“木条”。MPTCP 可以像 TCP 一样的模式工作,生成多个子流量,然后把数据分配给单独的子流量,这种机制对于上层应用程序来说是不透明的。应用程序可以通过 API 在链路池中添加和删除地址,但不能直接管理和操作 MPTCP。MPTCP 保证了低层级的 TCP 组件不会受到影响,即 MPCTP 子流量是传统的 TCP 流量。从数据发送者的角度来说,MPTCP 把来自应用程序的数据流切分成了一个个数据块,再把单个数据块封装到了单个子流量中。从数据接收者的角度来说,MPTCP 收集了 TCP 子流量里的数据块,并且重新组装成原始数据流,并传递给本地应用程序。
MPTCP运行原理
在 TCP 头部有一个大小为40字节的选项表示数据偏移量。如果数据偏移量的值大于5,就会使用 TCP 头部的最后32位字符(校验和指针)和数据的前8位之间的空间。MPTCP 会使用这个选项,并且所有的MPTCP 信号都会包含在这个选项字段里。
当打开一个会话的时候,主机会向远端主机会发送一个 TCP SYN 消息,在 MPTCP 选项字段里包含了一个MP_CAPABLE 信号。如果远端主机也支持 MPTCP,远端主机会返回一个 SYN+ACK 响应,同样在MPTCP 选项字段里包含了一个 MP_CAPABLE 信号。会话使用了 ACK 和 MP_CAPABLE 信号完成 TCP 和 MTCP 握手过程,确保两端都得到了对方的 MPTCP 会话数据。在整个会话过程中,两端交换了 64 位字节的会话密钥,同时各自生成一个 32位的哈希共享密钥。两个主机之间随后使用子链路的时候会用到这个共享密钥。
进一步来说,在 MPTCP 会话里,可以通过附带 MPTCP 字段的 TCP SYN 交换来生成 TCP 子流量,MPTCP 字段包含了一个 MP_JOIN 值。MP_JOIN 包含了接收端的哈希共享密钥和原始会话的 token 值,这样两端就能将新生成的 TCP 会话就能和原始会话关联起来了。另外 MP_JOIN 还包含一个随机数,用来防止重放攻击。MP_JOIN 字段包含了发送端的地址,即使地址值被 NAT 转换了,两端还是能获得对方的原始地址。会话两端能在任意端口生成 MP_JOIN 值,也就是说,使用服务器的 80 端口开始会话后,便可以在任意端口对上建立子流量,而服务器无需监听新端口。新的子流量包含 5 个元素(协议,源地址和端口号,目的地址和端口号)。两端可以通过发送 ADD_ADDR 消息告知对方新的地址,同样可以通过发送 REMOVE_ADDR 删除地址。
TCP 子流量除了使用传统的 TCP 信号,MPTCP 增加了一个数据序列信号(DSS),标示了 MPTCP 会话中子流量的数据流状态。发送端的序列号包括一个总的序列号,以及标示单个子流量的序列号。DSS ACK序列号是接收端接收到的有序数据的集合。另外,子流量会用到 SACK。
为了防止出现子流量数据丢失后造成阻塞的情况,发送端必须再使用另外一个子流量重发数据。子流量使用的是常规的 TCP 排序算法,一个不稳定的连接可能会造成子流量失效。这时,MPTCP 可以选择另外一条子流量重新发送数据。如果子流量依然失效,MPTCP 可以使用 TCP RST 重置该子流量。
单个子流量是被传统的 FIN 消息 TCP 交换或通过 TCP RST 消息停止的。 在 MPTCP 选项空间,作为数据顺序信号的数据 FIN 消息决定 MP-TCP 会话的关闭。
拥挤控制看起来仍然是 MPTCP 的一个未解决问题。一种实验性的方法是结合每个子流量的拥挤时间窗,以及根据 RTT 间隔以线性的速率增加总时间窗的总量,和对最大的时间窗的子流量采用最大的增量。采用这样的方式,总的流量也不比在最佳可用路径上的单个 TCP 会话差,并且单个子流量都占用的路径是相同份额的。其他可能减少各子流量耦合的方法也会被考虑。
MPTCP 和中间设备
如今的互联网充斥着各种各样的中间设备,比如 NAT 设备,负载均衡器,代理,过滤器,防火墙等。这就意味着使用不同的 IP 策略,不同的中间设备就会遇到各种各样的问题。
对于MPTCP 来说,一个重要的问题就是一些中间设备会修改 TCP 报文的字段值。
这个问题主要存在于 ADD_ADDR 消息和 NAT。如果想在一个 NAT 连接上传递 ip 地址,结果总是会失败,MPTCP也不例外。MPTCP 没有内置 NAT 识别函数,所有没办法探测到是否存在 NAT 设备。本地主机向远端主机发送报文的时候会带上自己的 ip 地址,但是 NAT 设备会对 ip 地址做转换,远端无法获取真实的ip地址,除非不经过 NAT 设备直接从本地主机发起 TCP 连接。
当存在NAT设备时,一个简单有效的方法让主动发起会话的主机创建子流量连接。这就意味着在一个Client/Server模式中,最好由客户端发起子流量连接。当然,没有NAT设备的时候就没有这些限制了,两端都可以发起子流量连接,发送ADD_ADDR消息将新的链路告知对方。这个方法对于MPTCP本身来说是无关紧要的,但是对于一个用到 MPTCP的应用程序来说是意义重大的。
MPTCP的一些启示
MPTCP使用额外的连接选项带来了 灵活性。
所有的TCP子流量都会带上MPTCP 选项,可以共享MPTCP状态。子流量没有主次之分,会话发起时被创建,会话结束时被销毁。子流量是IP协议无关的,可以同时存在IPv4和Ipv6协议连接。在多条网络链路上使用子流量实现了负载共享,实现了应用程序的主备链路控制,带来了更多的灵活性。
当应用到移动设备上时,又出现了一些意想不到的情况。我总是说我的移动设备不能做到“实时切换”。在蜂窝网络上发起的连接只能存在于蜂窝网络上,同样的在 WIFI 网络上发起的连接只能存在于WIFI网络上。实时会话不能跨网络连接。我发现当我的手机连上 WiFi 时,会优先使用 WIFI 而不是 4G,来建立新连接。这也符合实际情况,使用 4G 连接的边际成本比 WIFI 连接高1到1000倍。当 MPTCP 代替 TCP 时会出现什么情况?应用程序会使用所有可用的连接来建立子流量,会产生更多的流量。
但是,像往常一样,它总是会变得更加复杂。如果 WiFi 网络是一个企业服务,伴有 NAT,水平分割 VPN 和各种安全服务器有该怎么样?如果我的设备开始在这样的背景下进行 MPTCP,那么到什么程度是我的 WiFi 连接的属性在蜂窝数据连接保留?我曾这样公开新漏洞。如何一个虚拟接口,如 VPN,告知 MPTCP 感知应用程序,而其他接口不在同一安全域的 VPN 接口中?
然而,MPTCP 似乎在无缝切换的 WIFI 中发挥作用。在 MPTCP 下,一个移动手机进入 WiFi 服务地区以及 WIFI 无线子流到现有的数据传输,而不停止和重新启动的数据流的情况是有可能的。应用程序可能会在 WIFI 子流处于激活时关闭蜂窝子流。这项功能是在使用 MPTCP 的应用程序的控制之下,而不是在载体主机操作系统的控制之下。
向上堆栈(Stack)
显然不能停止在传输层使用 MPTCP。自定义的应用他们自己会做这件事。
举例来说,“mosh”应用是一串灵活的地址,会话状态是被加密分享的,服务将会接收一个来自任何客户端 IP 地址的重连接,只要客户端可以证明它知道被加密的分享。
在应用层上配置负载均衡,扩展 TCP 数据传输模型支持多个活动的 TCP 会话也是可能的,这在形式上与 MPTCP 没什么差异。
当然还可以更进一步,而不是使用多路径 TCP 会话,同样的两个末端点,你可以跨越多个末端点来代替共享的相同服务器上的数据,并且在多个服务器上也可以使用多路径 TCP 会话。这个时候,这东西看起来就非常像分布式点对点架构。
另一种方法是将数据流格式化到消息,同时在两个交换系统中允许多个消息通过不同的路径发送。这个方法,SCTP 和 MPTCP 虽类似,但SCTP高明在多地址对多路径的支持。它把 UDP 消息事务的特性和 TCP 可靠的队列传送服务很好的结合在一起。如今网络中的问题,不在于 TCP 或 UDP,而是许多中间件,包括 NATs,经常”敌视“ SCTP 时不时丢失 SCTP 的数据包。成为当今网络中的一个中间件增长的主要成本。当今网络协议模型的革新受制于网络中间件相对狭窄的规则,相似地 thumb 规则(译者注:一个简单的解释thumb规则的链接)在如今的网络中成为了 TCP,UDP 和中间件的”饲料“。
(我)已经很多次注意到网络协议栈的抽象是有些任意的,并且在参考栈的不同层级上都有可能实现相同的需求。在因特网多路径支持的方案中,我们看到过很多的方法,有在数据链路层,IP层,在路由层,在传输层,在应用层上探索并行数据流传输方案。每种方案都各有优劣。但是使我担心的是你是否会碰到同时采用了所有方案的情形?这会是超高效呢,或者是令人崩溃的复杂性呢?