上一篇处理ack的blog中我们知道当我们接收到ack的时候,我们会判断sack段,如果包含sack段的话,我们就要进行处理。这篇blog就主要来介绍内核如何处理sack段。

SACK是包含在tcp的option中的,由于tcp的头的长度的限制,因此SACK也就是最多包含4个段,也就是32个字节。我们先来看tcp中的SACK段的表示:

Java代码  
  1. struct tcp_sack_block {
  2. //起始序列号
  3. u32 start_seq;
  4. //结束序列号
  5. u32 end_seq;
  6. };
struct tcp_sack_block {
//起始序列号
u32 start_seq;
//结束序列号
u32 end_seq;
};

可以看到很简单,就是一个段的起始序列号和一个结束序列号。

前一篇blog我们知道tcp_skb_cb的sacked域也就是sack option的偏移值,而在tcp的option它的组成是由3部分组成的,第一部分为option类型,第二部分为当前option的长度,第三部分才是数据段,因此我们如果要取得SACK的段,就必须这样计算。

这里ack_skb也就是我们要处理的skbuffer。

Java代码  
  1. //首先得到sack option的起始指针。
  2. unsigned char *ptr = (skb_transport_header(ack_skb) +
  3. TCP_SKB_CB(ack_skb)->sacked);
  4. //加2的意思也就是加上类型和长度,这里刚好是2个字节。最终结果也就是sack option的数据段。
  5. struct tcp_sack_block_wire *sp_wire = (struct tcp_sack_block_wire *)(ptr+2);
//首先得到sack option的起始指针。
unsigned char *ptr = (skb_transport_header(ack_skb) +
TCP_SKB_CB(ack_skb)->sacked);
//加2的意思也就是加上类型和长度,这里刚好是2个字节。最终结果也就是sack option的数据段。
struct tcp_sack_block_wire *sp_wire = (struct tcp_sack_block_wire *)(ptr+2);

这里很奇怪,内核还有一个tcp_sack_block_wire类型的结构,它和tcp_sack_block是完全一样的。

而我们如果要得到当前的SACK段的个数我们要这样做:

Java代码  
  1. #define TCPOLEN_SACK_BASE       2
  2. int num_sacks = min(TCP_NUM_SACKS, (ptr[1] - TCPOLEN_SACK_BASE) >> 3);
#define TCPOLEN_SACK_BASE        2
int num_sacks = min(TCP_NUM_SACKS, (ptr[1] - TCPOLEN_SACK_BASE) >> 3);

这里ptr1也就是sack option的长度(字节数),而TCPOLEN_SACK_BASE为类型和长度字段的长度,因此这两个值的差也就是sack段的总长度,而这里每个段都是8个字节,因此我们右移3位就得到了它的个数,最后sack的段的长度不能大于4,因此我们要取一个最小值。

上面的结构下面这张图非常清晰的展示了,这几个域的关系:

然后我们来看SACK的处理,在内核中SACK的处理是通过tcp_sacktag_write_queue来实现的,这个函数比较长,因此这里我们分段来看。

先来看函数的原型

Java代码  
  1. static int
  2. tcp_sacktag_write_queue(struct sock *sk, struct sk_buff *ack_skb,
  3. u32 prior_snd_una)
static int
tcp_sacktag_write_queue(struct sock *sk, struct sk_buff *ack_skb,
u32 prior_snd_una)

第一个参数是当前的sock,第二个参数是要处理的skb,第三个参数是接受ack的时候的snd_una.

在看之前这里有几个重要的域要再要说明下。

1  tcp socket的sacked_out域,这个域保存了所有被sack的段的个数。

2 还有一个就是tcp_sacktag_state结构,这个结构保存了当前skb的一些信息。

Java代码  
  1. struct tcp_sacktag_state {
  2. int reord;
  3. int fack_count;
  4. int flag;
  5. };
struct tcp_sacktag_state {
int reord;
int fack_count;
int flag;
};

3 tcp socket的highest_sack域,这个域也就是被sack确认的最大序列号的skb。

先来看第一部分,这部分的代码主要功能是初始化一些用到的值,比如sack的指针,当前有多少sack段等等,以及一些合法性校验。

Java代码  
  1. //sack段的最大个数
  2. #define TCP_NUM_SACKS 4
  3. .....................................................................................................
  4. const struct inet_connection_sock *icsk = inet_csk(sk);
  5. struct tcp_sock *tp = tcp_sk(sk);
  6. ///下面两句代码,前面已经分析过了,也就是取得sack的指针以及sack 数据段的指针。
  7. unsigned char *ptr = (skb_transport_header(ack_skb) +
  8. TCP_SKB_CB(ack_skb)->sacked);
  9. struct tcp_sack_block_wire *sp_wire = (struct tcp_sack_block_wire *)(ptr+2);
  10. //这个数组最终会用来保存所有的SACK段。
  11. struct tcp_sack_block sp[TCP_NUM_SACKS];
  12. struct tcp_sack_block *cache;
  13. struct tcp_sacktag_state state;
  14. struct sk_buff *skb;
  15. //这里得到当前的sack段的个数,这段代码前面也介绍过了。
  16. int num_sacks = min(TCP_NUM_SACKS, (ptr[1] - TCPOLEN_SACK_BASE) >> 3);
  17. int used_sacks;
  18. ///重复的sack的个数。
  19. int found_dup_sack = 0;
  20. int i, j;
  21. int first_sack_index;
  22. state.flag = 0;
  23. state.reord = tp->packets_out;
  24. //如果sack的个数为0,则我们要更新相关的域。
  25. if (!tp->sacked_out) {
  26. if (WARN_ON(tp->fackets_out))
  27. tp->fackets_out = 0;
  28. ///这个函数主要更新highest_sack域。
  29. tcp_highest_sack_reset(sk);
  30. }
  31. //开始检测是否有重复的sack。这个函数紧接着会详细分析。
  32. found_dup_sack = tcp_check_dsack(sk, ack_skb, sp_wire,
  33. num_sacks, prior_snd_una);
  34. //如果有发现,则设置flag。
  35. if (found_dup_sack)
  36. state.flag |= FLAG_DSACKING_ACK;
  37. ///再次判断ack的序列号是否太老。
  38. if (before(TCP_SKB_CB(ack_skb)->ack_seq, prior_snd_una - tp->max_window))
  39. return 0;
  40. //如果packets_out为0,则说明我们没有发送还没有确认的段,此时进入out,也就是错误处理。
  41. if (!tp->packets_out)
  42. goto out;
