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