TCP选项之SACK选项概述

标准的TCP确认机制中,如果发送方发送了0-1000序号之间的数据,接收方收到了0-100、300-1000,那么接收方只能向发送方确认101,这时发送方会重传所有101-1000之间的数据,实际上这是不必要的,因为有可能仅仅是丢了一小段而已,但是在标准的TCP确认机制中,发送方无法感知这一事情,只能重传从101开始的所有数据。

为了优化这种情况,必须让发送方知道更多的接收信息,所以发展出了SACK选项,关于SACK的标准见RFC 2018。

SACK在实际使用中是比较普遍的一个选项,而且相关的内容也较多,准备用三篇笔记来记录相关内容:

  1. 概述:介绍基本概念;
  2. SACK选项的产生;
  3. SACK选项在发送方的处理。

1. 概述

SACK实现的需要发送方和接收方协作。为此,TCP首部实际上定义了两种选项:SACK允许选项、SACK选项。

1.1 SACK允许选项

SACK特性是TCP的一个可选特性,是否启用需要收发双发进行协商,通信双发在SYN段或SYN+ACK段中添加SACK允许选项通知对端本端是否支持SACK,如果双发都支持,那么后续连接态通信过程中就可以使用SACK选项了。所以SACK允许选项只能出现在SYN段中。

SACK允许选项格式如下图:
SACK允许选项

1.2 SACK选项

连接建立后,如果出现开头所述的情况,接收方就可以通过SACK选项告诉发送方字节的实际接收情况。SACK选项格式如下:
在这里插入图片描述
由于整个TCP首部的选项部分不能超过40字节,所以一个ACK段中最多可以容纳4组SACK信息。

Left Edge表示已收到的不连续块的第一个序号,Right Edge表示已收到的不连续块的最后一个序号+1,即左闭右开区间。通过ACK和SACK信息,发送方就可以确定接收方具体没有收到的数据就是从ACK到最大SACK信息之间的那些空洞的序号。

内核定义了两个数据结构用于表示这种左右边界组合:

//大端表示,即对网络上要传输的数据的直接表示
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()中进行的,代码如下:

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)) {
...
		//可见,是否启用SACK选项是有系统参数sysctl_tcp_sack(/proc/sys/net/ipv4/tcp_sack)控制的
		if (sysctl_tcp_sack) {
			sysctl_flags |= SYSCTL_FLAG_SACK;
			//这里之所以考虑时间戳选项,是因为可以将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),
					  //标识是否启用SACK,如果为1,则tcp_syn_build_options()会构造SACK允许选项
				      (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

struct tcp_options_received {
...
	u16 dsack : 1,		/* D-SACK is scheduled			*/
...
	//标识对端是否支持SACK,来源于SYN段,见下文
	sack_ok : 4,	/* SACK seen on SYN packet		*/
...
/*	SACKs data	*/
	u8	eff_sacks;	/* Size of SACK array to send with next packet */
	u8	num_sacks;	/* Number of SACK blocks		*/
...
};

2.2.2 tcp_parse_options()

void tcp_parse_options(struct sk_buff *skb, struct tcp_options_received *opt_rx,
		       int estab)
{
...
	case TCPOPT_SACK_PERM:
		//解析SACK允许选项,必须是SYN段、非连接态、sysctl_tcp_sack打开
		if (opsize == TCPOLEN_SACK_PERM && th->syn &&
			!estab && sysctl_tcp_sack) {
			//sack_ol置1表示对端支持SACK特性
			opt_rx->sack_ok = 1;
			tcp_sack_reset(opt_rx);
		}
		break;
	case TCPOPT_SACK:
		//解析SACK信息
		if ((opsize >= (TCPOLEN_SACK_BASE + TCPOLEN_SACK_PERBLOCK)) &&
		   !((opsize - TCPOLEN_SACK_BASE) % TCPOLEN_SACK_PERBLOCK) &&
		   opt_rx->sack_ok) {
			//可见,TCB控制块中的sacked记录的是SACK选项与TCP首部的偏移量
			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的序号,发送方可以根据第一个块更加精细的判断网络状况:如数据段被复制、错误重传等。

  • 12
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值