//sack段的最大个数
#define TCP_NUM_SACKS 4
.....................................................................................................
const struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
///下面两句代码,前面已经分析过了,也就是取得sack的指针以及sack 数据段的指针。
unsigned char *ptr = (skb_transport_header(ack_skb) +
TCP_SKB_CB(ack_skb)->sacked);
struct tcp_sack_block_wire *sp_wire = (struct tcp_sack_block_wire *)(ptr+2);
//这个数组最终会用来保存所有的SACK段。
struct tcp_sack_block sp[TCP_NUM_SACKS];
struct tcp_sack_block *cache;
struct tcp_sacktag_state state;
struct sk_buff *skb;
//这里得到当前的sack段的个数,这段代码前面也介绍过了。
int num_sacks = min(TCP_NUM_SACKS, (ptr[1] - TCPOLEN_SACK_BASE) >> 3);
int used_sacks;
///重复的sack的个数。
int found_dup_sack = 0;
int i, j;
int first_sack_index;
state.flag = 0;
state.reord = tp->packets_out;
//如果sack的个数为0,则我们要更新相关的域。
if (!tp->sacked_out) {
if (WARN_ON(tp->fackets_out))
tp->fackets_out = 0;
///这个函数主要更新highest_sack域。
tcp_highest_sack_reset(sk);
}
//开始检测是否有重复的sack。这个函数紧接着会详细分析。
found_dup_sack = tcp_check_dsack(sk, ack_skb, sp_wire,
num_sacks, prior_snd_una);
//如果有发现,则设置flag。
if (found_dup_sack)
state.flag |= FLAG_DSACKING_ACK;
///再次判断ack的序列号是否太老。
if (before(TCP_SKB_CB(ack_skb)->ack_seq, prior_snd_una - tp->max_window))
return 0;
//如果packets_out为0,则说明我们没有发送还没有确认的段,此时进入out,也就是错误处理。
if (!tp->packets_out)
goto out;

在看接下来的部分之前我们先来看tcp_highest_sack_reset和tcp_check_dsack函数,先是tcp_highest_sack_reset函数。

Java代码  
  1. static inline void tcp_highest_sack_reset(struct sock *sk)
  2. {
  3. //设置highest_sack为写队列的头。
  4. tcp_sk(sk)->highest_sack = tcp_write_queue_head(sk);
  5. }
static inline void tcp_highest_sack_reset(struct sock *sk)
{
//设置highest_sack为写队列的头。
tcp_sk(sk)->highest_sack = tcp_write_queue_head(sk);
}

这里原因很简单,因为当sacked_out为0,则说明没有通过sack确认的段,此时highest_sack自然就指向写队列的头。

第二个是tcp_check_dsack函数,这个函数比较复杂,他主要是为了检测D-SACK,也就是重复的sack。

有关dsack的概念可以去看RFC 2883和3708.

我这里简要的提一下dsack的功能,D-SACK的功能主要是使接受者能够通过sack的块来报道接收到的重复的段,从而使发送者更好的进行拥塞控制。

这里D-SACK的判断是通过RFC2883中所描述的进行的。如果是下面两种情况,则说明收到了一个D-SACK。

1 如果SACK的第一个段所ack的区域被当前skb的ack所确认的段覆盖了一部分,则说明我们收到了一个d-sack,而代码中也就是sack第一个段的起始序列号小于snd_una。下面的图描述了这种情况:

2 如果sack的第二个段完全包含了第二个段,则说明我们收到了重复的sack,下面这张图描述了这种关系。

最后要注意的是,这里收到D-SACK后,我们需要打开当前sock d-sack的option。并设置dsack的flag。

然后我们还需要判断dsack的数据是否已经被ack完全确认过了,如果确认过了,我们就需要更新undo_retrans域,这个域表示重传的数据段的个数。

来看代码:

Java代码  
  1. static int tcp_check_dsack(struct sock *sk, struct sk_buff *ack_skb,
  2. struct tcp_sack_block_wire *sp, int num_sacks,
  3. u32 prior_snd_una)
  4. {
  5. struct tcp_sock *tp = tcp_sk(sk);
  6. //首先取得sack的第一个段的起始和结束序列号
  7. u32 start_seq_0 = get_unaligned_be32(&sp[0].start_seq);
  8. u32 end_seq_0 = get_unaligned_be32(&sp[0].end_seq);
  9. int dup_sack = 0;
  10. ///判断D-sack,首先判断第一个条件,也就是起始序列号小于ack的序列号
  11. if (before(start_seq_0, TCP_SKB_CB(ack_skb)->ack_seq)) {
  12. //设置dsack标记。
  13. dup_sack = 1;
  14. ///这里更新tcp的option的sack_ok域。
  15. tcp_dsack_seen(tp);
  16. NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPDSACKRECV);
  17. } else if (num_sacks > 1) {
  18. //然后执行第二个判断,取得第二个段的起始和结束序列号。
  19. u32 end_seq_1 = get_unaligned_be32(&sp[1].end_seq);
  20. u32 start_seq_1 = get_unaligned_be32(&sp[1].start_seq);
  21. //执行第二个判断,也就是第二个段完全包含第一个段。
  22. if (!after(end_seq_0, end_seq_1) &&
  23. !before(start_seq_0, start_seq_1)) {
  24. dup_sack = 1;
  25. tcp_dsack_seen(tp);
  26. NET_INC_STATS_BH(sock_net(sk),
  27. LINUX_MIB_TCPDSACKOFORECV);
  28. }
  29. }
  30. ///判断是否dsack的数据段完全被ack所确认。
  31. if (dup_sack &&
  32. !after(end_seq_0, prior_snd_una) &&
  33. after(end_seq_0, tp->undo_marker))
  34. //更新重传段的个数。
  35. tp->undo_retrans--;
  36. return dup_sack;
  37. }
