linux内核协议栈 TCP选项之SACK选项的发送

 

目录

1. 相关数据结构

1.1 struct tcp_sock

1.2 struct tcp_options_received

2 慢速路径数据接收 tcp_data_queue()

2.1 判断SACK是否使能 tcp_is_sack()

2.2 DSACK 设置 tcp_dsack_set()

2.3 将SACK块加入到 selective_acks 数组 tcp_sack_new_ofo_skb()

3 发送SACK选项 tcp_transmit_skb()

3.1 SACK片数计算 tcp_established_options()

3.2 SACK 可选项填充 tcp_options_write()


当接收方收到乱序报文时,如果在TCP握手过程中,双方都表示支持SACK选项,那么就会生成SACK选项信息,并且在下一次报文发送过程中将这些选项发送给发送方。

1. 相关数据结构

1.1 struct tcp_sock

TCB结构中有如下字段和SACK选项的发送过程有关:

struct tcp_sock {
...//用户保存生成的DSACK块,因为DSACK块只能有一个,所以数组长度为1struct tcp_sack_block duplicate_sack[1]; /* D-SACK block *///用于保存生成的SACK块,下次发送时,会根据该数组内容构造SACK选项。//由于最多可以有4个SACK块,所以数组长度定义为了4struct tcp_sack_block selective_acks[4];
...
}

1.2 struct tcp_options_received

TCP的选项结构中保存了一些SACK选项使能信息、以及一些SACK信息块的计数信息,如下:

struct tcp_options_received {
...u16 	saw_tstamp : 1,	/* Saw TIMESTAMP on last packet		*/tstamp_ok : 1,	/* TIMESTAMP seen on SYN packet		*///如果设置为1,表示下次发送需要填充DSACK块,需要填充的块//已经放在了tp->duplicate_sack[0]中dsack : 1,	/* D-SACK is scheduled			*/wscale_ok : 1,	/* Wscale seen on SYN packet		*///在三次握手过程中,如果双方都支持SACK选项,那么该字段设置为1sack_ok : 4,	/* SACK seen on SYN packet		*/snd_wscale : 4,	/* Window scaling received from sender	*/rcv_wscale : 4;	/* Window scaling to send to receiver	*///下次发送可以携带的SACK选项的个数://nums_sacks+重复SACK(最多1)-时间戳选项(最多占1个),当然最多不能超过4个u8	eff_sacks;	/* Size of SACK array to send with next packet *///tp->selective_acks[]数组中记录的SACK块的数目u8	num_sacks;	/* Number of SACK blocks		*/
...
};

2 慢速路径数据接收 tcp_data_queue()

因为只有慢速路径才有可能会触发SACK选项的产生,所以我们分析tcp_data_queue()中和SACK相关的内容。

