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