static int tcp_check_dsack(struct sock *sk, struct sk_buff *ack_skb,
struct tcp_sack_block_wire *sp, int num_sacks,
u32 prior_snd_una)
{
struct tcp_sock *tp = tcp_sk(sk);
//首先取得sack的第一个段的起始和结束序列号
u32 start_seq_0 = get_unaligned_be32(&sp[0].start_seq);
u32 end_seq_0 = get_unaligned_be32(&sp[0].end_seq);
int dup_sack = 0;
///判断D-sack,首先判断第一个条件,也就是起始序列号小于ack的序列号
if (before(start_seq_0, TCP_SKB_CB(ack_skb)->ack_seq)) {
//设置dsack标记。
dup_sack = 1;
///这里更新tcp的option的sack_ok域。
tcp_dsack_seen(tp);
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPDSACKRECV);
} else if (num_sacks > 1) {
//然后执行第二个判断,取得第二个段的起始和结束序列号。
u32 end_seq_1 = get_unaligned_be32(&sp[1].end_seq);
u32 start_seq_1 = get_unaligned_be32(&sp[1].start_seq);
//执行第二个判断,也就是第二个段完全包含第一个段。
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(sock_net(sk),
LINUX_MIB_TCPDSACKOFORECV);
}
}
///判断是否dsack的数据段完全被ack所确认。
if (dup_sack &&
!after(end_seq_0, prior_snd_una) &&
after(end_seq_0, tp->undo_marker))
//更新重传段的个数。
tp->undo_retrans--;
return dup_sack;
}

然后回到tcp_sacktag_write_queue,接下来这部分很简单,主要是提取sack的段到sp中,并校验每个段的合法性,然后统计一些信息。

Java代码  
  1. //开始遍历,这里num_sacks也就是我们前面计算的sack段的个数
  2. for (i = 0; i < num_sacks; i++) {
  3. int dup_sack = !i && found_dup_sack;
  4. //赋值。
  5. sp[used_sacks].start_seq = get_unaligned_be32(&sp_wire[i].start_seq);
  6. sp[used_sacks].end_seq = get_unaligned_be32(&sp_wire[i].end_seq);
  7. //检测段的合法性。
  8. if (!tcp_is_sackblock_valid(tp, dup_sack,
  9. sp[used_sacks].start_seq,
  10. sp[used_sacks].end_seq)) {
  11. int mib_idx;
  12. if (dup_sack) {
  13. if (!tp->undo_marker)
  14. mib_idx = LINUX_MIB_TCPDSACKIGNOREDNOUNDO;
  15. else
  16. mib_idx = LINUX_MIB_TCPDSACKIGNOREDOLD;
  17. } else {
  18. /* Don't count olds caused by ACK reordering */
  19. if ((TCP_SKB_CB(ack_skb)->ack_seq != tp->snd_una) &&
  20. !after(sp[used_sacks].end_seq, tp->snd_una))
  21. continue;
  22. mib_idx = LINUX_MIB_TCPSACKDISCARD;
  23. }
  24. //更新统计信息。
  25. NET_INC_STATS_BH(sock_net(sk), mib_idx);
  26. if (i == 0)
  27. first_sack_index = -1;
  28. continue;
  29. }
  30. //忽略已经确认过的段。
  31. if (!after(sp[used_sacks].end_seq, prior_snd_una))
  32. continue;
  33. //这个值表示我们要使用的sack的段的个数。
  34. used_sacks++;
  35. }
//开始遍历,这里num_sacks也就是我们前面计算的sack段的个数
for (i = 0; i < num_sacks; i++) {
int dup_sack = !i && found_dup_sack;
//赋值。
sp[used_sacks].start_seq = get_unaligned_be32(&sp_wire[i].start_seq);
sp[used_sacks].end_seq = get_unaligned_be32(&sp_wire[i].end_seq);
//检测段的合法性。
if (!tcp_is_sackblock_valid(tp, dup_sack,
sp[used_sacks].start_seq,
sp[used_sacks].end_seq)) {
int mib_idx;
if (dup_sack) {
if (!tp->undo_marker)
mib_idx = LINUX_MIB_TCPDSACKIGNOREDNOUNDO;
else
mib_idx = LINUX_MIB_TCPDSACKIGNOREDOLD;
} else {
/* Don't count olds caused by ACK reordering */
if ((TCP_SKB_CB(ack_skb)->ack_seq != tp->snd_una) &&
!after(sp[used_sacks].end_seq, tp->snd_una))
continue;
mib_idx = LINUX_MIB_TCPSACKDISCARD;
}
//更新统计信息。
NET_INC_STATS_BH(sock_net(sk), mib_idx);
if (i == 0)
first_sack_index = -1;
continue;
}
//忽略已经确认过的段。
if (!after(sp[used_sacks].end_seq, prior_snd_una))
continue;
//这个值表示我们要使用的sack的段的个数。
used_sacks++;
}

然后接下来的代码就是排序sack的段,也就是按照序列号的大小来排序:

Java代码  
  1. for (i = used_sacks - 1; i > 0; i--) {
  2. for (j = 0; j < i; j++) {
  3. //可以看到这里通过比较起始序列号来排序。
  4. if (after(sp[j].start_seq, sp[j + 1].start_seq)) {
  5. //交换对应的值。
  6. swap(sp[j], sp[j + 1]);
  7. /* Track where the first SACK block goes to */
  8. if (j == first_sack_index)
  9. first_sack_index = j + 1;
  10. }
  11. }
  12. }
for (i = used_sacks - 1; i > 0; i--) {
for (j = 0; j < i; j++) {
//可以看到这里通过比较起始序列号来排序。
if (after(sp[j].start_seq, sp[j + 1].start_seq)) {
//交换对应的值。
swap(sp[j], sp[j + 1]);
/* Track where the first SACK block goes to */
if (j == first_sack_index)
first_sack_index = j + 1;
}
}
}

然后就是cache的初始化,这里的tcp socket的recv_sack_cache域要注意,这个域保存了上一次处理的sack的段的序列号。可以看到这个域类型也是tcp_sack_block,而且大小也是4,

Java代码  
  1. //如果sack的数据段的个数为0,则说明我们要忽略调cache,此时可以看到cache指向recv_sack_cache的末尾。
  2. if (!tp->sacked_out) {
  3. /* It's already past, so skip checking against it */
  4. cache = tp->recv_sack_cache + ARRAY_SIZE(tp->recv_sack_cache);
  5. } else {
  6. //否则取出cache,然后跳过空的块。
  7. cache = tp->recv_sack_cache;
  8. /* Skip empty blocks in at head of the cache */
  9. while (tcp_sack_cache_ok(tp, cache) && !cache->start_seq &&
  10. !cache->end_seq)
  11. //跳过空的块。
  12. cache++;
  13. }
