慢启动阶段的拥塞窗口增长函数tcp_slow_start如下,拥塞窗口以报文数量表示,参数acked表示当前ACK报文确认的数据包数量。如果拥塞窗口增加acked数量之后小于慢启动阈值ssthresh,使用二者相加结果作为新的拥塞窗口值。内核没有使用RFC3465中定义的ABC(Appropriate Byte Counting)算法,其最初设计的是针对按照字节表示拥塞窗口的系统。内核中规定了对数据报文的确认必须是对整个报文的确认,也可抵御ACK Division攻击。
u32 tcp_slow_start(struct tcp_sock *tp, u32 acked)
{
u32 cwnd = min(tp->snd_cwnd + acked, tp->snd_ssthresh);
acked -= cwnd - tp->snd_cwnd;
tp->snd_cwnd = min(cwnd, tp->snd_cwnd_clamp);
return acked;
}
如果当snd_cwnd增加acked值之后,大于慢启动阈值ssthresh时,将拥塞窗口CWND设置为ssthresh的值,以上函数tcp_slow_start将返回进入拥塞避免阶段接收到的ACK报文数量,交由拥塞算法处理。
ACK确认数据报文数量统计
如下函数tcp_clean_rtx_queue,遍历重传队列,计算当前ACK报文确认的数据报文数量(acked_pcount),SlowStart使用此数量决定拥塞窗口,不同于协议中规定的ACK报文数量,所以,对于Stretch-ACKs,内核当其为多个背靠背的ACK报文。
static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack,
u32 prior_snd_una, struct tcp_sacktag_state *sack)
{
for (skb = skb_rb_first(&sk->tcp_rtx_queue); skb; skb = next) {
struct tcp_skb_cb *scb = TCP_SKB_CB(skb);
const u32 start_seq = scb->seq;
u8 sacked = scb->sacked;
/* Determine how many packets and what bytes were acked, tso and else */
if (after(scb->end_seq, tp->snd_una)) {
if (tcp_skb_pcount(skb) == 1 || !after(tp->snd_una, scb->seq))
break;
acked_pcount = tcp_tso_acked(sk, skb);
if (!acked_pcount) break;
fully_acked = false;
} else {
acked_pcount = tcp_skb_pcount(skb);
}
if (sacked & TCPCB_SACKED_ACKED) {
tp->sacked_out -= acked_pcount;
} else if (tcp_is_sack(tp)) {
tp->delivered += acked_pcount;
...
}
if (!fully_acked)
break;
在如下的SACK处理函数tcp_sacktag_one中,如果当前数据报文没有被SACK确认过,此为第一次被SACK确认,将确认报文数量增加到套接口变量delivered中。
static u8 tcp_sacktag_one(struct sock *sk, struct tcp_sacktag_state *state, u8 sacked,
u32 start_seq, u32 end_seq, int dup_sack, int pcount, u64 xmit_time)
{
...
if (!(sacked & TCPCB_SACKED_ACKED)) {
...
sacked |= TCPCB_SACKED_ACKED;
state->flag |= FLAG_DATA_SACKED;
tp->sacked_out += pcount;
tp->delivered += pcount; /* Out-of-order packets delivered */
另外,对于Reno,每个接收到的重复ACK,认为是对端接收了一个数据报文,相应的增加delivered计数。
static void tcp_add_reno_sack(struct sock *sk, int num_dupack)
{
if (num_dupack) {
struct tcp_sock *tp = tcp_sk(sk);
u32 prior_sacked = tp->sacked_out;
s32 delivered;
tp->sacked_out += num_dupack;
tcp_check_reno_reordering(sk, 0);
delivered = tp->sacked_out - prior_sacked;
if (delivered > 0)
tp->delivered += delivered;
如果随后的乱序报文到达接收端,填补了接收队列中的空洞,接收端将发送正常的确认ACK,此操作有可能减少了剩余的重复ACK的发送,这里,将这些剩余重复ACK增加到delivered变量中,假设收到了它们。变量delivered至少将增加1。
static void tcp_remove_reno_sacks(struct sock *sk, int acked)
{
struct tcp_sock *tp = tcp_sk(sk);
if (acked > 0) {
/* One ACK acked hole. The rest eat duplicate ACKs. */
tp->delivered += max_t(int, acked - tp->sacked_out, 1);
if (acked - 1 >= tp->sacked_out)
tp->sacked_out = 0;
else
tp->sacked_out -= acked - 1;
}
计算确认报文数量
函数tcp_ack的最后使用tcp_newly_delivered计算S/ACK报文最新确认的报文数量.
static u32 tcp_newly_delivered(struct sock *sk, u32 prior_delivered, int flag)
{
...
delivered = tp->delivered - prior_delivered;
NET_ADD_STATS(net, LINUX_MIB_TCPDELIVERED, delivered);
if (flag & FLAG_ECE) {
tp->delivered_ce += delivered;
NET_ADD_STATS(net, LINUX_MIB_TCPDELIVEREDCE, delivered);
}
return delivered;
将新确认报文数量传递到tcp_cong_control函数中。
static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag)
{
u32 delivered = tp->delivered;
delivered = tcp_newly_delivered(sk, delivered, flag);
...
tcp_cong_control(sk, ack, delivered, flag, sack_state.rate);
函数tcp_cong_control最终将调用拥塞算法的函数指针cong_avoid。
static void tcp_cong_control(struct sock *sk, u32 ack, u32 acked_sacked,
int flag, const struct rate_sample *rs)
{
...
if (tcp_in_cwnd_reduction(sk)) {
...
} else if (tcp_may_raise_cwnd(sk, flag)) {
/* Advance cwnd if state allows */
tcp_cong_avoid(sk, ack, acked_sacked);
}
tcp_update_pacing_rate(sk);
}
以内核默认的Cubic拥塞算法为例,bictcp_cong_avoid将会调用开始时介绍的tcp_slow_start函数,传入新确认的报文数量acked。
static void bictcp_cong_avoid(struct sock *sk, u32 ack, u32 acked)
{
struct tcp_sock *tp = tcp_sk(sk);
struct bictcp *ca = inet_csk_ca(sk);
if (!tcp_is_cwnd_limited(sk))
return;
if (tcp_in_slow_start(tp)) {
if (hystart && after(ack, ca->end_seq))
bictcp_hystart_reset(sk);
acked = tcp_slow_start(tp, acked);
if (!acked)
return;
}
bictcp_update(ca, tp->snd_cwnd, acked);
tcp_cong_avoid_ai(tp, ca->cnt, acked);
内核版本 5.0