Date: Mon, 23 Dec 1996 15:19:42 +1100 (EST) From: Julian Assange <proff@iq.org> To: danny@panda.hilink.com.au (Daniel O'Callaghan) Cc: hackers@freebsd.org Subject: Re: ipretard.c selective tcp/ip queues and throughput limiters Message-ID: <199612230419.PAA03748@profane.iq.org> In-Reply-To: <Pine.BSF.3.91.961223091538.229V-100000@panda.hilink.com.au> from Daniel O'Callaghan at "Dec 23, 96 09:16:35 am"
next in thread | previous in thread | raw e-mail | index | archive | help
Written this morning. If people find it useful, I'll polish it. example: # ipfw add divert 92 tcp from any to any 80 out via ed0 # ./ipretard -v -t 1000/300 -w 2208 /* ipretard (c) 1996 Julian Assange (proff@suburbia.net) All Rights Reserved */ #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <sys/types.h> #include <sys/uio.h> #include <unistd.h> #include <string.h> #include <fcntl.h> #include <signal.h> #include <errno.h> #include <sys/socket.h> #include <netinet/in_systm.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/tcp.h> #define MAX_IP (65535+100) #define MAX_PACKET (MAX_IP+sizeof(struct packet)-1) struct packet { struct packet *next, *prev; time_t time; int len; struct sockaddr_in in; char data[1]; }; int holdoff_write=0; int holdoffs=0; int delay_drops=0; int window_changes=0; int bytes_in=0; int bytes_out=0; int add_bytes_out=0; int packets_in=0; int packets_out=0; int packets_queued=0; int bytes_queued=0; double samples=60.0*5.0; /* 5 minute period */ double max_thru=0.0; double load_avg; time_t ti; int verbose=0; void sigalrm(int i) { if (max_thru!=0.0) { load_avg=load_avg*(1.0-1.0/samples)+add_bytes_out/samples; if (load_avg>max_thru) { holdoff_write = 1; holdoffs++; } else holdoff_write = 0; } bytes_out+=add_bytes_out; add_bytes_out=0; if (verbose) printf("in: %d/%d %d avg out: %d/%d %d avg queued: %d/%d drop: %d win_change: %d holdoffs: %d loadavg %.2f\n", bytes_in, packets_in, packets_in? bytes_in/packets_in: 0, bytes_out, packets_out, packets_out? bytes_out/packets_out: 0, bytes_queued, packets_queued, delay_drops, window_changes, holdoffs, load_avg); ti=time(NULL); #ifdef POSIX signal(SIGALRM, sigalrm); #endif alarm(1); } u_short fast_ip_gen_check(struct ip *ip) /* well, for C anyway.. */ { register u_short *u = (u_short *)ip; register int sum = 0; u_char hl = ip->ip_hl * 4; sum += *u++; sum += *u++; sum += *u++; sum += *u++; sum += *u++; u++; /* skip checksum */ sum += *u++; sum += *u++; sum += *u++; sum += *u++; if (hl != sizeof (*ip)) for (hl = (hl - sizeof (*ip)) / 2; hl--;) sum += *u++; sum = (sum >> 16) + (sum & 0xffff); /* fold carries */ return (u_short) ~sum; } u_short fast_tcp_gen_check(struct ip *ip) /* well, for C anyway.. */ { register u_short *u; register int sum = 0; u_short len = ntohs(ip->ip_len) - ip->ip_hl * 4; u_char resprot[2] = {0, IPPROTO_TCP}; u = (u_short *) &ip->ip_src; sum += *u++; sum += *u++; sum += *u++; sum += *u++; sum += *(u_short *) resprot; sum += htons(len); u = (u_short *) ((char*)ip + ip->ip_hl * 4); sum += *u++; sum += *u++; sum += *u++; sum += *u++; sum += *u++; sum += *u++; sum += *u++; sum += *u++; u++; /* skip th_sum */ sum += *u++; if (!(len -= sizeof (struct tcphdr))) goto plainhdr; for (; len > 15; len -= 16) { sum += *u++; sum += *u++; sum += *u++; sum += *u++; sum += *u++; sum += *u++; sum += *u++; sum += *u++; } for (; len > 1; len -= 2) sum += *u++; if (len == 1) sum += *(u_char *) u; plainhdr: sum = (sum >> 16) + (sum & 0xffff); /* fold carries */ len = ~sum; return len; } void usage(char *av0) { fprintf(stderr, "usage: %s [-b ring_buf_len][-d max_packet_delay][-p divert_port][-t max_throughput_per_sec/sample_period][-v][-w max_window]\n", av0); exit(1); } int main(int argc, char **argv) { int c; int fd; int div_port=92; int buf_len=256*1024; /* 256k */ struct sockaddr_in in; fd_set fdr_set, fdw_set; int max_window=0; int max_ring_delay=5; struct packet *pq_base, *pq_head=NULL, *pq_tail; while ((c=getopt(argc, argv, "b:d:p:t:vw:"))!=-1) switch(c) { case 'b': buf_len=atoi(optarg); if (buf_len<MAX_PACKET) { fprintf(stderr, "%s: buf_len must be at least %d bytes\n", argv[0], MAX_PACKET); exit(1); } break; case 'd': max_ring_delay=atoi(optarg); break; case 'p': div_port=atoi(optarg); break; case 't': { char *p=strchr(optarg, '/'); if (!p) { fprintf(stderr, "%s: -m requires max_throughput/period e.g -m 5000/600 is 5k/s average measured over a 10 minute period\n", argv[0]); usage(argv[0]); } *p++='\0'; max_thru=atof(optarg); samples=atoi(p); } case 'v': verbose++; break; case 'w': max_window=atoi(optarg); break; default: usage(argv[0]); } if ((fd=socket(PF_INET, SOCK_RAW, IPPROTO_DIVERT))<0) { perror("socket"); exit(1); } memset(&in, 0, sizeof in); in.sin_family=PF_INET; in.sin_port=htons(div_port); if (bind(fd, (struct sockaddr *)&in, sizeof in)!=0) { perror("bind"); exit(1); } if (!(pq_tail=pq_base=malloc(buf_len))) { perror("malloc"); exit(1); } fcntl(fd, F_SETFL, O_NONBLOCK|fcntl(fd, F_GETFL, 0)); memset(pq_tail, 0, sizeof *pq_tail); FD_ZERO(&fdr_set); FD_ZERO(&fdw_set); signal(SIGALRM, sigalrm); alarm(1); ti=time(NULL); for (;;) { int n; if (bytes_queued<buf_len-MAX_PACKET-sizeof(struct packet)) FD_SET(fd, &fdr_set); else FD_CLR(fd, &fdr_set); if (!holdoff_write && pq_head) FD_SET(fd, &fdw_set); else FD_CLR(fd, &fdw_set); n=select(fd+1, &fdr_set, &fdw_set, NULL, NULL); if (n==0) continue; if (n<0) { if (errno==EINTR) continue; perror("select"); exit(1); } if (FD_ISSET(fd, &fdr_set)) { int cc; int in_len=sizeof(struct sockaddr_in); while ((cc=recvfrom(fd, pq_tail->data, MAX_IP, 0, (struct sockaddr*)&pq_tail->in, &in_len))>0) { struct packet *pq; pq_tail->len=cc; pq_tail->time=ti; bytes_queued+=sizeof(struct packet)-1+cc; bytes_in+=cc; if ((pq=(char*)pq_tail+sizeof(struct packet)-1+cc)+MAX_PACKET>(char *)pq_base+buf_len) pq=pq_base; if (!pq_head) pq_head=pq_tail; if (pq_tail->prev) pq_tail->prev->next=pq_tail; pq->next=NULL; pq->prev=pq_tail; pq_tail=pq; packets_queued++; packets_in++; } } if (packets_queued && FD_ISSET(fd, &fdw_set)) { if (ti-pq_head->time > max_ring_delay) { delay_drops++; goto deqeue; } if (max_window && pq_head->len>=sizeof(struct ip)+sizeof(struct tcphdr)) { struct ip *ip=(struct ip*)pq_head->data; u_short len=ntohs(ip->ip_len); if (ip->ip_p==IPPROTO_TCP && len==pq_head->len) { struct tcphdr *tcp=(struct tcphdr*)((char*)ip+ip->ip_hl*4); if (ntohs(tcp->th_win)>max_window) { tcp->th_win=htons(max_window); tcp->th_sum=fast_tcp_gen_check(ip); window_changes++; } } } /* according to divert(4) diverts to incoming need a valid ip_sum, while outgoing diverts have their ip_sum recalculated by the ip stack */ if (pq_head->in.sin_addr.s_addr!=INADDR_ANY) { struct ip *ip=(struct ip*)pq_head->data; ip->ip_sum=fast_ip_gen_check(ip); } if (sendto(fd, pq_head->data, pq_head->len, 0, (struct sockaddr*)&pq_head->in, sizeof(pq_head->in))==pq_head->len) { packets_out++; add_bytes_out+=pq_head->len; deqeue: bytes_queued-=sizeof(struct packet)-1+pq_head->len; pq_head=pq_head->next; if (pq_head) pq_head->prev=NULL; packets_queued--; } else { switch (errno) { case EAGAIN: case ENOBUFS: continue; case EMSGSIZE: default: perror("sendto"); goto deqeue; } } } } exit(1); }
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199612230419.PAA03748>