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
810