mptcp

466 阅读15分钟

简介

mptcp全称是multipath tcp,是对tcp的扩展演进,允许通信双方同时建立多条tcp链接进行数据传输,充分利用多条路径从而提高吞吐(聚合)或者提高可靠性(冗余)。

mptcp在socket和tcp层之间增加了一个mptcp层,mptcp层之下是多个tcp子流,mptcp层负责多个tcp子流的建立、管理、数据分发和组装,tcp子流就是普通的tcp。

用MPTCP就比较爽了,视频数据既可以走3g通道,也可以走wifi通道,两条腿走路,总是比一条腿来得靠谱和高效。

MPTCP允许在一条TCP链路中建立多个子通道。当一条通道按照三次握手的方式建立起来后,可以按照三次握手的

方式建立其他的子通道,这些通道以三次握手建立连接和四次握手解除连接。这些通道都会绑定于MPTCP session,

发送端的数据可以选择其中一条通道进行传输

兼容性

MPTCP的兼容性考虑主要基于两个方面,一个是应用程序的兼容性,另外一个是现有网络结构的兼容性

仔细分析上图可以看出MPTCP针对兼容性所给出的解决方案

  • 应用程序是基于socket开发的,应用程序对于socket之下使用普通TCP还是MPTCP是没有感知的。也就是说应用程序啥都不用改,只要升级了网络底层,就能利用MPTCP带来的诸多好处。
  • MPTCP可以有很多subflow,他们都是普通的TCP连接。对于网络传输链路中的诸多中间件,比如路由器、防火墙、NAT等,他们需处理的依然只是TCP连接,从而保证了对现有网络结构的兼容性。这一点非常重要,因为如果需升级中间件才能支持MPTCP的话,可以说协议在设计之初就已经失败了。

如何使用MPTCP

服务端支持

客户端

协议介绍

实现结构

MPTCP的实现主要分为三部分:

  • master subsocket:是一个标准的sock结构体用于TCP通信。
  • Multi-path control bock(mpcb):提供开启或关闭子通道、选择发送数据的子通道以及重组报文段的功能
  • slave subsocket:对应用程序并不可见,他们都是被mpcb管理并用于发送数据。

协议操作

MPTCP本身的工作方式与传统TCP非常相似。在三路握手时,双方交换MP_CAPABLE选项标明双方都支持MPTCP,之后双方便都可以主动创建subflow,并且互换 密钥 以防止攻击者通过子连接注入攻击。新的subflow可以通过带有MP_JOIN标志的 SYN 报文建立,也可以在任意时刻关闭。在双方有多个subflow时,发送者自行决定使用哪个subflow发送数据,也可以将包裹拆分为多块交由多个subflow同时发送。

由于MPTCP提供与TCP相同的 套接字 接口,任何使用标准的TCP应用程序可以应用于MPTCP协议,以实现同时利用多个子连接传播数据。

虽然与标准TCP工作方式很像,但是,MPTCP的核心思想是定义一种在两个主机之间建立连接的方式,而不是在两个接口之间(例如标准TCP)。在标准TCP中,连接应在两个IP地址之间建立。每个TCP连接由四元组来标识。鉴于此限制,应用程序只能通过单个连接创建一个TCP连接,因此会出现两个主机之间虽然可能同时建立了多个连接,但同一时刻只有单个连接被某个应用利用,而 Multipath TCP则允许连接同时使用多个路径。为此,MultipathTCP在每个需使用的路径上创建一个称为subflow的TCP连接

参数设置

因为MPTCP协议需兼容TCP协议,因此MPTCP的协议格式是和TCP协议一样的,MPTCP主要在TCP的Options字段作了扩展。

MPTCP现在已经是IETF标准了,其详细细节可见RFC 6824。

TCP协议的格式如下:

MPTCP在Options字段下作了一些扩展,具体扩展了哪些Options字段呢?

通过这些字段,MPTCP协议解决了数据包在多条网络路径上传输的诸多问题,他们包括但不限于:

  • 同一连接下网络路径加入和删除
  • 多网络路径数据传输序列号不连续的问题
  • 多网络路径数据传输的拥塞控制策略

Value

Symbol

Name

0x0

MP_CAPABLE

Multipath Capable

0x1

MP_JOIN

