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