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