//如果sack的数据段的个数为0,则说明我们要忽略调cache,此时可以看到cache指向recv_sack_cache的末尾。
if (!tp->sacked_out) {
/* It's already past, so skip checking against it */
cache = tp->recv_sack_cache + ARRAY_SIZE(tp->recv_sack_cache);
} else {
//否则取出cache,然后跳过空的块。
cache = tp->recv_sack_cache;
/* Skip empty blocks in at head of the cache */
while (tcp_sack_cache_ok(tp, cache) && !cache->start_seq &&
!cache->end_seq)
//跳过空的块。
cache++;
}

然后就是开始真正处理重传队列中的skb了。

我们要知道重传队列中的skb有三种类型,分别是SACKED(S), RETRANS(R) 和LOST(L),而每种类型所处理的数据包的个数分别保存在sacked_out, retrans_out 和lost_out中。

而处于重传队列的skb也就是会处于下面6中状态:

Java代码  
  1. * Tag  InFlight Description
  2. * 0         1      - orig segment is in flight.
  3. * S         0      - nothing flies, orig reached receiver.
  4. * L       0        - nothing flies, orig lost by net.
  5. * R          2     - both orig and retransmit are in flight.
  6. * L|R  1       - orig is lost, retransmit is in flight.
  7. * S|R    1     - orig reached receiver, retrans is still in flight.

* Tag  InFlight Description
* 0      1      - orig segment is in flight.
* S      0      - nothing flies, orig reached receiver.
* L       0     - nothing flies, orig lost by net.
* R       2     - both orig and retransmit are in flight.
* L|R   1       - orig is lost, retransmit is in flight.
* S|R    1      - orig reached receiver, retrans is still in flight.

这里Tag也就是上面所说的三种类型,而InFlight也就是表示还在网络中的段的个数。

然后重传队列中的skb的状态变迁是通过下面这几种事件来触发的:

Java代码  
  1. 1. New ACK (+SACK) arrives. (tcp_sacktag_write_queue())
  2. * 2. Retransmission. (tcp_retransmit_skb(), tcp_xmit_retransmit_queue())
  3. * 3. Loss detection event of one of three flavors:
  4. *   A. Scoreboard estimator decided the packet is lost.
  5. *      A'. Reno "three dupacks" marks head of queue lost.
  6. *      A''. Its FACK modfication, head until snd.fack is lost.
  7. *   B. SACK arrives sacking data transmitted after never retransmitted
  8. *      hole was sent out.
  9. *   C. SACK arrives sacking SND.NXT at the moment, when the
  10. *      segment was retransmitted.
  11. * 4. D-SACK added new rule: D-SACK changes any tag to S.

1. New ACK (+SACK) arrives. (tcp_sacktag_write_queue())
* 2. Retransmission. (tcp_retransmit_skb(), tcp_xmit_retransmit_queue())
* 3. Loss detection event of one of three flavors:
*   A. Scoreboard estimator decided the packet is lost.
*      A'. Reno "three dupacks" marks head of queue lost.
*      A''. Its FACK modfication, head until snd.fack is lost.
*   B. SACK arrives sacking data transmitted after never retransmitted
*      hole was sent out.
*   C. SACK arrives sacking SND.NXT at the moment, when the
*      segment was retransmitted.
* 4. D-SACK added new rule: D-SACK changes any tag to S.

在进入这段代码分析之前,我们先来看几个重要的域。

tcp socket的high_seq域,这个域是我们进入拥塞控制的时候最大的发送序列号,也就是snd_nxt.

然后这里还有FACK的概念,FACK算法也就是收到的不同的SACK块之间的hole,他就认为是这些段丢失掉了。因此这里tcp socket有一个fackets_out域,这个域表示了

Java代码  
  1. //首先取得写队列的头,以便与下面的遍历。
  2. skb = tcp_write_queue_head(sk);
  3. state.fack_count = 0;
  4. i = 0;
  5. ///这里used_sacks表示我们需要处理的sack段的个数。
  6. while (i < used_sacks) {
  7. u32 start_seq = sp[i].start_seq;
  8. u32 end_seq = sp[i].end_seq;
  9. //得到是否是重复的sack
  10. int dup_sack = (found_dup_sack && (i == first_sack_index));
  11. struct tcp_sack_block *next_dup = NULL;
  12. if (found_dup_sack && ((i + 1) == first_sack_index))
  13. next_dup = &sp[i + 1];
  14. //如果sack段的结束序列号大于将要发送的最大序列号,这个情况说明我们可能有数据丢失。因此设置丢失标记。这里可以看到也就是上面所说的事件B到达。
  15. if (after(end_seq, tp->high_seq))
  16. state.flag |= FLAG_DATA_LOST;
  17. //跳过一些太老的cache
  18. while (tcp_sack_cache_ok(tp, cache) &&
  19. !before(start_seq, cache->end_seq))
  20. cache++;
  21. //如果有cache,就先处理cache的sack块。
  22. if (tcp_sack_cache_ok(tp, cache) && !dup_sack &&
  23. after(end_seq, cache->start_seq)) {
  24. //如果当前的段的起始序列号小于cache的起始序列号(这个说明他们之间有交叉),则我们处理他们之间的段。
  25. if (before(start_seq, cache->start_seq)) {
  26. skb = tcp_sacktag_skip(skb, sk, &state,
  27. start_seq);
  28. skb = tcp_sacktag_walk(skb, sk, next_dup,
  29. &state,
  30. start_seq,
  31. cache->start_seq,
  32. dup_sack);
  33. }
  34. //处理剩下的块,也就是cache->end_seq和ned_seq之间的段。
  35. if (!after(end_seq, cache->end_seq))
  36. goto advance_sp;
  37. //是否有需要跳过处理的skb
  38. skb = tcp_maybe_skipping_dsack(skb, sk, next_dup,
  39. &state,
  40. cache->end_seq);
  41. /* ...tail remains todo... */
  42. //如果刚好等于sack处理的最大序列号,则我们需要处理这个段。
  43. if (tcp_highest_sack_seq(tp) == cache->end_seq) {
  44. /* ...but better entrypoint exists! */
  45. skb = tcp_highest_sack(sk);
  46. if (skb == NULL)
  47. break;
  48. state.fack_count = tp->fackets_out;
  49. cache++;
  50. goto walk;
  51. }
  52. //再次检测是否有需要skip的段。
  53. skb = tcp_sacktag_skip(skb, sk, &state, cache->end_seq);
  54. ///紧接着处理下一个cache。
  55. cache++;
  56. continue;
  57. }
  58. //然后处理这次新的sack段。
  59. if (!before(start_seq, tcp_highest_sack_seq(tp))) {
  60. skb = tcp_highest_sack(sk);
  61. if (skb == NULL)
  62. break;
  63. state.fack_count = tp->fackets_out;
  64. }
  65. skb = tcp_sacktag_skip(skb, sk, &state, start_seq);
  66. walk:
  67. ///处理sack的段,主要是tag赋值。
  68. skb = tcp_sacktag_walk(skb, sk, next_dup, &state,
  69. start_seq, end_seq, dup_sack);
  70. advance_sp:
  71. /* SACK enhanced FRTO (RFC4138, Appendix B): Clearing correct
  72. * due to in-order walk
  73. */
  74. if (after(end_seq, tp->frto_highmark))
  75. state.flag &= ~FLAG_ONLY_ORIG_SACKED;
  76. i++;
  77. }
