标准的 TCP 确认机制中,如果发送方发送了 0-1000 序号之间的数据,接收方收到了 0-100、300-1000,那么接收方只能向发送方确认 101,这时发送方会重传所有 101-1000 之间的数据,实际上这是不必要的,因为有可能仅仅是丢了一小段而已,但是在标准的 TCP 确认机制中,发送方无法感知这一事情,只能重传从 101 开始的所有数据。
为了优化这种情况,必须让发送方知道更多的接收信息,所以发展出了SACK 选项,关于 SACK 的标准见 RFC 2018。
1、概述 SACK 实现的需要发送方和接收方协作。为此,TCP 首部实际上定义了两种选项:SACK允许选项、SACK选项。
1.1、SACK 允许选项 SACK 特性是 TCP 的一个可选特性,是否启用需要收发双发进行协商,通信双发在 SYN 段或 SYN+ACK 段中添加 SACK 允许选项通知对端本端是否支持SACK,如果双发都支持,那么后续连接态通信过程中就可以使用 SACK 选项了。所以 SACK 允许选项只能出现在 SYN 段中。
SACK允许选项格式如下图:
1.2、SACK 选项 连接建立后,如果出现开头所述的情况,接收方就可以通过 SACK 选项告诉发送方字节的实际接收情况。
SACK选项格式如下:
由于整个 TCP 首部的选项部分不能超过 40 字节,所以一个 ACK 段中最多可以容纳 4 组 SACK 信息。
Left Edge 表示已收到的不连续块的第一个序号,Right Edge 表示已收到的不连续块的最后一个序号+1,即左闭右开区间。通过 ACK 和 SACK 信息,发送方就可以确定接收方具体没有收到的数据就是从 ACK 到最大 SACK 信息之间的那些空洞的序号。
内核定义了两个数据结构用于表示这种左右边界组合:
1 2 3 4 5 6 7 8 9 10 struct tcp_sack_block_wire { __be32 start_seq; __be32 end_seq; };struct tcp_sack_block { u32 start_seq; u32 end_seq; };
2、SACK 允许选项的发送 下面看看 TCP 建链过程中对 SACK 允许选项是如何处理的。
2.1、SYN 段发送 与 SACK 允许选项相关的处理是在 tcp_transmit_skb() 中进行的,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 static int tcp_transmit_skb (struct sock *sk, struct sk_buff *skb, int clone_it, gfp_t gfp_mask) { ... int sysctl_flags; ... sysctl_flags = 0 ; if (unlikely(tcb->flags & TCPCB_FLAG_SYN)) { ... if (sysctl_tcp_sack) { sysctl_flags |= SYSCTL_FLAG_SACK; if (!(sysctl_flags & SYSCTL_FLAG_TSTAMPS)) tcp_header_size += TCPOLEN_SACKPERM_ALIGNED; } } ... if (unlikely(tcb->flags & TCPCB_FLAG_SYN)) { tcp_syn_build_options((__be32 *)(th + 1 ), tcp_advertise_mss(sk), (sysctl_flags & SYSCTL_FLAG_TSTAMPS), (sysctl_flags & SYSCTL_FLAG_SACK), (sysctl_flags & SYSCTL_FLAG_WSCALE), tp->rx_opt.rcv_wscale, tcb->when, tp->rx_opt.ts_recent, #ifdef CONFIG_TCP_MD5SIG md5 ? &md5_hash_location : #endif NULL ); } ... }
2.2、接收 SYN 段 这个过程中和 SACK 允许选项相关的内容主要是对选项的解析,这是由tcp_parse_options() 完成的。不过我们知道,接收到的 TCP 选项都是解析到了结构 struct tcp_options_received 中,所以先来看看该结构中和 SACK 有关的字段定义:
2.2.1、struct tcp_options_received 1 2 3 4 5 6 7 8 9 10 11 12 struct tcp_options_received { ... u16 dsack : 1 , ... sack_ok : 4 , ... u8 eff_sacks; u8 num_sacks; ... };
2.2.2、tcp_parse_options() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void tcp_parse_options (struct sk_buff *skb, struct tcp_options_received *opt_rx, int estab) { ... case TCPOPT_SACK_PERM: if (opsize == TCPOLEN_SACK_PERM && th->syn && !estab && sysctl_tcp_sack) { opt_rx->sack_ok = 1 ; tcp_sack_reset(opt_rx); } break ; case TCPOPT_SACK: if ((opsize >= (TCPOLEN_SACK_BASE + TCPOLEN_SACK_PERBLOCK)) && !((opsize - TCPOLEN_SACK_BASE) % TCPOLEN_SACK_PERBLOCK) && opt_rx->sack_ok) { TCP_SKB_CB(skb)->sacked = (ptr - 2 ) - (unsigned char *)th; } break ; ... }
2.3、发送 SYN+ACK 段 显然,和发送 SYN 段时的处理相同,都是在 tcp_transmit_skb() 中完成的。
3、D-SACK 为了更好的反应网络情况,RFC 2883 在 SACK 选项的基础上提出了D-SACK(即Duplicate SACK)。接收方收到的乱序报文中同样有可能是会出现重复段,在 SACK 选项的第一个块中携带该重复段的序号,该序号可能是已经确认过的(小于ACK序号),或者大于其后面其它 SACK 的序号,发送方可以根据第一个块更加精细的判断网络状况:如数据段被复制、错误重传等。