为了避免网络出现不必要的拥塞,SCTP偶联会在适当时候控制进入到网络中的数据包数量,其控制方法是端点使用慢启动和避免拥塞等算法。无论如何,慢启动和避免拥塞都会尽量增大拥塞控制窗口(cwnd),而重传或通路idle时则会试图降低拥塞控制窗口。
1. 几个相关概念
慢启动(Slow-Start)
当在一个状态不明的网络或者是空闲了相当长时间的网络上开始传送数据时,SCTP偶联会向网络探询一下以确定其可用的容量。为了达到这个目的,在开始传送数据时或者是对重发定时器检出的丢失消息的重发时,会使用慢启动算法 。一般来说,当cwnd≤ssthresh,SCTP端点就要用慢启动算法来逐渐增加cwnd。
避免拥塞(Congestion Avoidance)
当cwnd>ssthresh,如果发送方有cwnd或更多完成证实(即收到SACK)的数据,则cwnd应当在每个RTT(双向传输时间)中增加一个MTU的量,以避免拥塞。
不得不提一下三个半变量。SCTP端点使用以下几个控制变量来控制其传送速率。
接收方广播的窗口大小(rwnd,单位Byte):由接收方根据接收数据包的缓冲容量来设置(这个变量是对整个偶联而言的)。
拥塞控制窗口(cwnd,单位Byte):根据发送方观察的网络情况来调整(到每个目的地的地址即每个通路path都应维持这样一个变量)。
慢启动门限(ssthresh,单位Byte):发送方用这个阀值变量判别是处于慢启动阶段还是避免拥塞阶段(到每个目的地地址都应维持这样一个变量)。ssthresh的初始值可以是任意的大,比如rwnd。
SCTP还需要一个附加的控制变量partial_bytes_acked,这个变量用来在避免拥塞阶段帮助调整cwnd。
与TCP不同,SCTP发送方必须要针对每个目的地地址都维护后三个变量。
2. SCTP拥塞控制的三种场景
一、建立偶联后或长期空闲后,开始传送数据。此时初始cwnd较小,肯定小于或等于阀值ssthresh,则开始慢启动,适当增加cwnd。
二、cwnd慢慢变大,当cwnd>ssthresh时,如果发送方有cwnd或更多完成证实(即收到SACK)的数据,则启用避免拥塞算法,cwnd应当在每个RTT(双向传播时间)中增加一个MTU值。
三、当有未证实的数据时,会启动重传,开启拥塞控制,此时会先减小cwnd到一个较小的值,比如MTU。开始慢启动,慢慢增大cwnd。等cwnd>ssthresh时又看看要不要开启避免拥塞算法。
3. 内核中SCTP协议栈的源码分析
提高或降低cwnd的函数有两个,在文件net/sctp/transport.c中,以版本linux-3.7.1为例讲述。
一、什么时候提高cwnd
收到SACK时,在函数sctp_transport_raise_cwnd中更新path的cwnd和partial_bytes_acked。调用关系如下图所示。
上图中,sctp_transport_raise_cwnd处理了两种情况:
慢启动(rfc2960 7.2.1):cwnd += min(bytes_acked, pmtu);
避免拥塞(rfc2960 7.2.2):cwnd += pmtu(if (new_pba >= cwnd));
关键实现代码如下:
if (cwnd <= ssthresh) {
/* RFC 4960 7.2.1 slow-start algorithm */
if (asoc->fast_recovery)
return;
if (bytes_acked > pmtu)
cwnd += pmtu;
else
cwnd += bytes_acked;
} else {
/* RFC 2960 7.2.2 congestion avoidance */
pba += bytes_acked; //pba is partial_bytes_acked
if (pba >= cwnd) {
cwnd += pmtu;
pba = ((cwnd < pba) ? (pba - cwnd) : 0);
}
}
二、什么时候降低cwnd
发生重传时,或发送CWR时,或通路IDLE(实际是发送HB)时,通过函数sctp_transport_lower_cwnd更新cwnd。调用关系如下图所示。
在函数sctp_retransmit中,会重置ssthresh和cwnd:
a. T3-rtx超时,见rfc2960 7.2.3。代码实现如下所示:
transport->ssthresh = max(transport->cwnd/2, 4*asoc->pathmtu);
transport->cwnd = asoc->pathmtu;
b. 快速重传时,见rfc2960 7.2.4。代码实现如下所示:
transport->ssthresh = max(transport->cwnd/2, 4*asoc->pathmtu);
transport->cwnd = transport->ssthresh;
注意,rfc2960规定ssthresh取值max(cwnd/2,2*MTU),linux-3.7.1有所微调,慢启动的空间更大。总之,分组的丢失将基本上导致cwnd降低一半。
c.处理延迟的ECN的处理基本同快速重传。RFC 2481 Section 6.1.2。
d.通路IDLE即发送HB时,直接降低cwnd,阀值不变,见rfc2960 7.2.1。代码实现如下所示:
transport->cwnd = max(transport->cwnd/2, 4*asoc->pathmtu);