summaryrefslogtreecommitdiffstats
path: root/sys/netinet/ip_dummynet.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/netinet/ip_dummynet.c')
-rw-r--r--sys/netinet/ip_dummynet.c644
1 files changed, 644 insertions, 0 deletions
diff --git a/sys/netinet/ip_dummynet.c b/sys/netinet/ip_dummynet.c
new file mode 100644
index 0000000..401e24d
--- /dev/null
+++ b/sys/netinet/ip_dummynet.c
@@ -0,0 +1,644 @@
+/*
+ * Copyright (c) 1998 Luigi Rizzo
+ *
+ * Redistribution and use in source forms, with and without modification,
+ * are permitted provided that this entire comment appears intact.
+ *
+ * Redistribution in binary form may occur without any restrictions.
+ * Obviously, it would be nice if you gave credit where credit is due
+ * but requiring it would be too onerous.
+ *
+ * This software is provided ``AS IS'' without any warranties of any kind.
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * This module implements IP dummynet, a bandwidth limiter/delay emulator
+ * used in conjunction with the ipfw package.
+ *
+ * Changes:
+ *
+ * 980821: changed conventions in the queueing logic
+ * packets passed from dummynet to ip_in/out are prepended with
+ * a vestigial mbuf type MT_DUMMYNET which contains a pointer
+ * to the matching rule.
+ * ip_input/output will extract the parameters, free the vestigial mbuf,
+ * and do the processing.
+ *
+ * 980519: fixed behaviour when deleting rules.
+ * 980518: added splimp()/splx() to protect against races
+ * 980513: initial release
+ */
+
+/* include files marked with XXX are probably not needed */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/queue.h> /* XXX */
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/time.h>
+#include <sys/sysctl.h>
+#include <net/if.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/in_var.h>
+#include <netinet/ip.h>
+#include <netinet/ip_fw.h>
+#include <netinet/ip_dummynet.h>
+#include <netinet/ip_var.h>
+
+#include "opt_bdg.h"
+#ifdef BRIDGE
+#include <netinet/if_ether.h> /* for struct arpcom */
+#include <net/bridge.h>
+#endif
+
+static struct dn_pipe *all_pipes = NULL ; /* list of all pipes */
+
+static int dn_debug = 0 ; /* verbose */
+static int dn_calls = 0 ; /* number of calls */
+static int dn_idle = 1;
+#ifdef SYSCTL_NODE
+SYSCTL_NODE(_net_inet_ip, OID_AUTO, dummynet, CTLFLAG_RW, 0, "Dummynet");
+SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, debug, CTLFLAG_RW, &dn_debug, 0, "");
+SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, calls, CTLFLAG_RD, &dn_calls, 0, "");
+SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, idle, CTLFLAG_RD, &dn_idle, 0, "");
+#endif
+
+static int ip_dn_ctl(struct sockopt *sopt);
+
+static void rt_unref(struct rtentry *);
+static void dummynet(void *);
+static void dn_restart(void);
+static void dn_move(struct dn_pipe *pipe, int immediate);
+static void dummynet_flush(void);
+
+/*
+ * the following is needed when deleting a pipe, because rules can
+ * hold references to the pipe.
+ */
+extern LIST_HEAD (ip_fw_head, ip_fw_chain) ip_fw_chain;
+
+/*
+ * invoked to reschedule the periodic task if necessary.
+ * Should only be called when dn_idle = 1 ;
+ */
+static void
+dn_restart()
+{
+ struct dn_pipe *pipe;
+
+ if (!dn_idle)
+ return;
+
+ for (pipe = all_pipes ; pipe ; pipe = pipe->next ) {
+ /* if there any pipe that needs work, restart */
+ if (pipe->r.head || pipe->p.head || pipe->numbytes < 0 ) {
+ dn_idle = 0;
+ timeout(dummynet, NULL, 1);
+ return ;
+ }
+ }
+}
+
+static void
+rt_unref(struct rtentry *rt)
+{
+ if (rt == NULL)
+ return ;
+ if (rt->rt_refcnt <= 0)
+ printf("-- warning, refcnt now %ld, decreasing\n", rt->rt_refcnt);
+ RTFREE(rt);
+}
+
+/*
+ * move packets from R-queue to P-queue
+ */
+static void
+dn_move(struct dn_pipe *pipe, int immediate)
+{
+ struct dn_pkt *pkt;
+
+ /*
+ * consistency check, should catch new pipes which are
+ * not initialized properly.
+ */
+ if ( pipe->p.head == NULL &&
+ pipe->ticks_from_last_insert != pipe->delay) {
+ printf("Warning, empty pipe and delay %d (should be %d)\n",
+ pipe->ticks_from_last_insert, pipe->delay);
+ pipe->ticks_from_last_insert = pipe->delay;
+ }
+ /* this ought to go in dn_dequeue() */
+ if (!immediate && pipe->ticks_from_last_insert < pipe->delay)
+ pipe->ticks_from_last_insert++;
+ if ((pkt = pipe->r.head) != NULL) {
+ /*
+ * Move at most numbytes bytes from src and move to dst.
+ * delay is set to ticks_from_last_insert, which
+ * is reset after the first insertion;
+ */
+ while ( pkt ) {
+ int len = pkt->dn_m->m_pkthdr.len ;
+
+ /*
+ * queue limitation: pass packets down if the len is
+ * such that the pkt would go out before the next tick.
+ */
+ if (pipe->bandwidth) {
+ int len_scaled = len*8*hz ;
+ /* numbytes is in bit/sec, scaled 8*hz ... */
+ if (pipe->numbytes < len_scaled)
+ break;
+ pipe->numbytes -= len_scaled;
+ }
+ pipe->r_len--; /* elements in queue */
+ pipe->r_len_bytes -= len ;
+
+ /*
+ * to add delay jitter, must act here. A lower value
+ * (bounded to 0) means lower delay.
+ */
+ pkt->delay = pipe->ticks_from_last_insert;
+ pipe->ticks_from_last_insert = 0;
+ /* compensate the decrement done next in dn_dequeue */
+ if (!immediate && pkt->delay >0 && pipe->p.head==NULL)
+ pkt->delay++;
+ if (pipe->p.head == NULL)
+ pipe->p.head = pkt;
+ else
+ (struct dn_pkt *)pipe->p.tail->dn_next = pkt;
+ pipe->p.tail = pkt;
+ pkt = (struct dn_pkt *)pkt->dn_next;
+ pipe->p.tail->dn_next = NULL;
+ }
+ pipe->r.head = pkt;
+
+ /*** XXX just a sanity check */
+ if ( ( pkt == NULL && pipe->r_len != 0) ||
+ ( pkt != NULL && pipe->r_len == 0) )
+ printf("-- Warning, pipe head %p len %d\n",
+ (void *)pkt, pipe->r_len);
+ }
+
+ /*
+ * deliver packets downstream after the delay in the P-queue.
+ */
+
+ if (pipe->p.head == NULL)
+ return;
+ if (!immediate)
+ pipe->p.head->delay--;
+ while ( (pkt = pipe->p.head) && pkt->delay < 1) {
+ /*
+ * first unlink, then call procedures since ip_input()
+ * can result in a call to ip_output cnd viceversa,
+ * thus causing nested calls
+ */
+ pipe->p.head = (struct dn_pkt *) pkt->dn_next ;
+
+ /*
+ * the trick to avoid flow-id settings here is to prepend a
+ * vestigial mbuf to the packet, with the following values:
+ * m_type = MT_DUMMYNET
+ * m_next = the actual mbuf to be processed by ip_input/output
+ * m_data = the matching rule
+ * The vestigial element is the same memory area used by
+ * the dn_pkt, and IS FREED HERE because it can contain
+ * parameters passed to the called routine. The buffer IS NOT
+ * A REAL MBUF, just a block of memory acquired with malloc().
+ */
+ switch (pkt->dn_dir) {
+ case DN_TO_IP_OUT: {
+ struct route *ro = &(pkt->ro) ;
+
+ (void)ip_output((struct mbuf *)pkt, (struct mbuf *)pkt->ifp,
+ ro, pkt->dn_dst, NULL);
+ rt_unref (ro->ro_rt) ;
+ }
+ break ;
+ case DN_TO_IP_IN :
+ ip_input((struct mbuf *)pkt) ;
+ break ;
+#ifdef BRIDGE
+ case DN_TO_BDG_FWD : {
+ struct mbuf *m = (struct mbuf *)pkt ;
+
+ bdg_forward(&m, pkt->ifp);
+ if (m)
+ m_freem(m);
+ }
+ break ;
+#endif
+ default:
+ printf("dummynet: bad switch %d!\n", pkt->dn_dir);
+ m_freem(pkt->dn_m);
+ break ;
+ }
+ FREE(pkt, M_IPFW);
+ }
+}
+/*
+ * this is the periodic task that moves packets between the R-
+ * and the P- queue
+ */
+/*ARGSUSED*/
+void
+dummynet(void * __unused unused)
+{
+ struct dn_pipe *p ;
+ int s ;
+
+ dn_calls++ ;
+ for (p = all_pipes ; p ; p = p->next ) {
+ /*
+ * Increment the amount of data that can be sent. However,
+ * don't do that if the channel is idle
+ * (r.head == NULL && numbytes >= bandwidth).
+ * This bug fix is from tim shepard (shep@bbn.com)
+ */
+ s = splimp();
+ if (p->r.head != NULL || p->numbytes < p->bandwidth )
+ p->numbytes += p->bandwidth ;
+ dn_move(p, 0); /* is it really 0 (also below) ? */
+ splx(s);
+ }
+
+ /*
+ * finally, if some queue has data, restart the timer.
+ */
+ s = splimp();
+ dn_idle = 1;
+ dn_restart();
+ splx(s);
+}
+
+/*
+ * dummynet hook for packets.
+ * input and output use the same code, so i use bit 16 in the pipe
+ * number to chose the direction: 1 for output packets, 0 for input.
+ * for input, only m is significant. For output, also the others.
+ */
+int
+dummynet_io(int pipe_nr, int dir,
+ struct mbuf *m, struct ifnet *ifp, struct route *ro,
+ struct sockaddr_in *dst,
+ struct ip_fw_chain *rule)
+{
+ struct dn_pkt *pkt;
+ struct dn_pipe *pipe;
+ int len = m->m_pkthdr.len ;
+
+ int s=splimp();
+
+ pipe_nr &= 0xffff ;
+ /*
+ * locate pipe. First time is expensive, next have direct access.
+ */
+
+ if ( (pipe = rule->rule->pipe_ptr) == NULL ) {
+ for (pipe=all_pipes; pipe && pipe->pipe_nr !=pipe_nr; pipe=pipe->next)
+ ;
+ if (pipe == NULL) {
+ splx(s);
+ if (dn_debug)
+ printf("warning, pkt for no pipe %d\n", pipe_nr);
+ m_freem(m);
+ return 0 ;
+ } else
+ rule->rule->pipe_ptr = pipe ;
+ }
+
+ /*
+ * should i drop ?
+ * This section implements random packet drop.
+ */
+ if ( (pipe->plr && random() < pipe->plr) ||
+ (pipe->queue_size && pipe->r_len >= pipe->queue_size) ||
+ (pipe->queue_size_bytes &&
+ len + pipe->r_len_bytes > pipe->queue_size_bytes) ||
+ (pkt = (struct dn_pkt *)malloc(sizeof (*pkt),
+ M_IPFW, M_NOWAIT) ) == NULL ) {
+ splx(s);
+ if (dn_debug)
+ printf("-- dummynet: drop from pipe %d, have %d pks, %d bytes\n",
+ pipe_nr, pipe->r_len, pipe->r_len_bytes);
+ pipe->r_drops++ ;
+ m_freem(m);
+ return 0 ; /* XXX error */
+ }
+ bzero(pkt, sizeof(*pkt) );
+ /* build and enqueue packet */
+ pkt->hdr.mh_type = MT_DUMMYNET ;
+ (struct ip_fw_chain *)pkt->hdr.mh_data = rule ;
+ pkt->dn_next = NULL;
+ pkt->dn_m = m;
+ pkt->dn_dir = dir ;
+ pkt->delay = 0;
+
+ pkt->ifp = ifp;
+ if (dir == DN_TO_IP_OUT) {
+ /*
+ * we need to copy *ro because for icmp pkts (and maybe others)
+ * the caller passed a pointer into the stack.
+ */
+ pkt->ro = *ro;
+ if (ro->ro_rt)
+ ro->ro_rt->rt_refcnt++ ; /* XXX */
+ /*
+ * and again, dst might be a pointer into *ro...
+ */
+ if (dst == (struct sockaddr_in *)&ro->ro_dst) /* dst points into ro */
+ dst = (struct sockaddr_in *)&(pkt->ro.ro_dst) ;
+
+ pkt->dn_dst = dst; /* XXX this can't be right! */
+ }
+ if (pipe->r.head == NULL)
+ pipe->r.head = pkt;
+ else
+ (struct dn_pkt *)pipe->r.tail->dn_next = pkt;
+ pipe->r.tail = pkt;
+ pipe->r_len++;
+ pipe->r_len_bytes += len ;
+
+ /*
+ * here we could implement RED if we like to
+ */
+
+ if (pipe->r.head == pkt) { /* process immediately */
+ dn_move(pipe, 1);
+ }
+ if (dn_idle)
+ dn_restart();
+ splx(s);
+ return 0;
+}
+
+/*
+ * dispose all packets queued on a pipe
+ */
+static void
+purge_pipe(struct dn_pipe *pipe)
+{
+ struct dn_pkt *pkt, *n ;
+ struct rtentry *tmp_rt ;
+
+ for (pkt = pipe->r.head ; pkt ; ) {
+ rt_unref (tmp_rt = pkt->ro.ro_rt ) ;
+ m_freem(pkt->dn_m);
+ n = pkt ;
+ pkt = (struct dn_pkt *)pkt->dn_next ;
+ free(n, M_IPFW) ;
+ }
+ for (pkt = pipe->p.head ; pkt ; ) {
+ rt_unref (tmp_rt = pkt->ro.ro_rt ) ;
+ m_freem(pkt->dn_m);
+ n = pkt ;
+ pkt = (struct dn_pkt *)pkt->dn_next ;
+ free(n, M_IPFW) ;
+ }
+}
+
+/*
+ * delete all pipes returning memory
+ */
+static void
+dummynet_flush()
+{
+ struct dn_pipe *q, *p = all_pipes ;
+ int s = splnet() ;
+
+ all_pipes = NULL ;
+ splx(s) ;
+ /*
+ * purge all queued pkts and delete all pipes
+ */
+ for ( ; p ; ) {
+ purge_pipe(p);
+ q = p ;
+ p = p->next ;
+ free(q, M_IPFW);
+ }
+}
+
+extern struct ip_fw_chain *ip_fw_default_rule ;
+/*
+ * when a firewall rule is deleted, scan all pipes and remove the flow-id
+ * from packets matching this rule.
+ */
+void
+dn_rule_delete(void *r)
+{
+ struct dn_pipe *p ;
+ int matches = 0 ;
+
+ for ( p = all_pipes ; p ; p = p->next ) {
+ struct dn_pkt *x ;
+ for (x = p->r.head ; x ; x = (struct dn_pkt *)x->dn_next )
+ if (x->hdr.mh_data == r) {
+ matches++ ;
+ x->hdr.mh_data = (void *)ip_fw_default_rule ;
+ }
+ for (x = p->p.head ; x ; x = (struct dn_pkt *)x->dn_next )
+ if (x->hdr.mh_data == r) {
+ matches++ ;
+ x->hdr.mh_data = (void *)ip_fw_default_rule ;
+ }
+ }
+ printf("dn_rule_delete, r %p, default %p%s, %d matches\n",
+ (void *)r, (void *)ip_fw_default_rule,
+ r == ip_fw_default_rule ? " AARGH!":"", matches);
+}
+
+/*
+ * handler for the various dummynet socket options
+ * (get, flush, config, del)
+ */
+static int
+ip_dn_ctl(struct sockopt *sopt)
+{
+ int error = 0 ;
+ size_t size ;
+ char *buf, *bp ;
+ struct dn_pipe *p, tmp_pipe ;
+
+ struct dn_pipe *x, *a, *b ;
+
+ /* Disallow sets in really-really secure mode. */
+ if (sopt->sopt_dir == SOPT_SET && securelevel >= 3)
+ return (EPERM);
+
+ switch (sopt->sopt_name) {
+ default :
+ panic("ip_dn_ctl -- unknown option");
+
+ case IP_DUMMYNET_GET :
+ for (p = all_pipes, size = 0 ; p ; p = p->next )
+ size += sizeof( *p ) ;
+ buf = malloc(size, M_TEMP, M_WAITOK);
+ if (buf == 0) {
+ error = ENOBUFS ;
+ break ;
+ }
+ for (p = all_pipes, bp = buf ; p ; p = p->next ) {
+ struct dn_pipe *q = (struct dn_pipe *)bp ;
+
+ bcopy(p, bp, sizeof( *p ) );
+ /*
+ * return bw and delay in bits/s and ms, respectively
+ */
+ q->delay = (q->delay * 1000) / hz ;
+ bp += sizeof( *p ) ;
+ }
+ error = sooptcopyout(sopt, buf, size);
+ FREE(buf, M_TEMP);
+ break ;
+ case IP_DUMMYNET_FLUSH :
+ dummynet_flush() ;
+ break ;
+ case IP_DUMMYNET_CONFIGURE :
+ p = &tmp_pipe ;
+ error = sooptcopyin(sopt, p, sizeof *p, sizeof *p);
+ if (error)
+ break ;
+ /*
+ * The config program passes parameters as follows:
+ * bandwidth = bits/second (0 = no limits);
+ * delay = ms
+ * must be translated in ticks.
+ * queue_size = slots (0 = no limit)
+ * queue_size_bytes = bytes (0 = no limit)
+ * only one can be set, must be bound-checked
+ */
+ p->delay = ( p->delay * hz ) / 1000 ;
+ if (p->queue_size == 0 && p->queue_size_bytes == 0)
+ p->queue_size = 50 ;
+ if (p->queue_size != 0 ) /* buffers are prevailing */
+ p->queue_size_bytes = 0 ;
+ if (p->queue_size > 100)
+ p->queue_size = 50 ;
+ if (p->queue_size_bytes > 1024*1024)
+ p->queue_size_bytes = 1024*1024 ;
+#if 0
+ printf("ip_dn: config pipe %d %d bit/s %d ms %d bufs\n",
+ p->pipe_nr,
+ p->bandwidth * 8 * hz ,
+ p->delay * 1000 / hz , p->queue_size);
+#endif
+ for (a = NULL , b = all_pipes ; b && b->pipe_nr < p->pipe_nr ;
+ a = b , b = b->next) ;
+ if (b && b->pipe_nr == p->pipe_nr) {
+ /* XXX should spl and flush old pipe... */
+ b->bandwidth = p->bandwidth ;
+ b->delay = p->delay ;
+ b->ticks_from_last_insert = p->delay ;
+ b->queue_size = p->queue_size ;
+ b->queue_size_bytes = p->queue_size_bytes ;
+ b->plr = p->plr ;
+ } else {
+ int s ;
+ x = malloc(sizeof(struct dn_pipe), M_IPFW, M_DONTWAIT) ;
+ if (x == NULL) {
+ printf("ip_dummynet.c: sorry no memory\n");
+ error = ENOSPC ;
+ break ;
+ }
+ bzero(x, sizeof(*x) );
+ x->bandwidth = p->bandwidth ;
+ x->delay = p->delay ;
+ x->ticks_from_last_insert = p->delay ;
+ x->pipe_nr = p->pipe_nr ;
+ x->queue_size = p->queue_size ;
+ x->queue_size_bytes = p->queue_size_bytes ;
+ x->plr = p->plr ;
+
+ s = splnet() ;
+ x->next = b ;
+ if (a == NULL)
+ all_pipes = x ;
+ else
+ a->next = x ;
+ splx(s);
+ }
+ break ;
+
+ case IP_DUMMYNET_DEL :
+ p = &tmp_pipe ;
+ error = sooptcopyin(sopt, p, sizeof *p, sizeof *p);
+ if (error)
+ break ;
+
+ for (a = NULL , b = all_pipes ; b && b->pipe_nr < p->pipe_nr ;
+ a = b , b = b->next) ;
+ if (b && b->pipe_nr == p->pipe_nr) { /* found pipe */
+ int s = splnet() ;
+ struct ip_fw_chain *chain = ip_fw_chain.lh_first;
+
+ if (a == NULL)
+ all_pipes = b->next ;
+ else
+ a->next = b->next ;
+ /*
+ * remove references to this pipe from the ip_fw rules.
+ */
+ for (; chain; chain = chain->chain.le_next) {
+ register struct ip_fw *const f = chain->rule;
+ if (f->pipe_ptr == b)
+ f->pipe_ptr = NULL ;
+ }
+ splx(s);
+ purge_pipe(b); /* remove pkts from here */
+ free(b, M_IPFW);
+ }
+ break ;
+ }
+ return error ;
+}
+
+static void
+ip_dn_init(void)
+{
+ printf("DUMMYNET initialized (990811)\n");
+ all_pipes = NULL ;
+ ip_dn_ctl_ptr = ip_dn_ctl;
+}
+
+static ip_dn_ctl_t *old_dn_ctl_ptr;
+
+static int
+dummynet_modevent(module_t mod, int type, void *data)
+{
+ int s;
+
+ switch (type) {
+ case MOD_LOAD:
+ s = splnet();
+ old_dn_ctl_ptr = ip_dn_ctl_ptr;
+ ip_dn_init();
+ splx(s);
+ break;
+ case MOD_UNLOAD:
+ s = splnet();
+ ip_dn_ctl_ptr = old_dn_ctl_ptr;
+ splx(s);
+ dummynet_flush();
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static moduledata_t dummynet_mod = {
+ "dummynet",
+ dummynet_modevent,
+ NULL
+};
+DECLARE_MODULE(dummynet, dummynet_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);
OpenPOWER on IntegriCloud