static void tcp_data_queue(struct sock *sk, struct sk_buff *skb)
{struct tcphdr *th = tcp_hdr(skb);struct tcp_sock *tp = tcp_sk(sk);int eaten = -1;...//如果DSACK还没有被清除,则复位它,因为DSACK同时有且仅有一个信息块if (tp->rx_opt.dsack) {tp->rx_opt.dsack = 0;tp->rx_opt.eff_sacks = min_t(unsigned int, tp->rx_opt.num_sacks,4 - tp->rx_opt.tstamp_ok);}//收到的数据就是想要接收的数据if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {
...//收到了期望的数据,所以可能已经能够和乱序队列中的数据衔接上了,//因此如果当前有SACK块,那么检查是否可以移除它们if (tp->rx_opt.num_sacks)tcp_sack_remove(tp);
...return;}//收到的完全是个重复段if (!after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt)) {/* A retransmit, 2nd most common case.  Force an immediate ack. */NET_INC_STATS_BH(LINUX_MIB_DELAYEDACKLOST);//根据是否支持DSACK设置重复SACKtcp_dsack_set(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq);
...return;}
...//输入段的前半部分是已经收到过的数据,后半部分是新数据,即sep < rcv_nxt < end_seqif (before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {//可见,部分段内容重复也会触发重复SACKtcp_dsack_set(tp, TCP_SKB_CB(skb)->seq, tp->rcv_nxt);
...}
...//到这里,说明收到的是一个seq大于rcv_nxt的乱序包,但是要注意,//虽然是乱序包,但是这个包同样是有可能已经接收过的SOCK_DEBUG(sk, "out of order segment: rcv_next %X seq %X - %X\n",tp->rcv_nxt, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq);//如果之前乱序队列是空的,这里需要特别处理下(为了缩小篇幅,下面删除了不相关的代码)if (!skb_peek(&tp->out_of_order_queue)) {//因为之前乱序队列是空的,所以这里无需考虑过多,直接构造第一个SACK块即可if (tcp_is_sack(tp)) {tp->rx_opt.num_sacks = 1;tp->rx_opt.dsack     = 0;tp->rx_opt.eff_sacks = 1;tp->selective_acks[0].start_seq = TCP_SKB_CB(skb)->seq;tp->selective_acks[0].end_seq = TCP_SKB_CB(skb)->end_seq;}} else {//下面的代码之所以这么复杂,是为了按照序号升序维护乱序队列,这样在后续处理时方便//从乱序队列队尾开始寻找插入点struct sk_buff *skb1 = tp->out_of_order_queue.prev;u32 seq = TCP_SKB_CB(skb)->seq;u32 end_seq = TCP_SKB_CB(skb)->end_seq;//收到的数据段的开始序号紧接着乱序队列中最后一包的末尾序号.比如乱序队列最后//一包的序号为[500,1000),收到的数据段序号为[1000,1200)if (seq == TCP_SKB_CB(skb1)->end_seq) {__skb_append(skb1, skb, &tp->out_of_order_queue);//如果乱序段和selective_acks[0]的末尾序号刚好能衔接,那么处理非常简单,//直接更新selective_acks[0].end_seq即可,其它情况需要将仔细更新SACK块if (!tp->rx_opt.num_sacks ||tp->selective_acks[0].end_seq != seq)goto add_sack;tp->selective_acks[0].end_seq = end_seq;return;}//找到升序插入点do {if (!after(TCP_SKB_CB(skb1)->seq, seq))break;} while ((skb1 = skb1->prev) !=(struct sk_buff *)&tp->out_of_order_queue);//新接收的段skb和前一个段skb1序号有重叠if (skb1 != (struct sk_buff *)&tp->out_of_order_queue &&before(seq, TCP_SKB_CB(skb1)->end_seq)) {//新收到的段完全是一个重复段if (!after(end_seq, TCP_SKB_CB(skb1)->end_seq)) {/* All the bits are present. Drop. */__kfree_skb(skb);//设置DSACK块信息tcp_dsack_set(tp, seq, end_seq);goto add_sack;}//部分重叠,同样设置DSACK块信息if (after(seq, TCP_SKB_CB(skb1)->seq)) {/* Partial overlap. */tcp_dsack_set(tp, seq, TCP_SKB_CB(skb1)->end_seq);} else {//没有重叠,skb1迁移一个节点,为下面的__skb_insert()做准备skb1 = skb1->prev;}}__skb_insert(skb, skb1, skb1->next, &tp->out_of_order_queue);//检测新插入的段和乱序队列中其之后的段是否有重叠。前面的判断已经保证了新接收段//的seq一定是小于后一个段的seq的,所以下面只能是两种情况://1. 新接收段[1000, 1200),后一个段[1100, 1500)-----部分重叠//2. 新接收段[1000, 1200),后一个段[1050, 1100),再后一个段[1150, 1300)----完全重叠while ((skb1 = skb->next) !=(struct sk_buff *)&tp->out_of_order_queue &&after(end_seq, TCP_SKB_CB(skb1)->seq)) {//部分重叠if (before(end_seq, TCP_SKB_CB(skb1)->end_seq)) {//重新设定DSACK信息tcp_dsack_extend(tp, TCP_SKB_CB(skb1)->seq, end_seq);//因为是部分重叠,所以不需要继续检查了break;}//新接收的段已经完全包含了后一个段,所以可以将后一个段从乱序队列中删除了__skb_unlink(skb1, &tp->out_of_order_queue);//重新设定DSACK信息tcp_dsack_extend(tp, TCP_SKB_CB(skb1)->seq,TCP_SKB_CB(skb1)->end_seq);__kfree_skb(skb1);}add_sack://设定SACK块if (tcp_is_sack(tp))tcp_sack_new_ofo_skb(sk, seq, end_seq);}
}

