Table of Contents
这篇笔记记录了发送方在收到SACK信息后的处理过程中,对SACK信息块的检查,具体包括:
- 判断第一个SACK块是否是DSACK;
- 检查SACK信息块(包括DSACK)是否合法。
1 检测是否存在DSACK
tcp_check_dsack()用于判断收到的第一个SACK块是否是DSACK,参数sp指向输入段携带的SACK选项信息,num_sacks表示输入段携带了几个SACK块。
关于如何判定收到的SACK块是DSACK,详细的介绍可以参考RFC 2883.实际上思路很简单,就是接收端只有在收到重复段的情况下才会发送DSACK,而重复段有两种情况:
- 该段已经被确认过了;
- 该段是个乱序段,但是之前也已经接收过该乱序段了;
所以,对应的DSACK块有两种情况:
- DSACK块的起始序号小于ACK序号;
- DSACK块的序号范围一定在后一个SACK块的序号范围之内。
static int tcp_check_dsack(struct tcp_sock *tp, struct sk_buff *ack_skb,
struct tcp_sack_block_wire *sp, int num_sacks,
u32 prior_snd_una)
{
//DSACK只能出现在sp[0],这里提取sp[0]的起始序号
u32 start_seq_0 = ntohl(get_unaligned(&sp[0].start_seq));
u32 end_seq_0 = ntohl(get_unaligned(&sp[0].end_seq));
int dup_sack = 0;
//如果SACK块的起始序号小于输入段携带的确认号,那么说明对端一定是收到了
//一个已经确认过的重复段,才会触发这样的SACK,所以认为这是一个DSACK
if (before(start_seq_0, TCP_SKB_CB(ack_skb)->ack_seq)) {
dup_sack = 1;
tcp_dsack_seen(tp);
NET_INC_STATS_BH(LINUX_MIB_TCPDSACKRECV);
} else if (num_sacks > 1) {
//如果有多个SACK块,那么提取第二个SACK块的起始序号
u32 end_seq_1 = ntohl(get_unaligned(&sp[1].end_seq));
u32 start_seq_1 = ntohl(get_unaligned(&sp[1].start_seq));
//如果第二个SACK块将第一个SACK块完全包含,那么说明对端一定是收到了
//一个乱序的重复段,所以也认为这是一个DSACK
if (!after(end_seq_0, end_seq_1) && !before(start_seq_0, start_seq_1)) {
dup_sack = 1;
tcp_dsack_seen(tp);
NET_INC_STATS_BH(LINUX_MIB_TCPDSACKOFORECV);
}
}
/* D-SACK for already forgotten data... Do dumb counting. */
//拥塞控制算法需要
if (dup_sack && !after(end_seq_0, prior_snd_una) && after(end_seq_0, tp->undo_marker))
tp->undo_retrans--;
return dup_sack;
}
static void tcp_dsack_seen(struct tcp_sock *tp)
{
//设置sack_ok的bit3,表示检测到了DSACK
tp->rx_opt.sack_ok |= 4;
}
2 检测SACK块是否有效
@is_dsack: 要检测的SACK块是否是一个DSACK块
static int tcp_is_sackblock_valid(struct tcp_sock *tp, int is_dsack, u32 start_seq, u32 end_seq)
{
/* Too far in future, or reversed (interpretation is ambiguous) */
//cond1:SACK的确认范围包含了还没有发送的数据;
//cond2: SACK块的第一个序号大于等于第二个序号,序号范围非法
if (after(end_seq, tp->snd_nxt) || !before(start_seq, end_seq))
return 0;
/* Nasty start_seq wrap-around check (see comments above) */
//和上面的cond1类似,确认范围不合理
if (!before(start_seq, tp->snd_nxt))
return 0;
/* In outstanding window? ...This is valid exit for D-SACKs too.
* start_seq == snd_una is non-sensical (see comments above)
*/
//满足该条件,说明SACK确认范围确实是在[snd_una, snd_nxt)之间,合法SACK
if (after(start_seq, tp->snd_una))
return 1;
//到了这里,说明SACK确认范围和[snd_una, snd_nxt)是这样的:
//snd_una=200, snd_nxt=1000,start_seq=100, end_seq = 500
//即SACK的前半部分是已经被ACK过的,后半部分是没有ACK的(虚假SACK)
if (!is_dsack || !tp->undo_marker)
return 0;
/* ...Then it's D-SACK, and must reside below snd_una completely */
if (!after(end_seq, tp->snd_una))
return 0;
if (!before(start_seq, tp->undo_marker))
return 1;
/* Too old */
if (!after(end_seq, tp->undo_marker))
return 0;
/* Undo_marker boundary crossing (overestimates a lot). Known already:
* start_seq < undo_marker and end_seq >= undo_marker.
*/
return !before(start_seq, end_seq - tp->max_window);
}