//首先取得写队列的头,以便与下面的遍历。
skb = tcp_write_queue_head(sk);
state.fack_count = 0;
i = 0;
///这里used_sacks表示我们需要处理的sack段的个数。
while (i < used_sacks) {
u32 start_seq = sp[i].start_seq;
u32 end_seq = sp[i].end_seq;
//得到是否是重复的sack
int dup_sack = (found_dup_sack && (i == first_sack_index));
struct tcp_sack_block *next_dup = NULL;
if (found_dup_sack && ((i + 1) == first_sack_index))
next_dup = &sp[i + 1];
//如果sack段的结束序列号大于将要发送的最大序列号,这个情况说明我们可能有数据丢失。因此设置丢失标记。这里可以看到也就是上面所说的事件B到达。
if (after(end_seq, tp->high_seq))
state.flag |= FLAG_DATA_LOST;
//跳过一些太老的cache
while (tcp_sack_cache_ok(tp, cache) &&
!before(start_seq, cache->end_seq))
cache++;
//如果有cache,就先处理cache的sack块。
if (tcp_sack_cache_ok(tp, cache) && !dup_sack &&
after(end_seq, cache->start_seq)) {
//如果当前的段的起始序列号小于cache的起始序列号(这个说明他们之间有交叉),则我们处理他们之间的段。
if (before(start_seq, cache->start_seq)) {
skb = tcp_sacktag_skip(skb, sk, &state,
start_seq);
skb = tcp_sacktag_walk(skb, sk, next_dup,
&state,
start_seq,
cache->start_seq,
dup_sack);
}
//处理剩下的块,也就是cache->end_seq和ned_seq之间的段。
if (!after(end_seq, cache->end_seq))
goto advance_sp;
//是否有需要跳过处理的skb
skb = tcp_maybe_skipping_dsack(skb, sk, next_dup,
&state,
cache->end_seq);
/* ...tail remains todo... */
//如果刚好等于sack处理的最大序列号,则我们需要处理这个段。
if (tcp_highest_sack_seq(tp) == cache->end_seq) {
/* ...but better entrypoint exists! */
skb = tcp_highest_sack(sk);
if (skb == NULL)
break;
state.fack_count = tp->fackets_out;
cache++;
goto walk;
}
//再次检测是否有需要skip的段。
skb = tcp_sacktag_skip(skb, sk, &state, cache->end_seq);
///紧接着处理下一个cache。
cache++;
continue;
}
//然后处理这次新的sack段。
if (!before(start_seq, tcp_highest_sack_seq(tp))) {
skb = tcp_highest_sack(sk);
if (skb == NULL)
break;
state.fack_count = tp->fackets_out;
}
skb = tcp_sacktag_skip(skb, sk, &state, start_seq);
walk:
///处理sack的段,主要是tag赋值。
skb = tcp_sacktag_walk(skb, sk, next_dup, &state,
start_seq, end_seq, dup_sack);
advance_sp:
/* SACK enhanced FRTO (RFC4138, Appendix B): Clearing correct
* due to in-order walk
*/
if (after(end_seq, tp->frto_highmark))
state.flag &= ~FLAG_ONLY_ORIG_SACKED;
i++;
}

上面的代码并不复杂,这里主要有两个函数,我们需要详细的来分析,一个是tcp_sacktag_skip,一个是tcp_sacktag_walk。

先来看tcp_sacktag_skip,我们给重传队列的skb的tag赋值时,我们需要遍历整个队列,可是由于我们有序列号,因此我们可以先确认起始的skb,然后从这个skb开始遍历,这里这个函数就是用来确认起始skb的,这里确认的步骤主要是通过start_seq来确认的。

Java代码  
  1. static struct sk_buff *tcp_sacktag_skip(struct sk_buff *skb, struct sock *sk,
  2. struct tcp_sacktag_state *state,
  3. u32 skip_to_seq)
  4. {
  5. //开始遍历重传队列。
  6. tcp_for_write_queue_from(skb, sk) {
  7. //如果当前的skb刚好等于发送队列的头,则说明我们这个是第一个数据包,则我们直接跳出循环。
  8. if (skb == tcp_send_head(sk))
  9. break;
  10. //如果skb的结束序列号大于我们传递进来的序列号,则说明这个skb包含了我们sack确认的段,因此我们退出循环。
  11. if (after(TCP_SKB_CB(skb)->end_seq, skip_to_seq))
  12. break;
  13. //更新fack的计数。
  14. state->fack_count += tcp_skb_pcount(skb);
  15. }
  16. //返回skb
  17. return skb;
  18. }
static struct sk_buff *tcp_sacktag_skip(struct sk_buff *skb, struct sock *sk,
struct tcp_sacktag_state *state,
u32 skip_to_seq)
{
//开始遍历重传队列。
tcp_for_write_queue_from(skb, sk) {
//如果当前的skb刚好等于发送队列的头,则说明我们这个是第一个数据包,则我们直接跳出循环。
if (skb == tcp_send_head(sk))
break;
//如果skb的结束序列号大于我们传递进来的序列号,则说明这个skb包含了我们sack确认的段,因此我们退出循环。
if (after(TCP_SKB_CB(skb)->end_seq, skip_to_seq))
break;
//更新fack的计数。
state->fack_count += tcp_skb_pcount(skb);
}
//返回skb
return skb;
}