2.1 判断SACK是否使能 tcp_is_sack()

static inline int tcp_is_sack(const struct tcp_sock *tp)
{//在三次握手阶段,如果通信双方都携带了SACK允许选项,那么这个字段将被设置为1return tp->rx_opt.sack_ok;
}

2.2 DSACK 设置 tcp_dsack_set()

一旦接收到重复报文,就会调用tcp_dsack_set()设置DSACK内容,当前设置的前提是SACK特性可以使用,以及系统参数sysctl_tcp_dsack(/proc/sys/net/ipv4/tcp_dsack)开启。

static void tcp_dsack_set(struct tcp_sock *tp, u32 seq, u32 end_seq)
{//SACK特性可用并且系统使能了DSACKif (tcp_is_sack(tp) && sysctl_tcp_dsack) {//根据收到的是新数据还是老数据更新相应的DSACK统计量if (before(seq, tp->rcv_nxt))NET_INC_STATS_BH(LINUX_MIB_TCPDSACKOLDSENT);elseNET_INC_STATS_BH(LINUX_MIB_TCPDSACKOFOSENT);//设置DSACK字段,表示需要发送DSACK信息,这样下次发送段时,//会将DSACK块放到SACK选项放到第一个位置tp->rx_opt.dsack = 1;//将DSACK块信息记录到duplicate_sack[0]中tp->duplicate_sack[0].start_seq = seq;tp->duplicate_sack[0].end_seq = end_seq;//更新下次待发送的SACK选项个数(需要减去一个DSACK块)tp->rx_opt.eff_sacks = min(tp->rx_opt.num_sacks + 1,4 - tp->rx_opt.tstamp_ok);}
}

2.3 将SACK块加入到 selective_acks 数组 tcp_sack_new_ofo_skb()

如果检测到一个sack,tcp_sack_new_ofo_skb()

//参数[seq, end_seq)就是要添加的
static void tcp_sack_new_ofo_skb(struct sock *sk, u32 seq, u32 end_seq)
{struct tcp_sock *tp = tcp_sk(sk);//指向保存SACK块的数组的第一个元素struct tcp_sack_block *sp = &tp->selective_acks[0];//当前selective_acks[]数组中已有多少个SACK块int cur_sacks = tp->rx_opt.num_sacks;int this_sack;//如果之前没有任何SACK块,那么这是一个新的SACK,直接填充到selective_acks[0]即可if (!cur_sacks)goto new_sack;//下面这个循环尝试将[seq, end_seq)与selective_acks[]数组中现有的SACK块合并,//如果合并成功,那么不需要更新任何计数信息,直接返回for (this_sack = 0; this_sack < cur_sacks; this_sack++, sp++) {//tcp_sack_extend()尝试将[seq, end_seq)和sp合并,如果合并成功则返回1if (tcp_sack_extend(sp, seq, end_seq)) {//根据RFC 2018的规定,selective_acks[]数组的第一个SACK块应该是最新的乱序报文,//所以这里需要将合并后的sp移动到数组的第一个位置for (; this_sack > 0; this_sack--, sp--)tcp_sack_swap(sp, sp - 1);//因为发生了SACK块的合并(扩展了左右边界),所以原来不连续的SACK块可能会变得连续,//tcp_sack_maybe_coalesce()函数重新检测这些SACK块的边界,尽可能将它们合并if (cur_sacks > 1)tcp_sack_maybe_coalesce(tp);return;}}/* Could not find an adjacent existing SACK, build a new one,* put it at the front, and shift everyone else down.  We* always know there is at least one SACK present already here.** If the sack array is full, forget about the last one.*///见注释,这是一个独立的SACK块,所以要将其放到第一个位置,//并且如果当前已有4个SACK块存在,那么需要丢弃最后一个if (this_sack >= 4) {this_sack--;tp->rx_opt.num_sacks--;sp--;}for (; this_sack > 0; this_sack--, sp--)*sp = *(sp - 1);new_sack://将参数指定的序号生成一个新的SACK块填充到sp指向的位置sp->start_seq = seq;sp->end_seq = end_seq;//更新SACK块的数目tp->rx_opt.num_sacks++;//结合时间戳选项、重复SACK更新下次可以发送的SACK块的数目tp->rx_opt.eff_sacks = min(tp->rx_opt.num_sacks + tp->rx_opt.dsack,4 - tp->rx_opt.tstamp_ok);
}

