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