然后是最关键的一个函数tcp_sacktag_walk,这个函数主要是遍历重传队列,找到对应需要设置的段,然后设置tcp_cb的sacked域为TCPCB_SACKED_ACKED,这里要注意,还有一种情况就是sack确认了多个skb,这个时候我们就需要合并这些skb,然后再处理。

然后来看代码。

Java代码  
  1. static struct sk_buff *tcp_sacktag_walk(struct sk_buff *skb, struct sock *sk,
  2. struct tcp_sack_block *next_dup,
  3. struct tcp_sacktag_state *state,
  4. u32 start_seq, u32 end_seq,
  5. int dup_sack_in)
  6. {
  7. struct tcp_sock *tp = tcp_sk(sk);
  8. struct sk_buff *tmp;
  9. //开始遍历skb队列。
  10. tcp_for_write_queue_from(skb, sk) {
  11. //in_sack不为0的话表示当前的skb就是我们要设置标记的skb。
  12. int in_sack = 0;
  13. int dup_sack = dup_sack_in;
  14. if (skb == tcp_send_head(sk))
  15. break;
  16. //由于skb是有序的,因此如果某个skb的序列号大于sack段的结束序列号,我们就退出循环。
  17. if (!before(TCP_SKB_CB(skb)->seq, end_seq))
  18. break;
  19. //如果存在next_dup,则判断是否需要进入处理。这里就是skb的序列号小于dup的结束序列号
  20. if ((next_dup != NULL) &&
  21. before(TCP_SKB_CB(skb)->seq, next_dup->end_seq)) {
  22. //返回值付给in_sack,也就是这个函数会返回当前skb是否能够被sack的段确认。
  23. in_sack = tcp_match_skb_to_sack(sk, skb,
  24. next_dup->start_seq,
  25. next_dup->end_seq);
  26. if (in_sack > 0)
  27. dup_sack = 1;
  28. }
  29. //如果小于等于0,则尝试着合并多个skb段(主要是由于可能一个sack段确认了多个skb,这样我们尝试着合并他们)
  30. if (in_sack <= 0) {
  31. tmp = tcp_shift_skb_data(sk, skb, state,
  32. start_seq, end_seq, dup_sack);
  33. //这里tmp就为我们合并成功的skb。
  34. if (tmp != NULL) {
  35. //如果不等,则我们从合并成功的skb重新开始处理。
  36. if (tmp != skb) {
  37. skb = tmp;
  38. continue;
  39. }
  40. in_sack = 0;
  41. } else {
  42. //否则我们单独处理这个skb
  43. in_sack = tcp_match_skb_to_sack(sk, skb,
  44. start_seq,
  45. end_seq);
  46. }
  47. }
  48. if (unlikely(in_sack < 0))
  49. break;
  50. ///如果in_sack大于0,则说明我们需要处理这个skb了。
  51. if (in_sack) {
  52. //开始处理skb,紧接着我们会分析这个函数。
  53. TCP_SKB_CB(skb)->sacked = tcp_sacktag_one(skb, sk,
  54. state,
  55. dup_sack,
  56. tcp_skb_pcount(skb));
  57. //是否需要更新sack处理的那个最大的skb。
  58. if (!before(TCP_SKB_CB(skb)->seq,
  59. tcp_highest_sack_seq(tp)))
  60. tcp_advance_highest_sack(sk, skb);
  61. }
  62. state->fack_count += tcp_skb_pcount(skb);
  63. }
  64. return skb;
  65. }
static struct sk_buff *tcp_sacktag_walk(struct sk_buff *skb, struct sock *sk,
struct tcp_sack_block *next_dup,
struct tcp_sacktag_state *state,
u32 start_seq, u32 end_seq,
int dup_sack_in)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *tmp;
//开始遍历skb队列。
tcp_for_write_queue_from(skb, sk) {
//in_sack不为0的话表示当前的skb就是我们要设置标记的skb。
int in_sack = 0;
int dup_sack = dup_sack_in;
if (skb == tcp_send_head(sk))
break;
//由于skb是有序的,因此如果某个skb的序列号大于sack段的结束序列号,我们就退出循环。
if (!before(TCP_SKB_CB(skb)->seq, end_seq))
break;
//如果存在next_dup,则判断是否需要进入处理。这里就是skb的序列号小于dup的结束序列号
if ((next_dup != NULL) &&
before(TCP_SKB_CB(skb)->seq, next_dup->end_seq)) {
//返回值付给in_sack,也就是这个函数会返回当前skb是否能够被sack的段确认。
in_sack = tcp_match_skb_to_sack(sk, skb,
next_dup->start_seq,
next_dup->end_seq);
if (in_sack > 0)
dup_sack = 1;
}
//如果小于等于0,则尝试着合并多个skb段(主要是由于可能一个sack段确认了多个skb,这样我们尝试着合并他们)
if (in_sack <= 0) {
tmp = tcp_shift_skb_data(sk, skb, state,
start_seq, end_seq, dup_sack);
//这里tmp就为我们合并成功的skb。
if (tmp != NULL) {
//如果不等,则我们从合并成功的skb重新开始处理。
if (tmp != skb) {
skb = tmp;
continue;
}
in_sack = 0;
} else {
//否则我们单独处理这个skb
in_sack = tcp_match_skb_to_sack(sk, skb,
start_seq,
end_seq);
}
}
if (unlikely(in_sack < 0))
break;
///如果in_sack大于0,则说明我们需要处理这个skb了。
if (in_sack) {
//开始处理skb,紧接着我们会分析这个函数。
TCP_SKB_CB(skb)->sacked = tcp_sacktag_one(skb, sk,
state,
dup_sack,
tcp_skb_pcount(skb));
//是否需要更新sack处理的那个最大的skb。
if (!before(TCP_SKB_CB(skb)->seq,
tcp_highest_sack_seq(tp)))
tcp_advance_highest_sack(sk, skb);
}
state->fack_count += tcp_skb_pcount(skb);
}
return skb;
}

