]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/netinet/tcp_stacks/sack_filter.c
Optionally bind ktls threads to NUMA domains
[FreeBSD/FreeBSD.git] / sys / netinet / tcp_stacks / sack_filter.c
1 /*-
2  * Copyright (c) 2017-9 Netflix, Inc.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23  * SUCH DAMAGE.
24  *
25  */
26 #include <sys/cdefs.h>
27 __FBSDID("$FreeBSD$");
28 #ifndef _KERNEL
29 #define _WANT_TCPCB 1
30 #endif
31 #include <sys/types.h>
32 #include <sys/queue.h>
33 #include <sys/socket.h>
34 #ifdef _KERNEL
35 #include <sys/mbuf.h>
36 #include <sys/sockopt.h>
37 #endif
38 #include <netinet/tcp.h>
39 #include <netinet/tcp_var.h>
40 #include <netinet/tcp_seq.h>
41 #ifndef _KERNEL
42 #include <stdio.h>
43 #include <unistd.h>
44 #include <string.h>
45 #include <strings.h>
46 #include <stdlib.h>
47 #include <limits.h>
48 #include <getopt.h>
49 #endif
50 #include "sack_filter.h"
51
52 /*
53  * Sack filter is used to filter out sacks
54  * that have already been processed. The idea
55  * is pretty simple really, consider two sacks
56  *
57  * SACK 1
58  *   cum-ack A
59  *     sack B - C
60  * SACK 2
61  *   cum-ack A
62  *     sack D - E
63  *     sack B - C
64  *
65  * The previous sack information (B-C) is repeated
66  * in SACK 2. If the receiver gets SACK 1 and then
67  * SACK 2 then any work associated with B-C as already
68  * been completed. This only effects where we may have
69  * (as in bbr or rack) cases where we walk a linked list.
70  *
71  * Now the utility trys to keep everything in a single
72  * cache line. This means that its not perfect and
73  * it could be that so big of sack's come that a
74  * "remembered" processed sack falls off the list and
75  * so gets re-processed. Thats ok, it just means we
76  * did some extra work. We could of course take more
77  * cache line hits by expanding the size of this
78  * structure, but then that would cost more.
79  */
80
81 #ifndef _KERNEL
82 int detailed_dump = 0;
83 uint64_t cnt_skipped_oldsack = 0;
84 uint64_t cnt_used_oldsack = 0;
85 int highest_used=0;
86 int over_written=0;
87 int empty_avail=0;
88 int no_collapse = 0;
89 FILE *out = NULL;
90 FILE *in = NULL;
91 #endif
92
93 #define sack_blk_used(sf, i) ((1 << i) & sf->sf_bits)
94 #define sack_blk_set(sf, i) ((1 << i) | sf->sf_bits)
95 #define sack_blk_clr(sf, i) (~(1 << i) & sf->sf_bits)
96
97 #ifndef _KERNEL
98 static
99 #endif
100 void
101 sack_filter_clear(struct sack_filter *sf, tcp_seq seq)
102 {
103         sf->sf_ack = seq;
104         sf->sf_bits = 0;
105         sf->sf_cur = 0;
106         sf->sf_used = 0;
107 }
108 /*
109  * Given a previous sack filter block, filter out
110  * any entries where the cum-ack moves over them
111  * fully or partially.
112  */
113 static void
114 sack_filter_prune(struct sack_filter *sf, tcp_seq th_ack)
115 {
116         int32_t i;
117         /* start with the oldest */
118         for (i = 0; i < SACK_FILTER_BLOCKS; i++) {
119                 if (sack_blk_used(sf, i)) {
120                         if (SEQ_GT(th_ack, sf->sf_blks[i].end)) {
121                                 /* This block is consumed */
122                                 sf->sf_bits = sack_blk_clr(sf, i);
123                                 sf->sf_used--;
124                         } else if (SEQ_GT(th_ack, sf->sf_blks[i].start)) {
125                                 /* Some of it is acked */
126                                 sf->sf_blks[i].start = th_ack;
127                                 /* We could in theory break here, but
128                                  * there are some broken implementations
129                                  * that send multiple blocks. We want
130                                  * to catch them all with similar seq's.
131                                  */
132                         }
133                 }
134         }
135         sf->sf_ack = th_ack;
136 }
137
138 /*
139  * Return true if you find that
140  * the sackblock b is on the score
141  * board. Update it along the way
142  * if part of it is on the board.
143  */
144 static int32_t
145 is_sack_on_board(struct sack_filter *sf, struct sackblk *b)
146 {
147         int32_t i, cnt;
148
149         for (i = sf->sf_cur, cnt=0; cnt < SACK_FILTER_BLOCKS; cnt++) {
150                 if (sack_blk_used(sf, i)) {
151                         if (SEQ_LT(b->start, sf->sf_ack)) {
152                                 /* Behind cum-ack update */
153                                 b->start = sf->sf_ack;
154                         }
155                         if (SEQ_LT(b->end, sf->sf_ack)) {
156                                 /* End back behind too */
157                                 b->end = sf->sf_ack;
158                         }
159                         if (b->start == b->end) {
160                                 return(1);
161                         }
162                         /* Jonathans Rule 1 */
163                         if (SEQ_LEQ(sf->sf_blks[i].start, b->start) &&
164                             SEQ_GEQ(sf->sf_blks[i].end, b->end)) {
165                                 /**
166                                  * Our board has this entirely in
167                                  * whole or in part:
168                                  *
169                                  * board  |-------------|
170                                  * sack   |-------------|
171                                  * <or>
172                                  * board  |-------------|
173                                  * sack       |----|
174                                  *
175                                  */
176                                 return(1);
177                         }
178                         /* Jonathans Rule 2 */
179                         if(SEQ_LT(sf->sf_blks[i].end, b->start)) {
180                                 /**
181                                  * Not near each other:
182                                  *
183                                  * board   |---|
184                                  * sack           |---|
185                                  */
186                                 goto nxt_blk;
187                         }
188                         /* Jonathans Rule 3 */
189                         if (SEQ_GT(sf->sf_blks[i].start, b->end)) {
190                                 /**
191                                  * Not near each other:
192                                  *
193                                  * board         |---|
194                                  * sack  |---|
195                                  */
196                                 goto nxt_blk;
197                         }
198                         if (SEQ_LEQ(sf->sf_blks[i].start, b->start)) {
199                                 /**
200                                  * The board block partial meets:
201                                  *
202                                  *  board   |--------|
203                                  *  sack        |----------|
204                                  *    <or>
205                                  *  board   |--------|
206                                  *  sack    |--------------|
207                                  *
208                                  * up with this one (we have part of it).
209                                  * 1) Update the board block to the new end
210                                  *      and
211                                  * 2) Update the start of this block to my end.
212                                  */
213                                 b->start = sf->sf_blks[i].end;
214                                 sf->sf_blks[i].end = b->end;
215                                 goto nxt_blk;
216                         }
217                         if (SEQ_GEQ(sf->sf_blks[i].end, b->end)) {
218                                 /**
219                                  * The board block partial meets:
220                                  *
221                                  *  board       |--------|
222                                  *  sack  |----------|
223                                  *     <or>
224                                  *  board       |----|
225                                  *  sack  |----------|
226                                  * 1) Update the board block to the new start
227                                  *      and
228                                  * 2) Update the start of this block to my end.
229                                  */
230                                 b->end = sf->sf_blks[i].start;
231                                 sf->sf_blks[i].start = b->start;
232                                 goto nxt_blk;
233                         }
234                 }
235         nxt_blk:
236                 i++;
237                 i %= SACK_FILTER_BLOCKS;
238         }
239         /* Did we totally consume it in pieces? */
240         if (b->start != b->end)
241                 return(0);
242         else
243                 return(1);
244 }
245
246 static int32_t
247 sack_filter_old(struct sack_filter *sf, struct sackblk *in, int  numblks)
248 {
249         int32_t num, i;
250         struct sackblk blkboard[TCP_MAX_SACK];
251         /*
252          * An old sack has arrived. It may contain data
253          * we do not have. We might not have it since
254          * we could have had a lost ack <or> we might have the
255          * entire thing on our current board. We want to prune
256          * off anything we have. With this function though we
257          * won't add to the board.
258          */
259         for( i = 0, num = 0; i<numblks; i++ ) {
260                 if (is_sack_on_board(sf, &in[i])) {
261 #ifndef _KERNEL
262                         cnt_skipped_oldsack++;
263 #endif
264                         continue;
265                 }
266                 /* Did not find it (or found only
267                  * a piece of it). Copy it to
268                  * our outgoing board.
269                  */
270                 memcpy(&blkboard[num], &in[i], sizeof(struct sackblk));
271 #ifndef _KERNEL
272                 cnt_used_oldsack++;
273 #endif
274                 num++;
275         }
276         if (num) {
277                 memcpy(in, blkboard, (num * sizeof(struct sackblk)));
278         }
279         return (num);
280 }
281
282 /*
283  * Given idx its used but there is space available
284  * move the entry to the next free slot
285  */
286 static void
287 sack_move_to_empty(struct sack_filter *sf, uint32_t idx)
288 {
289         int32_t i, cnt;
290
291         i = (idx + 1) % SACK_FILTER_BLOCKS;
292         for (cnt=0; cnt <(SACK_FILTER_BLOCKS-1); cnt++) {
293                 if (sack_blk_used(sf, i) == 0) {
294                         memcpy(&sf->sf_blks[i], &sf->sf_blks[idx], sizeof(struct sackblk));
295                         sf->sf_bits = sack_blk_clr(sf, idx);
296                         sf->sf_bits = sack_blk_set(sf, i);
297                         return;
298                 }
299                 i++;
300                 i %= SACK_FILTER_BLOCKS;
301         }
302 }
303
304 static int32_t
305 sack_filter_new(struct sack_filter *sf, struct sackblk *in, int numblks, tcp_seq th_ack)
306 {
307         struct sackblk blkboard[TCP_MAX_SACK];
308         int32_t num, i;
309         /*
310          * First lets trim the old and possibly
311          * throw any away we have.
312          */
313         for(i=0, num=0; i<numblks; i++) {
314                 if (is_sack_on_board(sf, &in[i]))
315                         continue;
316                 memcpy(&blkboard[num], &in[i], sizeof(struct sackblk));
317                 num++;
318         }
319         if (num == 0)
320                 return(num);
321
322         /* Now what we are left with is either
323          * completely merged on to the board
324          * from the above steps, or is new
325          * and need to be added to the board
326          * with the last one updated to current.
327          *
328          * First copy it out, we want to return that
329          * to our caller for processing.
330          */
331         memcpy(in, blkboard, (num * sizeof(struct sackblk)));
332         numblks = num;
333         /* Now go through and add to our board as needed */
334         for(i=(num-1); i>=0; i--) {
335                 if (is_sack_on_board(sf, &blkboard[i])) {
336                         continue;
337                 }
338                 /* Add this guy its not listed */
339                 sf->sf_cur++;
340                 sf->sf_cur %= SACK_FILTER_BLOCKS;
341                 if ((sack_blk_used(sf, sf->sf_cur)) &&
342                     (sf->sf_used < SACK_FILTER_BLOCKS)) {
343                         sack_move_to_empty(sf, sf->sf_cur);
344                 }
345 #ifndef _KERNEL
346                 if (sack_blk_used(sf, sf->sf_cur)) {
347                         over_written++;
348                         if (sf->sf_used < SACK_FILTER_BLOCKS)
349                                 empty_avail++;
350                 }
351 #endif
352                 memcpy(&sf->sf_blks[sf->sf_cur], &in[i], sizeof(struct sackblk));
353                 if (sack_blk_used(sf, sf->sf_cur) == 0) {
354                         sf->sf_used++;
355 #ifndef _KERNEL
356                         if (sf->sf_used > highest_used)
357                                 highest_used = sf->sf_used;
358 #endif
359                         sf->sf_bits = sack_blk_set(sf, sf->sf_cur);
360                 }
361         }
362         return(numblks);
363 }
364
365 /*
366  * Given a sack block on the board (the skip index) see if
367  * any other used entries overlap or meet, if so return the index.
368  */
369 static int32_t
370 sack_blocks_overlap_or_meet(struct sack_filter *sf, struct sackblk *sb, uint32_t skip)
371 {
372         int32_t i;
373
374         for(i=0; i<SACK_FILTER_BLOCKS; i++) {
375                 if (sack_blk_used(sf, i) == 0)
376                         continue;
377                 if (i == skip)
378                         continue;
379                 if (SEQ_GEQ(sf->sf_blks[i].end, sb->start) &&
380                     SEQ_LEQ(sf->sf_blks[i].end, sb->end) &&
381                     SEQ_LEQ(sf->sf_blks[i].start, sb->start)) {
382                         /**
383                          * The two board blocks meet:
384                          *
385                          *  board1   |--------|
386                          *  board2       |----------|
387                          *    <or>
388                          *  board1   |--------|
389                          *  board2   |--------------|
390                          *    <or>
391                          *  board1   |--------|
392                          *  board2   |--------|
393                          */
394                         return(i);
395                 }
396                 if (SEQ_LEQ(sf->sf_blks[i].start, sb->end) &&
397                     SEQ_GEQ(sf->sf_blks[i].start, sb->start) &&
398                     SEQ_GEQ(sf->sf_blks[i].end, sb->end)) {
399                         /**
400                          * The board block partial meets:
401                          *
402                          *  board       |--------|
403                          *  sack  |----------|
404                          *     <or>
405                          *  board       |----|
406                          *  sack  |----------|
407                          * 1) Update the board block to the new start
408                          *      and
409                          * 2) Update the start of this block to my end.
410                          */
411                         return(i);
412                 }
413         }
414         return (-1);
415 }
416
417 /*
418  * Collapse entry src into entry into
419  * and free up the src entry afterwards.
420  */
421 static void
422 sack_collapse(struct sack_filter *sf, int32_t src, int32_t into)
423 {
424         if (SEQ_LT(sf->sf_blks[src].start, sf->sf_blks[into].start)) {
425                 /* src has a lower starting point */
426                 sf->sf_blks[into].start = sf->sf_blks[src].start;
427         }
428         if (SEQ_GT(sf->sf_blks[src].end, sf->sf_blks[into].end)) {
429                 /* src has a higher ending point */
430                 sf->sf_blks[into].end = sf->sf_blks[src].end;
431         }
432         sf->sf_bits = sack_blk_clr(sf, src);
433         sf->sf_used--;
434 }
435
436 static void
437 sack_board_collapse(struct sack_filter *sf)
438 {
439         int32_t i, j, i_d, j_d;
440
441         for(i=0; i<SACK_FILTER_BLOCKS; i++) {
442                 if (sack_blk_used(sf, i) == 0)
443                         continue;
444                 /*
445                  * Look at all other blocks but this guy
446                  * to see if they overlap. If so we collapse
447                  * the two blocks together.
448                  */
449                 j = sack_blocks_overlap_or_meet(sf, &sf->sf_blks[i], i);
450                 if (j == -1) {
451                         /* No overlap */
452                         continue;
453                 }
454                 /*
455                  * Ok j and i overlap with each other, collapse the
456                  * one out furthest away from the current position.
457                  */
458                 if (sf->sf_cur > i)
459                         i_d = sf->sf_cur - i;
460                 else
461                         i_d = i - sf->sf_cur;
462                 if (sf->sf_cur > j)
463                         j_d = sf->sf_cur - j;
464                 else
465                         j_d = j - sf->sf_cur;
466                 if (j_d > i_d) {
467                         sack_collapse(sf, j, i);
468                 } else
469                         sack_collapse(sf, i, j);
470         }
471 }
472
473 #ifndef _KERNEL
474 uint64_t saved=0;
475 uint64_t tot_sack_blks=0;
476
477 static void
478 sack_filter_dump(FILE *out, struct sack_filter *sf)
479 {
480         int i;
481         fprintf(out, "  sf_ack:%u sf_bits:0x%x c:%d used:%d\n",
482                 sf->sf_ack, sf->sf_bits,
483                 sf->sf_cur, sf->sf_used);
484
485         for(i=0; i<SACK_FILTER_BLOCKS; i++) {
486                 if (sack_blk_used(sf, i)) {
487                         fprintf(out, "Entry:%d start:%u end:%u\n", i,
488                                sf->sf_blks[i].start,
489                                sf->sf_blks[i].end);
490                 }
491         }
492 }
493 #endif
494
495 #ifndef _KERNEL
496 static
497 #endif
498 int
499 sack_filter_blks(struct sack_filter *sf, struct sackblk *in, int numblks,
500                  tcp_seq th_ack)
501 {
502         int32_t i, ret;
503
504         if (numblks > TCP_MAX_SACK) {
505 #ifdef _KERNEL
506                 panic("sf:%p sb:%p Impossible number of sack blocks %d > 4\n",
507                       sf, in,
508                       numblks);
509 #endif
510                 return(numblks);
511         }
512 #ifndef _KERNEL
513         if ((sf->sf_used > 1) && (no_collapse == 0))
514                 sack_board_collapse(sf);
515
516 #else
517         if (sf->sf_used > 1)
518                 sack_board_collapse(sf);
519 #endif
520         if ((sf->sf_used == 0) && numblks) {
521                 /*
522                  * We are brand new add the blocks in
523                  * reverse order. Note we can see more
524                  * than one in new, since ack's could be lost.
525                  */
526                 int cnt_added = 0;
527
528                 sf->sf_ack = th_ack;
529                 for(i=(numblks-1), sf->sf_cur=0; i >= 0; i--) {
530                         memcpy(&sf->sf_blks[sf->sf_cur], &in[i], sizeof(struct sackblk));
531                         sf->sf_bits = sack_blk_set(sf, sf->sf_cur);
532                         sf->sf_cur++;
533                         sf->sf_cur %= SACK_FILTER_BLOCKS;
534                         sf->sf_used++;
535                         cnt_added++;
536 #ifndef _KERNEL
537                         if (sf->sf_used > highest_used)
538                                 highest_used = sf->sf_used;
539 #endif
540                 }
541                 if (sf->sf_cur)
542                         sf->sf_cur--;
543
544                 return (cnt_added);
545         }
546         if (SEQ_GT(th_ack, sf->sf_ack)) {
547                 sack_filter_prune(sf, th_ack);
548         }
549         if (numblks) {
550                 if (SEQ_GEQ(th_ack, sf->sf_ack)) {
551                         ret = sack_filter_new(sf, in, numblks, th_ack);
552                 } else {
553                         ret = sack_filter_old(sf, in, numblks);
554                 }
555         } else
556                 ret = 0;
557         return (ret);
558 }
559
560 void
561 sack_filter_reject(struct sack_filter *sf, struct sackblk *in)
562 {
563         /*
564          * Given a specified block (that had made
565          * it past the sack filter). Reject that
566          * block triming it off any sack-filter block
567          * that has it. Usually because the block was
568          * too small and did not cover a whole send.
569          *
570          * This function will only "undo" sack-blocks
571          * that are fresh and touch the edges of
572          * blocks in our filter.
573          */
574         int i;
575
576         for(i=0; i<SACK_FILTER_BLOCKS; i++) {
577                 if (sack_blk_used(sf, i) == 0)
578                         continue;
579                 /*
580                  * Now given the sack-filter block does it touch
581                  * with one of the ends
582                  */
583                 if (sf->sf_blks[i].end == in->end) {
584                         /* The end moves back to start */
585                         if (SEQ_GT(in->start, sf->sf_blks[i].start))
586                                 /* in-blk       |----| */
587                                 /* sf-blk  |---------| */
588                                 sf->sf_blks[i].end = in->start;
589                         else {
590                                 /* It consumes this block */
591                                 /* in-blk  |---------| */
592                                 /* sf-blk     |------| */
593                                 /* <or> */
594                                 /* sf-blk  |---------| */
595                                 sf->sf_bits = sack_blk_clr(sf, i);
596                                 sf->sf_used--;
597                         }
598                         continue;
599                 }
600                 if (sf->sf_blks[i].start == in->start) {
601                         if (SEQ_LT(in->end, sf->sf_blks[i].end)) {
602                                 /* in-blk  |----|      */
603                                 /* sf-blk  |---------| */
604                                 sf->sf_blks[i].start = in->end;
605                         } else {
606                                 /* It consumes this block */
607                                 /* in-blk  |----------|  */
608                                 /* sf-blk  |-------|     */
609                                 /* <or> */
610                                 /* sf-blk  |----------|  */
611                                 sf->sf_bits = sack_blk_clr(sf, i);
612                                 sf->sf_used--;
613                         }
614                         continue;
615                 }
616         }
617 }
618
619 #ifndef _KERNEL
620
621 int
622 main(int argc, char **argv)
623 {
624         char buffer[512];
625         struct sackblk blks[TCP_MAX_SACK];
626         FILE *err;
627         tcp_seq th_ack, snd_una, snd_max = 0;
628         struct sack_filter sf;
629         int32_t numblks,i;
630         int snd_una_set=0;
631         double a, b, c;
632         int invalid_sack_print = 0;
633         uint32_t chg_remembered=0;
634         uint32_t sack_chg=0;
635         char line_buf[10][256];
636         int line_buf_at=0;
637
638         in = stdin;
639         out = stdout;
640         while ((i = getopt(argc, argv, "ndIi:o:?h")) != -1) {
641                 switch (i) {
642                 case 'n':
643                         no_collapse = 1;
644                         break;
645                 case 'd':
646                         detailed_dump = 1;
647                         break;
648                 case'I':
649                         invalid_sack_print = 1;
650                         break;
651                 case 'i':
652                         in = fopen(optarg, "r");
653                         if (in == NULL) {
654                                 fprintf(stderr, "Fatal error can't open %s for input\n", optarg);
655                                 exit(-1);
656                         }
657                         break;
658                 case 'o':
659                         out = fopen(optarg, "w");
660                         if (out == NULL) {
661                                 fprintf(stderr, "Fatal error can't open %s for output\n", optarg);
662                                 exit(-1);
663                         }
664                         break;
665                 default:
666                 case '?':
667                 case 'h':
668                         fprintf(stderr, "Use %s [ -i infile -o outfile -I]\n", argv[0]);
669                         return(0);
670                         break;
671                 };
672         }
673         sack_filter_clear(&sf, 0);
674         memset(buffer, 0, sizeof(buffer));
675         memset(blks, 0, sizeof(blks));
676         numblks = 0;
677         fprintf(out, "************************************\n");
678         while (fgets(buffer, sizeof(buffer), in) != NULL) {
679                 sprintf(line_buf[line_buf_at], "%s", buffer);
680                 line_buf_at++;
681                 if (strncmp(buffer, "QUIT", 4) == 0) {
682                         break;
683                 } else if (strncmp(buffer, "DUMP", 4) == 0) {
684                         sack_filter_dump(out, &sf);
685                 } else if (strncmp(buffer, "MAX:", 4) == 0) {
686                         snd_max = strtoul(&buffer[4], NULL, 0);
687                 } else if (strncmp(buffer, "COMMIT", 6) == 0) {
688                         int nn, ii;
689                         if (numblks) {
690                                 uint32_t szof, tot_chg;
691                                 for(ii=0; ii<line_buf_at; ii++) {
692                                         fprintf(out, "%s", line_buf[ii]);
693                                 }
694                                 fprintf(out, "------------------------------------\n");
695                                 nn = sack_filter_blks(&sf, blks, numblks, th_ack);
696                                 saved += numblks - nn;
697                                 tot_sack_blks += numblks;
698                                 fprintf(out, "ACK:%u\n", sf.sf_ack);
699                                 for(ii=0, tot_chg=0; ii<nn; ii++) {
700                                         szof = blks[ii].end - blks[ii].start;
701                                         tot_chg += szof;
702                                         fprintf(out, "SACK:%u:%u [%u]\n",
703                                                blks[ii].start,
704                                                 blks[ii].end, szof);
705                                 }
706                                 fprintf(out,"************************************\n");
707                                 chg_remembered = tot_chg;
708                                 if (detailed_dump) {
709                                         sack_filter_dump(out, &sf);
710                                         fprintf(out,"************************************\n");
711                                 }
712                         }
713                         memset(blks, 0, sizeof(blks));
714                         memset(line_buf, 0, sizeof(line_buf));
715                         line_buf_at=0;
716                         numblks = 0;
717                 } else if (strncmp(buffer, "CHG:", 4) == 0) {
718                         sack_chg = strtoul(&buffer[4], NULL, 0);
719                         if ((sack_chg != chg_remembered) &&
720                             (sack_chg > chg_remembered)){
721                                 fprintf(out,"***WARNING WILL RODGERS DANGER!! sack_chg:%u last:%u\n",
722                                         sack_chg, chg_remembered
723                                         );
724                         }
725                         sack_chg = chg_remembered = 0;
726                 } else if (strncmp(buffer, "RXT", 3) == 0) {
727                         sack_filter_clear(&sf, snd_una);
728                 } else if (strncmp(buffer, "ACK:", 4) == 0) {
729                         th_ack = strtoul(&buffer[4], NULL, 0);
730                         if (snd_una_set == 0) {
731                                 snd_una = th_ack;
732                                 snd_una_set = 1;
733                         } else if (SEQ_GT(th_ack, snd_una)) {
734                                 snd_una = th_ack;
735                         }
736                 } else if (strncmp(buffer, "EXIT", 4) == 0) {
737                         sack_filter_clear(&sf, snd_una);
738                         sack_chg = chg_remembered = 0;
739                 } else if (strncmp(buffer, "SACK:", 5) == 0) {
740                         char *end=NULL;
741                         uint32_t start;
742                         uint32_t endv;
743
744                         start = strtoul(&buffer[5], &end, 0);
745                         if (end) {
746                                 endv = strtoul(&end[1], NULL, 0);
747                         } else {
748                                 fprintf(out, "--Sack invalid skip 0 start:%u : ??\n", start);
749                                 continue;
750                         }
751                         if (SEQ_GT(endv, snd_max))
752                                 snd_max = endv;
753                         if (SEQ_LT(endv, start)) {
754                                 fprintf(out, "--Sack invalid skip 1 endv:%u < start:%u\n", endv, start);
755                                 continue;
756                         }
757                         if (numblks == TCP_MAX_SACK) {
758                                 fprintf(out, "--Exceeded max %d\n", numblks);
759                                 exit(0);
760                         }
761                         blks[numblks].start = start;
762                         blks[numblks].end = endv;
763                         numblks++;
764                 } else if (strncmp(buffer, "REJ:n:n", 4) == 0) {
765                         struct sackblk in;
766                         char *end=NULL;
767
768                         in.start = strtoul(&buffer[4], &end, 0);
769                         if (end) {
770                                 in.end = strtoul(&end[1], NULL, 0);
771                                 sack_filter_reject(&sf, &in);
772                         } else
773                                 fprintf(out, "Invalid input END:A:B\n");
774                 } else if (strncmp(buffer, "HELP", 4) == 0) {
775                         fprintf(out, "You can input:\n");
776                         fprintf(out, "SACK:S:E -- to define a sack block\n");
777                         fprintf(out, "RXT -- to clear the filter without changing the remembered\n");
778                         fprintf(out, "EXIT -- To clear the sack filter and start all fresh\n");
779                         fprintf(out, "ACK:N -- To advance the cum-ack to N\n");
780                         fprintf(out, "MAX:N -- To set send-max to N\n");
781                         fprintf(out, "COMMIT -- To apply the sack you built to the filter and dump the filter\n");
782                         fprintf(out, "DUMP -- To display the current contents of the sack filter\n");
783                         fprintf(out, "QUIT -- To exit this program\n");
784                 } else {
785                         fprintf(out, "Command %s unknown\n", buffer);
786                 }
787                 memset(buffer, 0, sizeof(buffer));
788         }
789         if (in != stdin) {
790                 fclose(in);
791         }
792         if (out != stdout) {
793                 fclose(out);
794         }
795         a = saved * 100.0;
796         b = tot_sack_blks * 1.0;
797         if (b > 0.0)
798                 c = a/b;
799         else
800                 c = 0.0;
801         if (out != stdout)
802                 err = stdout;
803         else
804                 err = stderr;
805         fprintf(err, "Saved %lu sack blocks out of %lu (%2.3f%%) old_skip:%lu old_usd:%lu high_cnt:%d ow:%d ea:%d\n",
806                 saved, tot_sack_blks, c, cnt_skipped_oldsack, cnt_used_oldsack, highest_used, over_written, empty_avail);
807         return(0);
808 }
809 #endif