Join Connection

0x2

DSS

Data Sequence Signal

0x3

ADD_ADDR

Add Address

0x4

REMOVE_ADDR

Remove Address

0x5

MP_PRIO

Change Subflow Priority

0x6

MP_FAIL

Fallback

0x7

MP_FASTCLOSE

Fast Close

0xf

(PRIVATE)

Private Use

0x8~0xe之间的字段目前还没有分配

连接建立

MPTCP的第一个子通道的建立遵守TCP的三次握手,唯一的区别是每次发送的

报文段需添加MP_CAPABLE的的TCP选项和一个安全用途的key。

MPTCP中关于MP_CAPABLE的定义如下:

Subtype的定义如下:

而下图是建立其他的子通道:

第二条子通道的建立依然遵守TCP的三次握手,而TCP选项换成了MP_JOIN。

而token是基于key的一个hash值,rand为一个随机数,而HMAC是基于rand的一个hash值

数据的发送和接收MPTCP可以选择多条子通道中任意一条来发送数据。MPTCP如果使用传统的TCP的方式来发送数据,将会出现一部分包在一条子通道,而另一部分包在另外一条子通道。这样的话,防火墙等中间设备将会收到TCP的序号跳跃的包,因此将会发生丢包等异常情况。为了解决这个问题,MPTCP通过增加DSN(data sequence number)来管理包的发送,DSN统计总的报文段序号,而每个子通道中的序号始终是连续。

MPTCP的接收包过程分为两个阶段:第一、每个子通道依据自身序号来重组报文段;第二、MPTCP的控制模块依据DSN对所有子通道的报文段进行重组。

  • data transfer [2]

  • connection release [2]

Global sequence number and subflow sequence number

因为两个路径通常具有不同的延迟特性,所以将不会按顺序接收通过两个子流发送的数据段。常规TCP使用每个TCP数据包报头中的序列号将数据放回原始顺序。一个简单的多路径TCP解决方案是按原样重用此序列号。不幸的是,这种简单的解决方案会给一些现有的中间盒(如防火墙)造成问题。在每个路径上,中间盒只能看到一半的数据包,因此它将观察到TCP序列空间中的许多间隙。测量结果表明,某些中间盒在遇到TCP序列号空白时会以奇怪的方式做出反应,例如一些会丢弃乱序段,而另一些则会尝试更新TCP确认以“弥补”某些差距。在路径上使用这种中间盒时,多路径TCP无法安全地发送TCP序列号空间中带有间隙的TCP段。另一方面,多路径TCP也不能发送所有子流中的每个数据段。那将浪费资源。

为了解决此问题,多路径TCP使用其自己的序列空间。多路径TCP发送的每个段都包含两个序列号:常规TCP头中的子流序列号,以及TCP选项中携带的附加数据序列号(DSN)。此解决方案确保在任何给定子流上发送的段具有连续的序列号,并且不会破坏中间框。然后,多路径TCP可以在一个路径上发送一些数据序列号,而在另一路径上发送其余数据;旧的中间盒将忽略DSN选项,多路径TCP接收器将使用它来对字节流重新排序,然后再将其提供给接收应用程序。

拥塞控制

Multipath TCP目前已经定义了多个拥塞控制机制。它们与传统的TCP拥塞控制方案的主要区别在于,它们需对不同路径上的拥塞做出反应,而不会对单路径TCP源产生不公平的影响,这些TCP源可能在其中一条路径上与它们竞争。 Linux内核中的Multipath TCP实现目前支持四路多径TCP拥塞控制方案。

  • 连接增加算法
  • 机会连接增长算法
  • 基于 wVegas 延迟的拥塞控制算法
  • 平衡连接增长算法

MPTCP中拥塞控制的设计需遵守以下两个原则:

第一:MPTCP和传统TCP应该拥有相同的吞吐量,而不是MPTCP中每一条子通道和传统TCP具有相同的吞吐量。

第二:MPTCP在选择子通道时应该选择拥塞情况更好的子通道。

特点小结

在RFC 6824中定义的不同的协议操作的目的是:

  • 处理何时以及如何添加/删除路径(例如,有的连接由于拥塞控制而被丢失了)
  • 与传统的TCP硬件兼容(例如,某些防火墙,如果序列号不连续,可以自动拒绝TCP连接)
  • 定义不同链路和不同主机之间的公平拥塞控制策略(特别是不支持MPTCP的主机)