然后我们来看tcp_sacktag_one函数,这个函数用来设置对应的tag,这里所要设置的也就是tcp_cb的sacked域。我们再来回顾一下它的值:

Java代码  
  1. #define TCPCB_SACKED_ACKED  0x01    /* SKB ACK'd by a SACK block    */
  2. #define TCPCB_SACKED_RETRANS    0x02    /* SKB retransmitted        */
  3. #define TCPCB_LOST      0x04    /* SKB is lost          */
  4. #define TCPCB_TAGBITS       0x07    /* All tag bits         */
  5. #define TCPCB_EVER_RETRANS  0x80    /* Ever retransmitted frame */
  6. #define TCPCB_RETRANS       (TCPCB_SACKED_RETRANS|TCPCB_EVER_RETRANS)
#define TCPCB_SACKED_ACKED   0x01    /* SKB ACK'd by a SACK block   */
#define TCPCB_SACKED_RETRANS    0x02    /* SKB retransmitted        */
#define TCPCB_LOST      0x04    /* SKB is lost          */
#define TCPCB_TAGBITS       0x07    /* All tag bits         */
#define TCPCB_EVER_RETRANS  0x80    /* Ever retransmitted frame */
#define TCPCB_RETRANS       (TCPCB_SACKED_RETRANS|TCPCB_EVER_RETRANS)

如果一切都正常的话,我们最终就会设置skb的这个域为TCPCB_SACKED_ACKED,也就是已经被sack过了。

这个函数处理比较简单,主要就是通过序列号以及sacked本身的值最终来确认sacked要被设置的值。

这里我们还记得,一开始sacked是被初始化为sack option的偏移(如果是正确的sack)的.

Java代码  
  1. static u8 tcp_sacktag_one(struct sk_buff *skb, struct sock *sk,
  2. struct tcp_sacktag_state *state,
  3. int dup_sack, int pcount)
  4. {
  5. struct tcp_sock *tp = tcp_sk(sk);
  6. u8 sacked = TCP_SKB_CB(skb)->sacked;
  7. int fack_count = state->fack_count;
  8. ...........................................................................................................
  9. //如果skb的结束序列号小于发送未确认的,则说明这个帧应当被丢弃。
  10. if (!after(TCP_SKB_CB(skb)->end_seq, tp->snd_una))
  11. return sacked;
  12. //如果当前的skb还未被sack确认过,则我们才会进入处理。
  13. if (!(sacked & TCPCB_SACKED_ACKED)) {
  14. //如果是重传被sack确认的。
  15. if (sacked & TCPCB_SACKED_RETRANS) {
  16. //如果设置了lost,则我们需要修改它的tag。
  17. if (sacked & TCPCB_LOST) {
  18. sacked &= ~(TCPCB_LOST|TCPCB_SACKED_RETRANS);
  19. //更新lost的数据包
  20. tp->lost_out -= pcount;
  21. tp->retrans_out -= pcount;
  22. }
  23. } else {
  24. ....................................................................................
  25. }
  26. //开始修改sacked,设置flag。
  27. sacked |= TCPCB_SACKED_ACKED;
  28. state->flag |= FLAG_DATA_SACKED;
  29. //增加sack确认的包的个数/
  30. tp->sacked_out += pcount;
  31. fack_count += pcount;
  32. //处理fack
  33. if (!tcp_is_fack(tp) && (tp->lost_skb_hint != NULL) &&
  34. before(TCP_SKB_CB(skb)->seq,
  35. TCP_SKB_CB(tp->lost_skb_hint)->seq))
  36. tp->lost_cnt_hint += pcount;
  37. if (fack_count > tp->fackets_out)
  38. tp->fackets_out = fack_count;
  39. }
  40. /* D-SACK. We can detect redundant retransmission in S|R and plain R
  41. * frames and clear it. undo_retrans is decreased above, L|R frames
  42. * are accounted above as well.
  43. */
  44. if (dup_sack && (sacked & TCPCB_SACKED_RETRANS)) {
  45. sacked &= ~TCPCB_SACKED_RETRANS;
  46. tp->retrans_out -= pcount;
  47. }
  48. return sacked;
  49. }
static u8 tcp_sacktag_one(struct sk_buff *skb, struct sock *sk,
struct tcp_sacktag_state *state,
int dup_sack, int pcount)
{
struct tcp_sock *tp = tcp_sk(sk);
u8 sacked = TCP_SKB_CB(skb)->sacked;
int fack_count = state->fack_count;
...........................................................................................................
//如果skb的结束序列号小于发送未确认的,则说明这个帧应当被丢弃。
if (!after(TCP_SKB_CB(skb)->end_seq, tp->snd_una))
return sacked;
//如果当前的skb还未被sack确认过,则我们才会进入处理。
if (!(sacked & TCPCB_SACKED_ACKED)) {
//如果是重传被sack确认的。
if (sacked & TCPCB_SACKED_RETRANS) {
//如果设置了lost,则我们需要修改它的tag。
if (sacked & TCPCB_LOST) {
sacked &= ~(TCPCB_LOST|TCPCB_SACKED_RETRANS);
//更新lost的数据包
tp->lost_out -= pcount;
tp->retrans_out -= pcount;
}
} else {
....................................................................................
}
//开始修改sacked,设置flag。
sacked |= TCPCB_SACKED_ACKED;
state->flag |= FLAG_DATA_SACKED;
//增加sack确认的包的个数/
tp->sacked_out += pcount;
fack_count += pcount;
//处理fack
if (!tcp_is_fack(tp) && (tp->lost_skb_hint != NULL) &&
before(TCP_SKB_CB(skb)->seq,
TCP_SKB_CB(tp->lost_skb_hint)->seq))
tp->lost_cnt_hint += pcount;
if (fack_count > tp->fackets_out)
tp->fackets_out = fack_count;
}
/* D-SACK. We can detect redundant retransmission in S|R and plain R
* frames and clear it. undo_retrans is decreased above, L|R frames
* are accounted above as well.
*/
if (dup_sack && (sacked & TCPCB_SACKED_RETRANS)) {
sacked &= ~TCPCB_SACKED_RETRANS;
tp->retrans_out -= pcount;
}
return sacked;
}

最后我们来看tcp_sacktag_write_queue的最后一部分,也就是更新cache的部分。

它也就是将处理过的sack清0,没处理过的保存到cache中。

