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
sack_filter_clear(struct sack_filter * sf,tcp_seq seq)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
sack_filter_prune(struct sack_filter * sf,tcp_seq th_ack)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
is_sack_on_board(struct sack_filter * sf,struct sackblk * b)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
sack_filter_old(struct sack_filter * sf,struct sackblk * in,int numblks)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
sack_move_to_empty(struct sack_filter * sf,uint32_t idx)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
sack_filter_new(struct sack_filter * sf,struct sackblk * in,int numblks,tcp_seq th_ack)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
sack_blocks_overlap_or_meet(struct sack_filter * sf,struct sackblk * sb,uint32_t skip)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
sack_collapse(struct sack_filter * sf,int32_t src,int32_t into)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
sack_board_collapse(struct sack_filter * sf)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
sack_filter_dump(FILE * out,struct sack_filter * sf)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
sack_filter_blks(struct sack_filter * sf,struct sackblk * in,int numblks,tcp_seq th_ack)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
sack_filter_reject(struct sack_filter * sf,struct sackblk * in)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
main(int argc,char ** argv)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