MPTCP为TCP传输增加了新的机制:

  • 子流系统,用于收集多个标准TCP连接(从一个主机到另一个主机的路径)。子流在TCP三次握手期间被识别。握手后,应用程序可以添加或删除一些子流(子类型字段分别为0x3和0x4;
  • MPTCP DSS选项,包含数据序列号和确认号。这些序列号和确认号允许没有任何损坏地以原始
  • 顺序从多个子流接收数据(子类型字段为0x2);
  • 一份修改过的重传协议,以处理拥塞控制,增强数据传输可靠性;

tcp连接时,一直以来都是只能绑定一个ip地址,但是随着多网卡主机越来越多,从一个主机到另一个主机往往都会有多条链路可以到达,这种情况下,如何充分利用这多条链路进行并行的传输或者作为链路备份。

多路径流量的调度和拥塞控制。

多路径的流量调度是指对于存在的多个子路径,如何分配流量到各个子路径,以达到尽可能提高带宽的目的。但另一方面,又需对各个子路径进行统一的拥塞控制,当一条链路上的流量发送拥塞时,把流量导到另一条链路上去。

路径发送失败后的重传。

既然依然是可靠通信,那么就会涉及到失败后的重传问题。重传时自然就要找到对应的序列号,然而,在多路径传输时,本来序列号连续的包可能被调度到不同的链路中发送,导致出现一个问题:在每个链路中发送的包的序列号不是连续的,在网络传输中可能会被网络安全设备拦截下来。所以需解决重传时的序列号的问题。

建立和管理子路径。

对于传输时的多路径,需对路径进行管理,以便能够知晓链路情况,在链路被移除时,通知对端不再使用这条链路。在链路添加时,使能对应的链路。

内核实现

初始化一个连接

初始化时,是通过SYN,SYN/ACK,ACK报文交互后完成的,在这些报文的tcp选项中,带有这一端使能的标志---MP_CAPABLE。同时也会传递一个生成的64位的key值来标示这条连接,在后面有新的子流添加到这条连接时,就会用来进行鉴权(确切说是以这个key生成的token)。

MPTCP利用TCP的三次握手进行了KEY信息的交换。MPTCP在客户端上发送SYN包的调用情况如下:

关键函数为mptcp_syn_options对MPTCP选项的填充,源码如下:

/MTK6833_S_Master/source/vnd/kernel-5.10/net/mptcp/options.c
bool mptcp_syn_options(struct sock *sk, const struct sk_buff *skb,
		       unsigned int *size, struct mptcp_out_options *opts)
{
	struct mptcp_subflow_context *subflow = mptcp_subflow_ctx(sk);


	/* we will use snd_isn to detect first pkt [re]transmission
	 * in mptcp_established_options_mp()
	 */
	subflow->snd_isn = TCP_SKB_CB(skb)->end_seq;
	if (subflow->request_mptcp) {
		opts->suboptions = OPTION_MPTCP_MPC_SYN;
		*size = TCPOLEN_MPTCP_MPC_SYN;
		return true;
	} else if (subflow->request_join) {
		pr_debug("remote_token=%u, nonce=%u", subflow->remote_token,
			 subflow->local_nonce);
		opts->suboptions = OPTION_MPTCP_MPJ_SYN;
		opts->join_id = subflow->local_id;
		opts->token = subflow->remote_token;
		opts->nonce = subflow->local_nonce;
		opts->backup = subflow->request_bkup;
		*size = TCPOLEN_MPTCP_MPJ_SYN;
		return true;
	}
	return false;
}

使能一条新的子流

在初始化一个连接以后,在有新的子流需要添加时,是在SYN,ACK报文的tcp选项中的MP_JOIN子段标示的,带有要加入哪个连接。

子路径选择

支持MPTCP的链路中存在多条子路径,因此在发送数据时需要选择最优路径来进行操作。

MPTCP利用内核通知链对MPTCP中各子路径进行增加路径、删除路径、修改路径优先级的操作。MPTCP根据相应的策略进行路径选择。

发送和接收数据

MPTCP在发送数据方面和TCP的区别是可以从多条路径中选择一条路径来发送数据。MPTCP在接收数据方面与TCP的区别是子路径对无序包进行重排后,MPTCP的mpcb需要多所有子路径的包进行排序

+-------------------------------+
                                   |           Application         |
      +---------------+            +-------------------------------+
      |  Application  |            |             MPTCP             |
      +---------------+            + - - - - - - - + - - - - - - - +
      |      TCP      |            | Subflow (TCP) | Subflow (TCP) |
      +---------------+            +-------------------------------+
      |      IP       |            |       IP      |      IP       |
      +---------------+            +-------------------------------+


      Figure 1: Comparison of Standard TCP and MPTCP Protocol Stacks

数据序号映射(Data Sequence Mapping)

由于所有的数据会通过不同的子路径发送,在接收端MPTCP需要对数据进行重新排序。因此需要数据序号映射。数据序号映射定义从子路径序列空间到数据序列空间的映射。子路径的序列空间是子路径自身的序列号,而数据序列空间维护着所有需发送的数据。如下图

红色子路径上的子路径序号分别是1、2,其数据序号是1000、1002。而下面的蓝色的子路径上的子路径序号和数据序号分别是200,1001。这说明从下面的蓝色子路径已经发送了199个报文,而上面的红色子路径才开始发送。

在MPTCP协议定义如下:

1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +--------------------------------------------------------------+
     |                                                              |
     |                Data Sequence Number (8 octets)               |
     |                                                              |
     +--------------------------------------------------------------+
     |              Subflow Sequence Number (4 octets)              |
     +-------------------------------+------------------------------+
     |  Data-Level Length (2 octets) |        Zeros (2 octets)      |
     +-------------------------------+------------------------------+

数据接收中的重组

内核使用三种队列接收数据,分别是:Backlog queue(sk->backlog)、Prequeue queue(tp->ucopy.prequeue)和 Receive queue (sk->receeive_queue)。MPTCP的实现增加了一个新的队列out-of-order queue对于各个子路径收到的数据进行重组

接收端窗口值

在TCP协议中影响数据发送的三个因素分别为:发送端窗口值、接收端窗口值和拥塞窗口值。

本文主要分析MPTCP中各个子路径对接收端窗口值rcv_wnd的处理。

接收端窗口值的初始化

在内核实现中,针对连接请求分为两个步骤处理:

  • SYN队列处理:当服务端收到SYN时,此连接请求request_sock将被存放于listening socket的SYN队列,服务端发送SYN/ACK并等待相应的ACK。
  • accept队列处理:一旦等待的ACK收到,服务端将会创建新的socket,并将连接请求从listening socket的SYN队列移到其accept队列。

当服务端进入LINSTEN状态后,收到第一个SYN包后的处理流程如下:

数据重发

TCP使用定时器函数tcp_retransmit_timer进行数据重发,MPTCP需要重发数据时,

不仅仅在原路径发送数据,而且会在另外一条子路径进行重发。这样考虑的原因是:

考虑网络中间件设备的影响, 保证子路径上数据序列号的完整性。目前的版本0.89依然如此实现,以后应该会优化。

MPTCP的结构如下图所示:

如上图所示:每一个slave subsock 和 master subsock实际上维持着一个正常的TCP链路,因此,他们都具有

重发定时器tcp_write_timer。MPTCP实现的思路就是:每个子链路发送失败的时候,将发送失败的SKB拷贝一份

到meta sock。然后让meta sock 再次选择另外一条子路径发送

如下是对tcp_retransmit_timer的修改:

拥塞控制

MPTCP的拥塞控制对TCP的拥塞控制的线性增加阶段进行了修改,而慢启动,快速重传、

快速恢复都没有改变。每条子路径拥有自己的cwnd,MPTCP的拥塞算法主要关心cwnd的改变。

拥塞算法设计原则

MPTCP的Throughput 要达到MPTCP中所有子路径中最好的一条路径

MPTCP应该和普通TCP一样从共享资源中获得相同资源

MPTCP中的流量将从拥塞的子路径转移到不拥塞的路径。

快速关闭

因为一条tcp通过发送RST报文,只能关闭一条子流,所以,为了能够快速的关闭所有的连接,使用快速关闭控制报文可以达到这个目的。