Java代码  
  1. //开始遍历,可以看到这里将将我们未处理的sack段的序列号清0.
  2. for (i = 0; i < ARRAY_SIZE(tp->recv_sack_cache) - used_sacks; i++) {
  3. tp->recv_sack_cache[i].start_seq = 0;
  4. tp->recv_sack_cache[i].end_seq = 0;
  5. }
  6. //然后保存这次处理了的段。
  7. for (j = 0; j < used_sacks; j++)
  8. tp->recv_sack_cache[i++] = sp[j];
  9. //标记丢失的段。
  10. tcp_mark_lost_retrans(sk);
  11. tcp_verify_left_out(tp);
  12. if ((state.reord < tp->fackets_out) &&
  13. ((icsk->icsk_ca_state != TCP_CA_Loss) || tp->undo_marker) &&
  14. (!tp->frto_highmark || after(tp->snd_una, tp->frto_highmark)))
  15. tcp_update_reordering(sk, tp->fackets_out - state.reord, 0);
//开始遍历,可以看到这里将将我们未处理的sack段的序列号清0.
for (i = 0; i < ARRAY_SIZE(tp->recv_sack_cache) - used_sacks; i++) {
tp->recv_sack_cache[i].start_seq = 0;
tp->recv_sack_cache[i].end_seq = 0;
}
//然后保存这次处理了的段。
for (j = 0; j < used_sacks; j++)
tp->recv_sack_cache[i++] = sp[j];
//标记丢失的段。
tcp_mark_lost_retrans(sk);
tcp_verify_left_out(tp);
if ((state.reord < tp->fackets_out) &&
((icsk->icsk_ca_state != TCP_CA_Loss) || tp->undo_marker) &&
(!tp->frto_highmark || after(tp->snd_una, tp->frto_highmark)))
tcp_update_reordering(sk, tp->fackets_out - state.reord, 0);

内核tcp协议栈SACK的处理相关推荐

  1. 什么叫linux网络协议栈,我们为什么使用Linux内核的TCP协议栈

    最近的一篇文章提出了"我们为什么使用Linux内核的TCP协议栈"的问题,并在Hacker News引发了非常有意思的讨论. 在CloudFlare的时候我也曾思考这个问题.我的经 ...

  2. packetdrill 深入理解内核网络协议栈的工具集

    对内核网络协议栈的学习,验证和深度研究 packetdrill 将大事化小 深入packetdrill packetdrill 模拟包和验证包 packetdrill 设计 独特的packetdril ...

  3. Linux内核TCP/IP参数分析与调优

    如图展示的是TCP的三个阶段.1,TCP三次握手. 2,TCP数据传输. 3,TCP的四次挥手. SYN:(同步序列编号,Synchronize Sequence Numbers)该标志仅在三次握手建 ...

  4. linux内核网络协议栈--监控和调优:接收数据(十五)

    译者序 本文翻译自 2016 年的一篇英文博客 Monitoring and Tuning the Linux Networking Stack: Receiving Data.如果能看懂英文,建议阅 ...

  5. Linux 内核网络协议栈运行原理

    封装:当应用程序用 TCP 协议传送数据时,数据首先进入内核网络协议栈中,然后逐一通过 TCP/IP 协议族的每层直到被当作一串比特流送入网络.对于每一层而言,对收到的数据都会封装相应的协议首部信息( ...

  6. linux协议栈劫持,Linux系统优化之TCP协议栈优化-基本篇1

    因为在做爬虫分布式系统的过程中,涉及到了一些linux系统优化方面的知识,所以来总结一下,我们会对linux的不同模块做相关的基本优化,这篇文章主要讲述的是关于tcp协议栈的参数优化. 1.机器环境 ...

  7. Linux内核网络协议栈:udp数据包发送(源码解读)

    <监视和调整Linux网络协议栈:接收数据> <监控和调整Linux网络协议栈的图解指南:接收数据> <Linux网络 - 数据包的接收过程> <Linux网 ...

  8. 用户态TCP协议栈的调研

    一.各种用户态socket的对比 1.MTCP 简单介绍: 韩国高校的一个科研项目,在DPDK的2016年的技术开发者大会上有讲,所以intel将这个也放到了官方上,所以一般搜索DPDK的用户态的协议 ...

  9. tcp协议栈实现,tcp定时器与滑动窗口实现

    要实现用户态协议栈,必须要搞懂TCP,TCP 11个状态.滑动窗口.拥塞控制.定时器等等. 要使用用户态协议栈,内核提供的epoll就不起作用了,我们需要自己实现用户态的epoll.epoll内部涉及 ...

最新文章

  1. 使用Swagger2Markup实现API文档的静态部署(一):AsciiDoc
  2. 2.3.1 进程同步 进程互斥
  3. SQL Server---触发
  4. android ros 节点编写_嵌入式的我们为什么要学ROS
  5. openlayers之obj.js提供的功能函数
  6. 二道Const,readonly 和 override, new的面试题
  7. spring boot注解_Spring-boot(二)注解
  8. 结合webpack配置_呕心沥血编写的webpack多入口零基础配置 【建议收藏】
  9. 创建多模块springcloud应用eureka server和client和消费端demo
  10. cnblogs用户体验评价
  11. android富文本文件存储,Android富文本
  12. 西门子PLC开发笔记(一):PLC介绍,西门子S1200系列接线、编程、下载和仿真
  13. 编程开发必须用到的工具书MSDN中文版
  14. 常用编程语言命令大全
  15. python生成泊松分布_Python Numpy泊松分布
  16. 红米Note8手机图纸-电路原理图+主板元件位号图
  17. 运用jQuery实现一个简易轮播图
  18. FreeRtos(1)-----任务创建与管理
  19. 74HC02或非门仿真示例
  20. 米狗族 Android平台Qt开发入门教程

热门文章

  1. python爬取微博图片教程_Python爬取微博实例分析
  2. 山东大学网络靶场实验平台—团队进度(三)
  3. UEFI EDK2 遇到的问题集合
  4. 前端网站资源精编!!
  5. MySQL索引优化(二)索引失效
  6. 15-EMM Procedure 5. Periodic TAU
  7. Java——迷你图书管理器(JDBC+MySQL+Apache DBUtils)
  8. PHP——人人都会编程
  9. 跨境电商前景 跨境电商运营前景待遇
  10. js 数组扁平化和树之间相互转换