3 发送SACK选项 tcp_transmit_skb()

SACK选项的发送当然是在TCP段发送过程中构造TCP首部选项时确定的,这个过程由tcp_options_write()完成,该函数由tcp_transmit_skb()调用。

3.1 SACK片数计算 tcp_established_options()

static unsigned tcp_established_options(struct sock *sk, struct sk_buff *skb,struct tcp_out_options *opts,struct tcp_md5sig_key **md5) {struct tcp_skb_cb *tcb = skb ? TCP_SKB_CB(skb) : NULL;struct tcp_sock *tp = tcp_sk(sk);unsigned size = 0;....//计算SACK的块数if (unlikely(tp->rx_opt.eff_sacks)) {const unsigned remaining = MAX_TCP_OPTION_SPACE - size;opts->num_sack_blocks =min_t(unsigned, tp->rx_opt.eff_sacks,(remaining - TCPOLEN_SACK_BASE_ALIGNED) /TCPOLEN_SACK_PERBLOCK);size += TCPOLEN_SACK_BASE_ALIGNED +opts->num_sack_blocks * TCPOLEN_SACK_PERBLOCK;}return size;//可选项字节数
}

3.2 SACK 可选项填充 tcp_options_write()

static void tcp_options_write(__be32 *ptr, struct tcp_sock *tp,const struct tcp_out_options *opts,__u8 **md5_hash) {...if (unlikely(opts->num_sack_blocks)) {//如果有DSACK,sp指向DSACK信息块,否则指向普通SACK信息块struct tcp_sack_block *sp = tp->rx_opt.dsack ?tp->duplicate_sack : tp->selective_acks;int this_sack;//填充位、类型、长度*ptr++ = htonl((TCPOPT_NOP  << 24) |(TCPOPT_NOP  << 16) |(TCPOPT_SACK <<  8) |(TCPOLEN_SACK_BASE + (opts->num_sack_blocks *TCPOLEN_SACK_PERBLOCK)));//这里实际上利用了struct tcp_sock中duplicate_sack[]和selective_acks紧邻这一前提,//eff_sacks已经考虑了DSACK和SACK,所以当填充了DSACK后,紧接着就可以填充SACKfor (this_sack = 0; this_sack < opts->num_sack_blocks;++this_sack) {*ptr++ = htonl(sp[this_sack].start_seq);*ptr++ = htonl(sp[this_sack].end_seq);}//DSACK选项一旦发送之后就会被清除,如果再次收到重复段,才会重新生成。但是普通的SACK选项//即使发送过了,也不会清除,所以下次发送时只要没有清除就还会携带if (tp->rx_opt.dsack) {tp->rx_opt.dsack = 0;tp->rx_opt.eff_sacks = tp->rx_opt.num_sacks;}}
}


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部