#include <sys/cdefs.h>
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#ifdef _KERNEL
#include <sys/mbuf.h>
#include <sys/sockopt.h>
#include <netinet/in_pcb.h>
#include <netinet/tcp_var.h>
#include <netinet/tcp_seq.h>
#else
#include <netinet/tcp_seq.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <limits.h>
#include <getopt.h>
struct sackblk {
tcp_seq start;
tcp_seq end;
};
struct tcpcb {
tcp_seq snd_una;
tcp_seq snd_max;
uint32_t t_maxseg;
};
#endif
#include "sack_filter.h"
#ifndef _KERNEL
int detailed_dump = 0;
uint64_t cnt_skipped_oldsack = 0;
uint64_t cnt_used_oldsack = 0;
int highest_used=0;
int over_written=0;
int empty_avail=0;
FILE *out = NULL;
FILE *in = NULL;
#endif
#define sack_blk_used(sf, i) ((1 << i) & sf->sf_bits)
#define sack_blk_set(sf, i) ((1 << i) | sf->sf_bits)
#define sack_blk_clr(sf, i) (~(1 << i) & sf->sf_bits)
#ifndef _KERNEL
static u_int tcp_fixed_maxseg(const struct tcpcb *tp)
{
return (tp->t_maxseg - 12);
}
static
#endif
void
sack_filter_clear(struct sack_filter *sf, tcp_seq seq)
{
sf->sf_ack = seq;
sf->sf_bits = 0;
sf->sf_cur = 0;
sf->sf_used = 0;
}
static void
sack_filter_prune(struct sack_filter *sf, tcp_seq th_ack)
{
int32_t i;
for (i = 0; i < SACK_FILTER_BLOCKS; i++) {
if (sack_blk_used(sf, i)) {
if (SEQ_GEQ(th_ack, sf->sf_blks[i].end)) {
sf->sf_bits = sack_blk_clr(sf, i);
sf->sf_used--;
} else if (SEQ_GT(th_ack, sf->sf_blks[i].start)) {
sf->sf_blks[i].start = th_ack;
}
}
}
sf->sf_ack = th_ack;
}
static int32_t
is_sack_on_board(struct sack_filter *sf, struct sackblk *b, int32_t segmax, uint32_t snd_max)
{
int32_t i, cnt;
int span_cnt = 0;
uint32_t span_start, span_end;
if (SEQ_LT(b->start, sf->sf_ack)) {
b->start = sf->sf_ack;
}
if (SEQ_LT(b->end, sf->sf_ack)) {
b->end = sf->sf_ack;
}
if (b->start == b->end) {
return(1);
}
span_start = b->start;
span_end = b->end;
for (i = sf->sf_cur, cnt=0; cnt < SACK_FILTER_BLOCKS; cnt++) {
if (sack_blk_used(sf, i)) {
if (SEQ_LEQ(sf->sf_blks[i].start, b->start) &&
SEQ_GEQ(sf->sf_blks[i].end, b->end)) {
return(1);
}
if(SEQ_LT(sf->sf_blks[i].end, b->start)) {
if ((b->end != snd_max) &&
(span_cnt < 2) &&
((b->end - b->start) < segmax)) {
return (1);
}
goto nxt_blk;
}
if (SEQ_GT(sf->sf_blks[i].start, b->end)) {
if ((b->end != snd_max) &&
(sf->sf_blks[i].end != snd_max) &&
(span_cnt < 2) &&
((b->end - b->start) < segmax)) {
return (1);
}
goto nxt_blk;
}
if (SEQ_LEQ(sf->sf_blks[i].start, b->start)) {
if (((b->end != snd_max) || (sf->sf_blks[i].end == snd_max)) &&
(span_cnt == 0) &&
((b->end - sf->sf_blks[i].end) < segmax)) {
return (1);
}
b->start = sf->sf_blks[i].end;
sf->sf_blks[i].end = b->end;
if (span_cnt == 0) {
span_start = sf->sf_blks[i].start;
span_end = sf->sf_blks[i].end;
} else {
if (SEQ_LT(span_start, sf->sf_blks[i].start)) {
span_start = sf->sf_blks[i].start;
}
if (SEQ_GT(span_end, sf->sf_blks[i].end)) {
span_end = sf->sf_blks[i].end;
}
}
span_cnt++;
goto nxt_blk;
}
if (SEQ_GEQ(sf->sf_blks[i].end, b->end)) {
if (((b->end != snd_max) || (sf->sf_blks[i].end == snd_max)) &&
(span_cnt == 0) &&
((sf->sf_blks[i].start - b->start) < segmax)) {
return (1);
}
b->end = sf->sf_blks[i].start;
sf->sf_blks[i].start = b->start;
if (span_cnt == 0) {
span_start = sf->sf_blks[i].start;
span_end = sf->sf_blks[i].end;
} else {
if (SEQ_LT(span_start, sf->sf_blks[i].start)) {
span_start = sf->sf_blks[i].start;
}
if (SEQ_GT(span_end, sf->sf_blks[i].end)) {
span_end = sf->sf_blks[i].end;
}
}
span_cnt++;
goto nxt_blk;
}
}
nxt_blk:
i++;
i %= SACK_FILTER_BLOCKS;
}
if (b->start != b->end) {
if ((b->end != snd_max) &&
((b->end - b->start) < segmax) &&
((span_end - span_start) < segmax)) {
return (1);
}
return(0);
} else {
return(1);
}
}
static void
sack_move_to_empty(struct sack_filter *sf, uint32_t idx)
{
int32_t i, cnt;
i = (idx + 1) % SACK_FILTER_BLOCKS;
for (cnt=0; cnt <(SACK_FILTER_BLOCKS-1); cnt++) {
if (sack_blk_used(sf, i) == 0) {
memcpy(&sf->sf_blks[i], &sf->sf_blks[idx], sizeof(struct sackblk));
sf->sf_bits = sack_blk_clr(sf, idx);
sf->sf_bits = sack_blk_set(sf, i);
return;
}
i++;
i %= SACK_FILTER_BLOCKS;
}
}
static int32_t
sack_filter_run(struct sack_filter *sf, struct sackblk *in, int numblks, tcp_seq th_ack, int32_t segmax, uint32_t snd_max)
{
struct sackblk blkboard[TCP_MAX_SACK];
int32_t num, i, room, at;
for(i=0, num=0; i<numblks; i++) {
if (is_sack_on_board(sf, &in[i], segmax, snd_max))
continue;
memcpy(&blkboard[num], &in[i], sizeof(struct sackblk));
num++;
}
if (num == 0) {
return(num);
}
room = SACK_FILTER_BLOCKS - sf->sf_used;
if (room < 1)
return (0);
for(i=0, at=0; i<num; i++) {
if (room == 0) {
break;
}
memcpy(&in[at], &blkboard[i], sizeof(struct sackblk));
at++;
room--;
sf->sf_cur++;
sf->sf_cur %= SACK_FILTER_BLOCKS;
if ((sack_blk_used(sf, sf->sf_cur)) &&
(sf->sf_used < SACK_FILTER_BLOCKS)) {
sack_move_to_empty(sf, sf->sf_cur);
}
memcpy(&sf->sf_blks[sf->sf_cur], &blkboard[i], sizeof(struct sackblk));
if (sack_blk_used(sf, sf->sf_cur) == 0) {
sf->sf_used++;
#ifndef _KERNEL
if (sf->sf_used > highest_used)
highest_used = sf->sf_used;
#endif
sf->sf_bits = sack_blk_set(sf, sf->sf_cur);
}
}
return(at);
}
static void
sack_collapse(struct sack_filter *sf, int32_t src, int32_t into)
{
if (SEQ_LT(sf->sf_blks[src].start, sf->sf_blks[into].start)) {
sf->sf_blks[into].start = sf->sf_blks[src].start;
}
if (SEQ_GT(sf->sf_blks[src].end, sf->sf_blks[into].end)) {
sf->sf_blks[into].end = sf->sf_blks[src].end;
}
sf->sf_bits = sack_blk_clr(sf, src);
sf->sf_used--;
}
static int32_t
sack_blocks_overlap_or_meet(struct sack_filter *sf, struct sackblk *sb, uint32_t skip)
{
int32_t i;
for(i=0; i<SACK_FILTER_BLOCKS; i++) {
if (sack_blk_used(sf, i) == 0)
continue;
if (i == skip)
continue;
if (SEQ_GEQ(sf->sf_blks[i].end, sb->start) &&
SEQ_LEQ(sf->sf_blks[i].end, sb->end) &&
SEQ_LEQ(sf->sf_blks[i].start, sb->start)) {
return(i);
}
if (SEQ_LEQ(sf->sf_blks[i].start, sb->end) &&
SEQ_GEQ(sf->sf_blks[i].start, sb->start) &&
SEQ_GEQ(sf->sf_blks[i].end, sb->end)) {
return(i);
}
}
return (-1);
}
static void
sack_board_collapse(struct sack_filter *sf)
{
int32_t i, j, i_d, j_d;
for(i=0; i<SACK_FILTER_BLOCKS; i++) {
if (sack_blk_used(sf, i) == 0)
continue;
j = sack_blocks_overlap_or_meet(sf, &sf->sf_blks[i], i);
if (j == -1) {
continue;
}
if (sf->sf_cur > i)
i_d = sf->sf_cur - i;
else
i_d = i - sf->sf_cur;
if (sf->sf_cur > j)
j_d = sf->sf_cur - j;
else
j_d = j - sf->sf_cur;
if (j_d > i_d) {
sack_collapse(sf, j, i);
} else
sack_collapse(sf, i, j);
}
}
#ifndef _KERNEL
uint64_t saved=0;
uint64_t tot_sack_blks=0;
static void
sack_filter_dump(FILE *out, struct sack_filter *sf)
{
int i;
fprintf(out, " sf_ack:%u sf_bits:0x%x c:%d used:%d\n",
sf->sf_ack, sf->sf_bits,
sf->sf_cur, sf->sf_used);
for(i=0; i<SACK_FILTER_BLOCKS; i++) {
if (sack_blk_used(sf, i)) {
fprintf(out, "Entry:%d start:%u end:%u the block is %s\n",
i,
sf->sf_blks[i].start,
sf->sf_blks[i].end,
(sack_blk_used(sf, i) ? "USED" : "NOT-USED")
);
}
}
}
#endif
#ifndef _KERNEL
static
#endif
int
sack_filter_blks(struct tcpcb *tp, struct sack_filter *sf, struct sackblk *in, int numblks,
tcp_seq th_ack)
{
int32_t i, ret;
int32_t segmax;
if (numblks > TCP_MAX_SACK) {
#ifdef _KERNEL
panic("sf:%p sb:%p Impossible number of sack blocks %d > 4\n",
sf, in,
numblks);
#endif
return(numblks);
}
if (sf->sf_used > 1)
sack_board_collapse(sf);
segmax = tcp_fixed_maxseg(tp);
if ((sf->sf_used == 0) && numblks) {
int cnt_added = 0;
sf->sf_ack = th_ack;
for(i=0, sf->sf_cur=0; i<numblks; i++) {
if ((in[i].end != tp->snd_max) &&
((in[i].end - in[i].start) < segmax)) {
continue;
}
memcpy(&sf->sf_blks[sf->sf_cur], &in[i], sizeof(struct sackblk));
sf->sf_bits = sack_blk_set(sf, sf->sf_cur);
sf->sf_cur++;
sf->sf_cur %= SACK_FILTER_BLOCKS;
sf->sf_used++;
cnt_added++;
#ifndef _KERNEL
if (sf->sf_used > highest_used)
highest_used = sf->sf_used;
#endif
}
if (sf->sf_cur)
sf->sf_cur--;
return (cnt_added);
}
if (SEQ_GT(th_ack, sf->sf_ack)) {
sack_filter_prune(sf, th_ack);
}
if (numblks) {
ret = sack_filter_run(sf, in, numblks, th_ack, segmax, tp->snd_max);
if (sf->sf_used > 1)
sack_board_collapse(sf);
} else
ret = 0;
return (ret);
}
void
sack_filter_reject(struct sack_filter *sf, struct sackblk *in)
{
int i;
for(i=0; i<SACK_FILTER_BLOCKS; i++) {
if (sack_blk_used(sf, i) == 0)
continue;
if (sf->sf_blks[i].end == in->end) {
if (SEQ_GT(in->start, sf->sf_blks[i].start))
sf->sf_blks[i].end = in->start;
else {
sf->sf_bits = sack_blk_clr(sf, i);
sf->sf_used--;
}
continue;
}
if (sf->sf_blks[i].start == in->start) {
if (SEQ_LT(in->end, sf->sf_blks[i].end)) {
sf->sf_blks[i].start = in->end;
} else {
sf->sf_bits = sack_blk_clr(sf, i);
sf->sf_used--;
}
continue;
}
}
}
#ifndef _KERNEL
int
main(int argc, char **argv)
{
char buffer[512];
struct sackblk blks[TCP_MAX_SACK];
FILE *err;
tcp_seq th_ack;
struct tcpcb tp;
struct sack_filter sf;
int32_t numblks,i;
int snd_una_set=0;
double a, b, c;
int invalid_sack_print = 0;
uint32_t chg_remembered=0;
uint32_t sack_chg=0;
char line_buf[10][256];
int line_buf_at=0;
in = stdin;
out = stdout;
memset(&tp, 0, sizeof(tp));
tp.t_maxseg = 1460;
while ((i = getopt(argc, argv, "dIi:o:?hS:")) != -1) {
switch (i) {
case 'S':
tp.t_maxseg = strtol(optarg, NULL, 0);
break;
case 'd':
detailed_dump = 1;
break;
case'I':
invalid_sack_print = 1;
break;
case 'i':
in = fopen(optarg, "r");
if (in == NULL) {
fprintf(stderr, "Fatal error can't open %s for input\n", optarg);
exit(-1);
}
break;
case 'o':
out = fopen(optarg, "w");
if (out == NULL) {
fprintf(stderr, "Fatal error can't open %s for output\n", optarg);
exit(-1);
}
break;
default:
case '?':
case 'h':
fprintf(stderr, "Use %s [ -i infile -o outfile -I -S maxseg -n -d ]\n", argv[0]);
return(0);
break;
};
}
sack_filter_clear(&sf, 0);
memset(buffer, 0, sizeof(buffer));
memset(blks, 0, sizeof(blks));
numblks = 0;
fprintf(out, "************************************\n");
while (fgets(buffer, sizeof(buffer), in) != NULL) {
sprintf(line_buf[line_buf_at], "%s", buffer);
line_buf_at++;
if (strncmp(buffer, "quit", 4) == 0) {
break;
} else if (strncmp(buffer, "dump", 4) == 0) {
sack_filter_dump(out, &sf);
} else if (strncmp(buffer, "max:", 4) == 0) {
tp.snd_max = strtoul(&buffer[4], NULL, 0);
} else if (strncmp(buffer, "commit", 6) == 0) {
int nn, ii;
if (numblks) {
uint32_t szof, tot_chg;
printf("Dumping line buffer (lines:%d)\n", line_buf_at);
for(ii=0; ii<line_buf_at; ii++) {
fprintf(out, "%s", line_buf[ii]);
}
fprintf(out, "------------------------------------ call sfb() nb:%d\n", numblks);
nn = sack_filter_blks(&tp, &sf, blks, numblks, th_ack);
saved += numblks - nn;
tot_sack_blks += numblks;
for(ii=0, tot_chg=0; ii<nn; ii++) {
szof = blks[ii].end - blks[ii].start;
tot_chg += szof;
fprintf(out, "sack:%u:%u [%u]\n",
blks[ii].start,
blks[ii].end, szof);
}
fprintf(out,"************************************\n");
chg_remembered = tot_chg;
if (detailed_dump) {
sack_filter_dump(out, &sf);
fprintf(out,"************************************\n");
}
}
memset(blks, 0, sizeof(blks));
memset(line_buf, 0, sizeof(line_buf));
line_buf_at=0;
numblks = 0;
} else if (strncmp(buffer, "chg:", 4) == 0) {
sack_chg = strtoul(&buffer[4], NULL, 0);
if ((sack_chg != chg_remembered) &&
(sack_chg > chg_remembered)){
fprintf(out,"***WARNING WILL RODGERS DANGER!! sack_chg:%u last:%u\n",
sack_chg, chg_remembered
);
}
sack_chg = chg_remembered = 0;
} else if (strncmp(buffer, "rxt", 3) == 0) {
sack_filter_clear(&sf, tp.snd_una);
} else if (strncmp(buffer, "ack:", 4) == 0) {
th_ack = strtoul(&buffer[4], NULL, 0);
if (snd_una_set == 0) {
tp.snd_una = th_ack;
snd_una_set = 1;
} else if (SEQ_GT(th_ack, tp.snd_una)) {
tp.snd_una = th_ack;
}
sack_filter_blks(&tp, &sf, NULL, 0, th_ack);
} else if (strncmp(buffer, "exit", 4) == 0) {
sack_filter_clear(&sf, tp.snd_una);
sack_chg = chg_remembered = 0;
} else if (strncmp(buffer, "sack:", 5) == 0) {
char *end=NULL;
uint32_t start;
uint32_t endv;
start = strtoul(&buffer[5], &end, 0);
if (end) {
endv = strtoul(&end[1], NULL, 0);
} else {
fprintf(out, "--Sack invalid skip 0 start:%u : ??\n", start);
continue;
}
if (SEQ_GT(endv, tp.snd_max))
tp.snd_max = endv;
if (SEQ_LT(endv, start)) {
fprintf(out, "--Sack invalid skip 1 endv:%u < start:%u\n", endv, start);
continue;
}
if (numblks == TCP_MAX_SACK) {
fprintf(out, "--Exceeded max %d\n", numblks);
exit(0);
}
blks[numblks].start = start;
blks[numblks].end = endv;
numblks++;
} else if (strncmp(buffer, "rej:n:n", 4) == 0) {
struct sackblk in;
char *end=NULL;
in.start = strtoul(&buffer[4], &end, 0);
if (end) {
in.end = strtoul(&end[1], NULL, 0);
sack_filter_reject(&sf, &in);
} else
fprintf(out, "Invalid input END:A:B\n");
} else if (strncmp(buffer, "save", 4) == 0) {
FILE *io;
io = fopen("sack_setup.bin", "w+");
if (io != NULL) {
if (fwrite(&sf, sizeof(sf), 1, io) != 1) {
printf("Failed to write out sf data\n");
unlink("sack_setup.bin");
goto outwrite;
}
if (fwrite(&tp, sizeof(tp), 1, io) != 1) {
printf("Failed to write out tp data\n");
unlink("sack_setup.bin");
} else
printf("Save completed\n");
outwrite:
fclose(io);
} else {
printf("failed to open sack_setup.bin for writting .. sorry\n");
}
} else if (strncmp(buffer, "restore", 7) == 0) {
FILE *io;
io = fopen("sack_setup.bin", "r");
if (io != NULL) {
if (fread(&sf, sizeof(sf), 1, io) != 1) {
printf("Failed to read out sf data\n");
goto outread;
}
if (fread(&tp, sizeof(tp), 1, io) != 1) {
printf("Failed to read out tp data\n");
} else {
printf("Restore completed\n");
sack_filter_dump(out, &sf);
}
outread:
fclose(io);
} else {
printf("can't open sack_setup.bin -- sorry no load\n");
}
} else if (strncmp(buffer, "help", 4) == 0) {
help:
fprintf(out, "You can input:\n");
fprintf(out, "sack:S:E -- to define a sack block\n");
fprintf(out, "rxt -- to clear the filter without changing the remembered\n");
fprintf(out, "save -- save current state to sack_setup.bin\n");
fprintf(out, "restore -- restore state from sack_setup.bin\n");
fprintf(out, "exit -- To clear the sack filter and start all fresh\n");
fprintf(out, "ack:N -- To advance the cum-ack to N\n");
fprintf(out, "max:N -- To set send-max to N\n");
fprintf(out, "commit -- To apply the sack you built to the filter and dump the filter\n");
fprintf(out, "dump -- To display the current contents of the sack filter\n");
fprintf(out, "quit -- To exit this program\n");
} else {
fprintf(out, "Command %s unknown\n", buffer);
goto help;
}
memset(buffer, 0, sizeof(buffer));
}
if (in != stdin) {
fclose(in);
}
if (out != stdout) {
fclose(out);
}
a = saved * 100.0;
b = tot_sack_blks * 1.0;
if (b > 0.0)
c = a/b;
else
c = 0.0;
if (out != stdout)
err = stdout;
else
err = stderr;
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",
saved, tot_sack_blks, c, cnt_skipped_oldsack, cnt_used_oldsack, highest_used, over_written, empty_avail);
return(0);
}
#endif