tcp: track reorder with sacks
[vpp.git] / src / vnet / tcp / tcp_sack.c
1 /*
2  * Copyright (c) 2020 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 #include <vnet/tcp/tcp_sack.h>
17
18 static void
19 scoreboard_remove_hole (sack_scoreboard_t * sb, sack_scoreboard_hole_t * hole)
20 {
21   sack_scoreboard_hole_t *next, *prev;
22
23   if (hole->next != TCP_INVALID_SACK_HOLE_INDEX)
24     {
25       next = pool_elt_at_index (sb->holes, hole->next);
26       next->prev = hole->prev;
27     }
28   else
29     {
30       sb->tail = hole->prev;
31     }
32
33   if (hole->prev != TCP_INVALID_SACK_HOLE_INDEX)
34     {
35       prev = pool_elt_at_index (sb->holes, hole->prev);
36       prev->next = hole->next;
37     }
38   else
39     {
40       sb->head = hole->next;
41     }
42
43   if (scoreboard_hole_index (sb, hole) == sb->cur_rxt_hole)
44     sb->cur_rxt_hole = TCP_INVALID_SACK_HOLE_INDEX;
45
46   /* Poison the entry */
47   if (CLIB_DEBUG > 0)
48     clib_memset (hole, 0xfe, sizeof (*hole));
49
50   pool_put (sb->holes, hole);
51 }
52
53 static sack_scoreboard_hole_t *
54 scoreboard_insert_hole (sack_scoreboard_t * sb, u32 prev_index,
55                         u32 start, u32 end)
56 {
57   sack_scoreboard_hole_t *hole, *next, *prev;
58   u32 hole_index;
59
60   pool_get (sb->holes, hole);
61   clib_memset (hole, 0, sizeof (*hole));
62
63   hole->start = start;
64   hole->end = end;
65   hole_index = scoreboard_hole_index (sb, hole);
66
67   prev = scoreboard_get_hole (sb, prev_index);
68   if (prev)
69     {
70       hole->prev = prev_index;
71       hole->next = prev->next;
72
73       if ((next = scoreboard_next_hole (sb, hole)))
74         next->prev = hole_index;
75       else
76         sb->tail = hole_index;
77
78       prev->next = hole_index;
79     }
80   else
81     {
82       sb->head = hole_index;
83       hole->prev = TCP_INVALID_SACK_HOLE_INDEX;
84       hole->next = TCP_INVALID_SACK_HOLE_INDEX;
85     }
86
87   return hole;
88 }
89
90 always_inline void
91 scoreboard_update_sacked (sack_scoreboard_t * sb, u32 start, u32 end,
92                           u8 has_rxt, u16 snd_mss)
93 {
94   if (!has_rxt)
95     {
96       /* Sequence was not retransmitted but it was sacked. Estimate reorder
97        * only if not in congestion recovery */
98       if (seq_lt (start, sb->high_sacked))
99         {
100           u32 reord = (sb->high_sacked - start + snd_mss - 1) / snd_mss;
101           reord = clib_min (reord, TCP_MAX_SACK_REORDER);
102           sb->reorder = clib_max (sb->reorder, reord);
103         }
104       return;
105     }
106
107   if (seq_geq (start, sb->high_rxt))
108     return;
109
110   sb->rxt_sacked +=
111     seq_lt (end, sb->high_rxt) ? (end - start) : (sb->high_rxt - start);
112 }
113
114 always_inline void
115 scoreboard_update_bytes (sack_scoreboard_t * sb, u32 ack, u32 snd_mss)
116 {
117   sack_scoreboard_hole_t *left, *right;
118   u32 sacked = 0, blks = 0, old_sacked;
119
120   old_sacked = sb->sacked_bytes;
121
122   sb->last_lost_bytes = 0;
123   sb->lost_bytes = 0;
124   sb->sacked_bytes = 0;
125
126   right = scoreboard_last_hole (sb);
127   if (!right)
128     {
129       sb->sacked_bytes = sb->high_sacked - ack;
130       sb->last_sacked_bytes = sb->sacked_bytes
131         - (old_sacked - sb->last_bytes_delivered);
132       return;
133     }
134
135   if (seq_gt (sb->high_sacked, right->end))
136     {
137       sacked = sb->high_sacked - right->end;
138       blks = 1;
139     }
140
141   /* As per RFC 6675 a sequence number is lost if:
142    *   DupThresh discontiguous SACKed sequences have arrived above
143    *   'SeqNum' or more than (DupThresh - 1) * SMSS bytes with sequence
144    *   numbers greater than 'SeqNum' have been SACKed.
145    * To avoid spurious retransmits, use reordering estimate instead of
146    * DupThresh to detect loss.
147    */
148   while (sacked <= (sb->reorder - 1) * snd_mss && blks < sb->reorder)
149     {
150       if (right->is_lost)
151         sb->lost_bytes += scoreboard_hole_bytes (right);
152
153       left = scoreboard_prev_hole (sb, right);
154       if (!left)
155         {
156           ASSERT (right->start == ack || sb->is_reneging);
157           sacked += right->start - ack;
158           right = 0;
159           break;
160         }
161
162       sacked += right->start - left->end;
163       blks++;
164       right = left;
165     }
166
167   /* right is first lost */
168   while (right)
169     {
170       sb->lost_bytes += scoreboard_hole_bytes (right);
171       sb->last_lost_bytes += right->is_lost ? 0 : (right->end - right->start);
172       right->is_lost = 1;
173       left = scoreboard_prev_hole (sb, right);
174       if (!left)
175         {
176           ASSERT (right->start == ack || sb->is_reneging);
177           sacked += right->start - ack;
178           break;
179         }
180       sacked += right->start - left->end;
181       right = left;
182     }
183
184   sb->sacked_bytes = sacked;
185   sb->last_sacked_bytes = sacked - (old_sacked - sb->last_bytes_delivered);
186 }
187
188 /**
189  * Figure out the next hole to retransmit
190  *
191  * Follows logic proposed in RFC6675 Sec. 4, NextSeg()
192  */
193 sack_scoreboard_hole_t *
194 scoreboard_next_rxt_hole (sack_scoreboard_t * sb,
195                           sack_scoreboard_hole_t * start,
196                           u8 have_unsent, u8 * can_rescue, u8 * snd_limited)
197 {
198   sack_scoreboard_hole_t *hole = 0;
199
200   hole = start ? start : scoreboard_first_hole (sb);
201   while (hole && seq_leq (hole->end, sb->high_rxt) && hole->is_lost)
202     hole = scoreboard_next_hole (sb, hole);
203
204   /* Nothing, return */
205   if (!hole)
206     {
207       sb->cur_rxt_hole = TCP_INVALID_SACK_HOLE_INDEX;
208       return 0;
209     }
210
211   /* Rule (1): if higher than rxt, less than high_sacked and lost */
212   if (hole->is_lost && seq_lt (hole->start, sb->high_sacked))
213     {
214       sb->cur_rxt_hole = scoreboard_hole_index (sb, hole);
215     }
216   else
217     {
218       /* Rule (2): available unsent data */
219       if (have_unsent)
220         {
221           sb->cur_rxt_hole = TCP_INVALID_SACK_HOLE_INDEX;
222           return 0;
223         }
224       /* Rule (3): if hole not lost */
225       else if (seq_lt (hole->start, sb->high_sacked))
226         {
227           /* And we didn't already retransmit it */
228           if (seq_leq (hole->end, sb->high_rxt))
229             {
230               sb->cur_rxt_hole = TCP_INVALID_SACK_HOLE_INDEX;
231               return 0;
232             }
233           *snd_limited = 0;
234           sb->cur_rxt_hole = scoreboard_hole_index (sb, hole);
235         }
236       /* Rule (4): if hole beyond high_sacked */
237       else
238         {
239           ASSERT (seq_geq (hole->start, sb->high_sacked));
240           *snd_limited = 1;
241           *can_rescue = 1;
242           /* HighRxt MUST NOT be updated */
243           return 0;
244         }
245     }
246
247   if (hole && seq_lt (sb->high_rxt, hole->start))
248     sb->high_rxt = hole->start;
249
250   return hole;
251 }
252
253 void
254 scoreboard_init_rxt (sack_scoreboard_t * sb, u32 snd_una)
255 {
256   sack_scoreboard_hole_t *hole;
257   hole = scoreboard_first_hole (sb);
258   if (hole)
259     {
260       snd_una = seq_gt (snd_una, hole->start) ? snd_una : hole->start;
261       sb->cur_rxt_hole = sb->head;
262     }
263   sb->high_rxt = snd_una;
264   sb->rescue_rxt = snd_una - 1;
265 }
266
267 void
268 scoreboard_init (sack_scoreboard_t * sb)
269 {
270   sb->head = TCP_INVALID_SACK_HOLE_INDEX;
271   sb->tail = TCP_INVALID_SACK_HOLE_INDEX;
272   sb->cur_rxt_hole = TCP_INVALID_SACK_HOLE_INDEX;
273   sb->reorder = TCP_DUPACK_THRESHOLD;
274 }
275
276 void
277 scoreboard_clear (sack_scoreboard_t * sb)
278 {
279   sack_scoreboard_hole_t *hole;
280   while ((hole = scoreboard_first_hole (sb)))
281     {
282       scoreboard_remove_hole (sb, hole);
283     }
284   ASSERT (sb->head == sb->tail && sb->head == TCP_INVALID_SACK_HOLE_INDEX);
285   ASSERT (pool_elts (sb->holes) == 0);
286   sb->sacked_bytes = 0;
287   sb->last_sacked_bytes = 0;
288   sb->last_bytes_delivered = 0;
289   sb->lost_bytes = 0;
290   sb->last_lost_bytes = 0;
291   sb->cur_rxt_hole = TCP_INVALID_SACK_HOLE_INDEX;
292   sb->is_reneging = 0;
293   sb->reorder = TCP_DUPACK_THRESHOLD;
294 }
295
296 void
297 scoreboard_clear_reneging (sack_scoreboard_t * sb, u32 start, u32 end)
298 {
299   sack_scoreboard_hole_t *last_hole;
300
301   scoreboard_clear (sb);
302   last_hole = scoreboard_insert_hole (sb, TCP_INVALID_SACK_HOLE_INDEX,
303                                       start, end);
304   last_hole->is_lost = 1;
305   sb->tail = scoreboard_hole_index (sb, last_hole);
306   sb->high_sacked = start;
307   scoreboard_init_rxt (sb, start);
308 }
309
310 /**
311  * Test that scoreboard is sane after recovery
312  *
313  * Returns 1 if scoreboard is empty or if first hole beyond
314  * snd_una.
315  */
316 u8
317 tcp_scoreboard_is_sane_post_recovery (tcp_connection_t * tc)
318 {
319   sack_scoreboard_hole_t *hole;
320   hole = scoreboard_first_hole (&tc->sack_sb);
321   return (!hole || (seq_geq (hole->start, tc->snd_una)
322                     && seq_lt (hole->end, tc->snd_nxt)));
323 }
324
325 void
326 tcp_rcv_sacks (tcp_connection_t * tc, u32 ack)
327 {
328   sack_scoreboard_hole_t *hole, *next_hole;
329   sack_scoreboard_t *sb = &tc->sack_sb;
330   sack_block_t *blk, *rcv_sacks;
331   u32 blk_index = 0, i, j, high_sacked;
332   u8 has_rxt;
333
334   sb->last_sacked_bytes = 0;
335   sb->last_bytes_delivered = 0;
336   sb->rxt_sacked = 0;
337
338   if (!tcp_opts_sack (&tc->rcv_opts) && !sb->sacked_bytes
339       && sb->head == TCP_INVALID_SACK_HOLE_INDEX)
340     return;
341
342   has_rxt = tcp_in_cong_recovery (tc);
343
344   /* Remove invalid blocks */
345   blk = tc->rcv_opts.sacks;
346   while (blk < vec_end (tc->rcv_opts.sacks))
347     {
348       if (seq_lt (blk->start, blk->end)
349           && seq_gt (blk->start, tc->snd_una)
350           && seq_gt (blk->start, ack)
351           && seq_lt (blk->start, tc->snd_nxt)
352           && seq_leq (blk->end, tc->snd_nxt))
353         {
354           blk++;
355           continue;
356         }
357       vec_del1 (tc->rcv_opts.sacks, blk - tc->rcv_opts.sacks);
358     }
359
360   /* Add block for cumulative ack */
361   if (seq_gt (ack, tc->snd_una))
362     {
363       vec_add2 (tc->rcv_opts.sacks, blk, 1);
364       blk->start = tc->snd_una;
365       blk->end = ack;
366     }
367
368   if (vec_len (tc->rcv_opts.sacks) == 0)
369     return;
370
371   tcp_scoreboard_trace_add (tc, ack);
372
373   /* Make sure blocks are ordered */
374   rcv_sacks = tc->rcv_opts.sacks;
375   for (i = 0; i < vec_len (rcv_sacks); i++)
376     for (j = i + 1; j < vec_len (rcv_sacks); j++)
377       if (seq_lt (rcv_sacks[j].start, rcv_sacks[i].start))
378         {
379           sack_block_t tmp = rcv_sacks[i];
380           rcv_sacks[i] = rcv_sacks[j];
381           rcv_sacks[j] = tmp;
382         }
383
384   if (sb->head == TCP_INVALID_SACK_HOLE_INDEX)
385     {
386       /* Handle reneging as a special case */
387       if (PREDICT_FALSE (sb->is_reneging))
388         {
389           /* No holes, only sacked bytes */
390           if (seq_leq (tc->snd_nxt, sb->high_sacked))
391             {
392               /* No progress made so return */
393               if (seq_leq (ack, tc->snd_una))
394                 return;
395
396               /* Update sacked bytes delivered and return */
397               sb->last_bytes_delivered = ack - tc->snd_una;
398               sb->sacked_bytes -= sb->last_bytes_delivered;
399               sb->is_reneging = seq_lt (ack, sb->high_sacked);
400               return;
401             }
402
403           /* New hole above high sacked. Add it and process normally */
404           hole = scoreboard_insert_hole (sb, TCP_INVALID_SACK_HOLE_INDEX,
405                                          sb->high_sacked, tc->snd_nxt);
406           sb->tail = scoreboard_hole_index (sb, hole);
407         }
408       /* Not reneging and no holes. Insert the first that covers all
409        * outstanding bytes */
410       else
411         {
412           hole = scoreboard_insert_hole (sb, TCP_INVALID_SACK_HOLE_INDEX,
413                                          tc->snd_una, tc->snd_nxt);
414           sb->tail = scoreboard_hole_index (sb, hole);
415           sb->high_sacked = tc->snd_una;
416         }
417       high_sacked = rcv_sacks[vec_len (rcv_sacks) - 1].end;
418     }
419   else
420     {
421       /* If we have holes but snd_nxt is beyond the last hole, update
422        * last hole end or add new hole after high sacked */
423       hole = scoreboard_last_hole (sb);
424       if (seq_gt (tc->snd_nxt, hole->end))
425         {
426           if (seq_geq (hole->start, sb->high_sacked))
427             {
428               hole->end = tc->snd_nxt;
429             }
430           /* New hole after high sacked block */
431           else if (seq_lt (sb->high_sacked, tc->snd_nxt))
432             {
433               scoreboard_insert_hole (sb, sb->tail, sb->high_sacked,
434                                       tc->snd_nxt);
435             }
436         }
437       /* Keep track of max byte sacked for when the last hole
438        * is acked */
439       high_sacked = seq_max (rcv_sacks[vec_len (rcv_sacks) - 1].end,
440                              sb->high_sacked);
441     }
442
443   /* Walk the holes with the SACK blocks */
444   hole = pool_elt_at_index (sb->holes, sb->head);
445
446   if (PREDICT_FALSE (sb->is_reneging))
447     {
448       sb->last_bytes_delivered += clib_min (hole->start - tc->snd_una,
449                                             ack - tc->snd_una);
450       sb->is_reneging = seq_lt (ack, hole->start);
451     }
452
453   while (hole && blk_index < vec_len (rcv_sacks))
454     {
455       blk = &rcv_sacks[blk_index];
456       if (seq_leq (blk->start, hole->start))
457         {
458           /* Block covers hole. Remove hole */
459           if (seq_geq (blk->end, hole->end))
460             {
461               next_hole = scoreboard_next_hole (sb, hole);
462
463               /* If covered by ack, compute delivered bytes */
464               if (blk->end == ack)
465                 {
466                   u32 sacked = next_hole ? next_hole->start :
467                     seq_max (sb->high_sacked, hole->end);
468                   if (PREDICT_FALSE (seq_lt (ack, sacked)))
469                     {
470                       sb->last_bytes_delivered += ack - hole->end;
471                       sb->is_reneging = 1;
472                     }
473                   else
474                     {
475                       sb->last_bytes_delivered += sacked - hole->end;
476                       sb->is_reneging = 0;
477                     }
478                 }
479               scoreboard_update_sacked (sb, hole->start, hole->end,
480                                         has_rxt, tc->snd_mss);
481               scoreboard_remove_hole (sb, hole);
482               hole = next_hole;
483             }
484           /* Partial 'head' overlap */
485           else
486             {
487               if (seq_gt (blk->end, hole->start))
488                 {
489                   scoreboard_update_sacked (sb, hole->start, blk->end,
490                                             has_rxt, tc->snd_mss);
491                   hole->start = blk->end;
492                 }
493               blk_index++;
494             }
495         }
496       else
497         {
498           /* Hole must be split */
499           if (seq_lt (blk->end, hole->end))
500             {
501               u32 hole_index = scoreboard_hole_index (sb, hole);
502               next_hole = scoreboard_insert_hole (sb, hole_index, blk->end,
503                                                   hole->end);
504               /* Pool might've moved */
505               hole = scoreboard_get_hole (sb, hole_index);
506               hole->end = blk->start;
507               next_hole->is_lost = hole->is_lost;
508
509               scoreboard_update_sacked (sb, blk->start, blk->end,
510                                         has_rxt, tc->snd_mss);
511
512               blk_index++;
513               ASSERT (hole->next == scoreboard_hole_index (sb, next_hole));
514             }
515           else if (seq_lt (blk->start, hole->end))
516             {
517               scoreboard_update_sacked (sb, blk->start, hole->end,
518                                         has_rxt, tc->snd_mss);
519               hole->end = blk->start;
520             }
521           hole = scoreboard_next_hole (sb, hole);
522         }
523     }
524
525   sb->high_sacked = high_sacked;
526   scoreboard_update_bytes (sb, ack, tc->snd_mss);
527
528   ASSERT (sb->last_sacked_bytes <= sb->sacked_bytes || tcp_in_recovery (tc));
529   ASSERT (sb->sacked_bytes == 0 || tcp_in_recovery (tc)
530           || sb->sacked_bytes <= tc->snd_nxt - seq_max (tc->snd_una, ack));
531   ASSERT (sb->last_sacked_bytes + sb->lost_bytes <= tc->snd_nxt
532           - seq_max (tc->snd_una, ack) || tcp_in_recovery (tc));
533   ASSERT (sb->head == TCP_INVALID_SACK_HOLE_INDEX || tcp_in_recovery (tc)
534           || sb->is_reneging || sb->holes[sb->head].start == ack);
535   ASSERT (sb->last_lost_bytes <= sb->lost_bytes);
536   ASSERT ((ack - tc->snd_una) + sb->last_sacked_bytes
537           - sb->last_bytes_delivered >= sb->rxt_sacked);
538   ASSERT ((ack - tc->snd_una) >= tc->sack_sb.last_bytes_delivered
539           || (tc->flags & TCP_CONN_FINSNT));
540
541   TCP_EVT (TCP_EVT_CC_SCOREBOARD, tc);
542 }
543
544 static u8
545 tcp_sack_vector_is_sane (sack_block_t * sacks)
546 {
547   int i;
548   for (i = 1; i < vec_len (sacks); i++)
549     {
550       if (sacks[i - 1].end == sacks[i].start)
551         return 0;
552     }
553   return 1;
554 }
555
556 /**
557  * Build SACK list as per RFC2018.
558  *
559  * Makes sure the first block contains the segment that generated the current
560  * ACK and the following ones are the ones most recently reported in SACK
561  * blocks.
562  *
563  * @param tc TCP connection for which the SACK list is updated
564  * @param start Start sequence number of the newest SACK block
565  * @param end End sequence of the newest SACK block
566  */
567 void
568 tcp_update_sack_list (tcp_connection_t * tc, u32 start, u32 end)
569 {
570   sack_block_t *new_list = tc->snd_sacks_fl, *block = 0;
571   int i;
572
573   /* If the first segment is ooo add it to the list. Last write might've moved
574    * rcv_nxt over the first segment. */
575   if (seq_lt (tc->rcv_nxt, start))
576     {
577       vec_add2 (new_list, block, 1);
578       block->start = start;
579       block->end = end;
580     }
581
582   /* Find the blocks still worth keeping. */
583   for (i = 0; i < vec_len (tc->snd_sacks); i++)
584     {
585       /* Discard if rcv_nxt advanced beyond current block */
586       if (seq_leq (tc->snd_sacks[i].start, tc->rcv_nxt))
587         continue;
588
589       /* Merge or drop if segment overlapped by the new segment */
590       if (block && (seq_geq (tc->snd_sacks[i].end, new_list[0].start)
591                     && seq_leq (tc->snd_sacks[i].start, new_list[0].end)))
592         {
593           if (seq_lt (tc->snd_sacks[i].start, new_list[0].start))
594             new_list[0].start = tc->snd_sacks[i].start;
595           if (seq_lt (new_list[0].end, tc->snd_sacks[i].end))
596             new_list[0].end = tc->snd_sacks[i].end;
597           continue;
598         }
599
600       /* Save to new SACK list if we have space. */
601       if (vec_len (new_list) < TCP_MAX_SACK_BLOCKS)
602         vec_add1 (new_list, tc->snd_sacks[i]);
603     }
604
605   ASSERT (vec_len (new_list) <= TCP_MAX_SACK_BLOCKS);
606
607   /* Replace old vector with new one */
608   vec_reset_length (tc->snd_sacks);
609   tc->snd_sacks_fl = tc->snd_sacks;
610   tc->snd_sacks = new_list;
611
612   /* Segments should not 'touch' */
613   ASSERT (tcp_sack_vector_is_sane (tc->snd_sacks));
614 }
615
616 u32
617 tcp_sack_list_bytes (tcp_connection_t * tc)
618 {
619   u32 bytes = 0, i;
620   for (i = 0; i < vec_len (tc->snd_sacks); i++)
621     bytes += tc->snd_sacks[i].end - tc->snd_sacks[i].start;
622   return bytes;
623 }
624
625 /*
626  * fd.io coding-style-patch-verification: ON
627  *
628  * Local Variables:
629  * eval: (c-set-style "gnu")
630  * End:
631  */