UMTS RRC state machine emulator module. User-space ABI in the
iproute tc package. All in one big patch, easy to split,
and easy to rebase, otherwise.

Based off Debian's iproute-20100519.

Signed-off-by: Andres Lagar-Cavilla <andres@lagarcavilla.org>

diff -r 2fc1dc07d6a3 include/linux/pkt_sched.h
--- a/include/linux/pkt_sched.h
+++ b/include/linux/pkt_sched.h
@@ -467,6 +467,75 @@ struct tc_netem_corrupt {
 
 #define NETEM_DIST_SCALE	8192
 
+/* UMTS RRC emulatr */
+
+enum {
+	TCA_RRC_UNSPEC,
+	TCA_RRC_DATA,
+	__TCA_RRC_MAX,
+};
+
+#define TCA_RRC_MAX (__TCA_RRC_MAX - 1)
+
+struct tc_rrc_qopt {
+	__u32	dch_tail;				/* length of dch tail in integral seconds */
+	__u32   fach_tail;				/* length of fach tail in integral seconds */
+	__u32	fach_dch_promo_mu;		/* average ms latency to upswitch from fach into dch */
+	__u32	fach_dch_promo_sigma;	/* +- ms latency to upswitch from fach into dch */
+	__u32   idle_promo_mu;			/* average ms latency to upswitch out of idle */
+	__u32   idle_promo_sigma;		/* +- ms latency to upswitch out of idle */
+	__u32   rlc_buf_threshold_ul;	/* RLC buffer threshold to leave fach for uplink */
+	__u32   rlc_buf_threshold_dl;	/* RLC buffer threholds to leave fach for downlink */
+	__u32	delay;					/* Wireless channel delay (average ms) */
+	__u32	jitter;					/* Wireless channel jitter (stdev ms) */
+	__u32	drop;					/* Wireless channel % drop packets */
+	__u32	dl_dch_rate;			/* Wireless channel downlink rate in bps */
+	__u32	ul_dch_rate;			/* Wireless channel uplink rate in bps */
+	__u32	flags;					/* See flag definitions below */
+	/* Second section is stats */
+	__u32   dch_pkts_ul;
+	__u32   dch_pkts_dl;
+	__u32   dch_bytes_ul;
+	__u32   dch_bytes_dl;
+	__u32   fach_pkts_ul;
+	__u32   fach_pkts_dl;
+	__u32   fach_bytes_ul;
+	__u32   fach_bytes_dl;
+	__u32	idle_upswitch;
+	__u32   dch_downswitch;
+	__u32   fach_upswitch;
+	__u32   fach_downswitch;
+	__u32   dch_ticks;
+	__u32   fach_ticks;
+	__u32   idle_trans_ticks;
+	__u32   fach_trans_ticks;
+	__u32   fd_calls;
+	__u32   fd_sleep_ticks;
+	__u32   fd_pkt_drops;
+	__u32	fd_byte_drops;
+};
+
+#define RRC_FLAG_IDLE_TO_DCH		1 /* upswitch from IDLE into DCH, otherwise IDLE->FACH */
+#define RRC_FLAG_IF_INVERT			2 /* If operating on a vif from a Xen dom0, ingress is 
+									   * actually egress, and viceversa */
+#define RRC_FLAG_FAST_DORMANCY		4 /* Trigger fast dormancy */
+
+#define DFLT_DCH_TAIL_S             5
+#define DFLT_FACH_TAIL_S            12
+#define DFLT_FACH_DCH_MU_MS         1500
+#define DFLT_FACH_DCH_SIGMA_MS      500
+#define DFLT_IDLE_PROMO_MU_MS       2000
+#define DFLT_IDLE_PROMO_SIGMA_MS    1000
+#define DFLT_RLC_BUF_THRESHOLD_UL   540
+#define DFLT_RLC_BUF_THRESHOLD_DL   475
+#define DFLT_IDLE_TO_DCH            1
+#define DFLT_IF_INVERT              1
+#define DFLT_DELAY_MS				30
+#define DFLT_JITTER_MS				10
+#define DFLT_DROP_PCT				8
+#define DFLT_DL_DCH_RATE_MBPS		7
+#define DFLT_UL_DCH_RATE_MBPS		1
+
 /* DRR */
 
 enum {
diff -r 2fc1dc07d6a3 tc/Makefile
--- a/tc/Makefile
+++ b/tc/Makefile
@@ -14,6 +14,7 @@ TCMODULES += q_cbq.o
 TCMODULES += q_rr.o
 TCMODULES += q_multiq.o
 TCMODULES += q_netem.o
+TCMODULES += q_rrc.o
 TCMODULES += f_rsvp.o
 TCMODULES += f_u32.o
 TCMODULES += f_route.o
diff -r 2fc1dc07d6a3 tc/q_rrc.c
--- /dev/null
+++ b/tc/q_rrc.c
@@ -0,0 +1,327 @@
+/*
+ * q_rrc.c        UMTS Radio Resource Control Emulator.
+ *
+ *        This program is free software; you can redistribute it and/or
+ *        modify it under the terms of the GNU General Public License
+ *        as published by the Free Software Foundation; either version
+ *        2 of the License, or (at your option) any later version.
+ *
+ * Author: Andres Lagar-Cavilla <andres@research.att.com>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <errno.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_common.h"
+
+static void explain(void)
+{
+    fprintf(stderr,
+"Usage: ... rrc [ dchtail SECONDS (%d) ] \n" \
+"                 [ fachtail SECONDS (%d) ]\n" \
+"                 [ fachdchmu MILISECS (%d) ]\n" \
+"                 [ fachdchsigma MILISECS (%d) ]\n" \
+"                 [ idlepromomu MILISECS (%d) ]\n" \
+"                 [ idlepromosigma MILISECS (%d) ]\n" \
+"                 [ rlcul BYTES (%d) ] \n" \
+"                 [ rlcdl BYTES(%d) ] \n" \
+"                 [ idletodch INTBOOL (%s) ]\n" \
+"                 [ inverted INTBOOL (%s) ]\n" \
+"                 [ delay MILISECS (%d) ]\n" \
+"                 [ jitter MILISECS (%d) ]\n" \
+"                 [ drop PERCENT (%d) ]\n" \
+"                 [ dchdlrate MBPS (%d) ]\n" \
+"                 [ dchulrate MBPS (%d) ]\n", 
+                    DFLT_DCH_TAIL_S, DFLT_FACH_TAIL_S, DFLT_FACH_DCH_MU_MS, 
+                    DFLT_FACH_DCH_SIGMA_MS, DFLT_IDLE_PROMO_MU_MS, 
+                    DFLT_IDLE_PROMO_SIGMA_MS, DFLT_RLC_BUF_THRESHOLD_UL,
+                    DFLT_RLC_BUF_THRESHOLD_DL, (DFLT_IDLE_TO_DCH) ? "yes" : "no",
+                    (DFLT_IF_INVERT) ? "yes" : "no", DFLT_DELAY_MS,
+                    DFLT_JITTER_MS, DFLT_DROP_PCT, DFLT_DL_DCH_RATE_MBPS,
+                    DFLT_UL_DCH_RATE_MBPS);
+}
+
+static void explain1(const char *arg)
+{
+    fprintf(stderr, "Illegal \"%s\"\n", arg);
+}
+
+static inline int __get_ticks(__u32 *ticks, unsigned t)
+{
+    if (tc_core_time2big(t)) {
+        fprintf(stderr, "Illegal %u time (too large)\n", t);
+        return -1;
+    }
+
+    *ticks = tc_core_time2tick(t);
+    return 0;
+}
+
+/* Adjust for the fact that psched_ticks aren't always usecs
+   (based on kernel PSCHED_CLOCK configuration */
+static int get_ticks(__u32 *ticks, const char *str)
+{
+    unsigned t;
+
+    if(get_time(&t, str))
+        return -1;
+
+    return __get_ticks(ticks, t);
+}
+
+static int rrc_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+               struct nlmsghdr *n)
+{
+    struct rtattr *tail;
+    struct tc_rrc_qopt opt;
+    SPRINT_BUF(b1);
+
+    /* Set defaults */
+    memset(&opt, 0, sizeof(opt));
+    __get_ticks(&opt.dch_tail,               DFLT_DCH_TAIL_S          * TIME_UNITS_PER_SEC);
+    __get_ticks(&opt.fach_tail,              DFLT_FACH_TAIL_S         * TIME_UNITS_PER_SEC);
+    __get_ticks(&opt.fach_dch_promo_mu,      DFLT_FACH_DCH_MU_MS      * TIME_UNITS_PER_SEC / 1000);
+    __get_ticks(&opt.fach_dch_promo_sigma,   DFLT_FACH_DCH_SIGMA_MS   * TIME_UNITS_PER_SEC / 1000);
+    __get_ticks(&opt.idle_promo_mu,          DFLT_IDLE_PROMO_MU_MS    * TIME_UNITS_PER_SEC / 1000);
+    __get_ticks(&opt.idle_promo_sigma,       DFLT_IDLE_PROMO_SIGMA_MS * TIME_UNITS_PER_SEC / 1000);
+    opt.rlc_buf_threshold_ul =  DFLT_RLC_BUF_THRESHOLD_UL;
+    opt.rlc_buf_threshold_dl =  DFLT_RLC_BUF_THRESHOLD_DL;
+    opt.flags = 0 | ((DFLT_IDLE_TO_DCH) ? RRC_FLAG_IDLE_TO_DCH : 0) |
+                    ((DFLT_IF_INVERT) ? RRC_FLAG_IF_INVERT : 0);
+    __get_ticks(&opt.delay,                  DFLT_DELAY_MS            * TIME_UNITS_PER_SEC / 1000);
+    __get_ticks(&opt.jitter,                 DFLT_JITTER_MS           * TIME_UNITS_PER_SEC / 1000);
+    snprintf(b1, 8, "%d", DFLT_DROP_PCT);
+    get_percent(&opt.drop, b1);
+    opt.dl_dch_rate = DFLT_DL_DCH_RATE_MBPS * 125000; /* 1000000 (mbit) / 8 (bits per byte). It */
+    opt.ul_dch_rate = DFLT_UL_DCH_RATE_MBPS * 125000; /* actually wants the rate in bytes */ 
+
+    /* Parse command line */
+    while (argc > 0) {
+		if (matches(*argv, "fastdormancy") == 0) {
+			opt.flags = RRC_FLAG_FAST_DORMANCY;
+			break;
+		} else
+        if (matches(*argv, "dchtail") == 0) {
+            NEXT_ARG();
+            if (get_ticks(&opt.dch_tail, *argv)) {
+                explain1("dchtail");
+                return -1;
+            }
+        } else 
+        if (matches(*argv, "fachtail") == 0) {
+            NEXT_ARG();
+            if (get_ticks(&opt.fach_tail, *argv)) {
+                explain1("fachtail");
+                return -1;
+            }
+        } else 
+        if (matches(*argv, "fachdchmu") == 0) {
+            NEXT_ARG();
+            if (get_ticks(&opt.fach_dch_promo_mu, *argv)) {
+                explain1("fachdchmu");
+                return -1;
+            }
+        } else 
+        if (matches(*argv, "fachdchsigma") == 0) {
+            NEXT_ARG();
+            if (get_ticks(&opt.fach_dch_promo_sigma, *argv)) {
+                explain1("fachdchsigma");
+                return -1;
+            }
+        } else 
+        if (matches(*argv, "idlepromomu") == 0) {
+            NEXT_ARG();
+            if (get_ticks(&opt.idle_promo_mu, *argv)) {
+                explain1("idlepromosigma");
+                return -1;
+            }
+        } else 
+        if (matches(*argv, "idlepromosigma") == 0) {
+            NEXT_ARG();
+            if (get_ticks(&opt.idle_promo_sigma, *argv)) {
+                explain1("idlepromosigma");
+                return -1;
+            }
+        } else 
+        if (matches(*argv, "delay") == 0) {
+            NEXT_ARG();
+            if (get_ticks(&opt.delay, *argv)) {
+                explain1("delay");
+                return -1;
+            }
+        } else 
+        if (matches(*argv, "jitter") == 0) {
+            NEXT_ARG();
+            if (get_ticks(&opt.jitter, *argv)) {
+                explain1("jitter");
+                return -1;
+            }
+        } else 
+        if (matches(*argv, "drop") == 0) {
+            NEXT_ARG();
+            if (get_percent(&opt.drop, *argv)) {
+                explain1("drop");
+                return -1;
+            }
+        } else 
+        if (matches(*argv, "dchdlrate") == 0) {
+            NEXT_ARG();
+            if (get_rate(&opt.dl_dch_rate, *argv)) {
+                explain1("dchdlrate");
+                return -1;
+            }
+        } else 
+        if (matches(*argv, "dchulrate") == 0) {
+            NEXT_ARG();
+            if (get_rate(&opt.ul_dch_rate, *argv)) {
+                explain1("dchulrate");
+                return -1;
+            }
+        } else 
+        if (matches(*argv, "rlcul") == 0) {
+            NEXT_ARG();
+            char *trailer;
+            opt.rlc_buf_threshold_ul = (__u32) strtoul(*argv, &trailer, 0);
+            if (errno == EINVAL) {
+                explain1("rlcul");
+                return -1;
+            }
+        } else 
+        if (matches(*argv, "rlcdl") == 0) {
+            NEXT_ARG();
+            char *trailer;
+            opt.rlc_buf_threshold_dl = (__u32) strtoul(*argv, &trailer, 0);
+            if (errno == EINVAL) {
+                explain1("rlcdl");
+                return -1;
+            }
+        } else 
+        if (matches(*argv, "idletodch") == 0) {
+            NEXT_ARG();
+            char *trailer;
+            if (((__u32) strtoul(*argv, &trailer, 0))) {
+                opt.flags |= RRC_FLAG_IDLE_TO_DCH;
+            } else {
+                opt.flags &= ~RRC_FLAG_IDLE_TO_DCH;
+            }
+            if (errno == EINVAL) {
+                explain1("idletodch");
+                return -1;
+            }
+        } else 
+        if (matches(*argv, "invert") == 0) {
+            NEXT_ARG();
+            char *trailer;
+            if (((__u32) strtoul(*argv, &trailer, 0))) {
+                opt.flags |= RRC_FLAG_IF_INVERT;
+            } else {
+                opt.flags &= ~RRC_FLAG_IF_INVERT;
+            }
+            if (errno == EINVAL) {
+                explain1("invert");
+                return -1;
+            }
+        } else 
+        if (strcmp(*argv, "help") == 0) {
+            explain();
+            return -1;
+        }
+
+        argc--; argv++;
+    }
+
+    /* Sanity checking. If we're asking for fast dormancy, nothing else matters */
+	if (!(opt.flags & RRC_FLAG_FAST_DORMANCY)) {
+		if ((!opt.rlc_buf_threshold_ul) || 
+			(!opt.rlc_buf_threshold_dl) || 
+			(!opt.dch_tail) || 
+			(!opt.fach_tail) || 
+			(!opt.fach_dch_promo_mu) || 
+			(!opt.idle_promo_mu) || 
+			(opt.idle_promo_mu <= opt.idle_promo_sigma) || 
+			(opt.fach_dch_promo_mu <= opt.fach_dch_promo_sigma) ||
+			(!opt.dl_dch_rate) ||
+			(!opt.ul_dch_rate) ||
+			(opt.delay && (opt.delay < opt.jitter))) { 
+			explain();
+			return -1;
+		}
+	}
+
+    /* Pack it up */
+    tail = NLMSG_TAIL(n);
+    if (addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt)) < 0)
+        return -1;
+    tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+
+    return 0;
+}
+
+static int rrc_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+    struct tc_rrc_qopt qopt;
+    int len = RTA_PAYLOAD(opt) - sizeof(qopt);
+    SPRINT_BUF(b1);
+
+    /* Get the goods */
+    if (opt == NULL)
+        return 0;
+
+    if (len < 0) {
+        fprintf(stderr, "options size error\n");
+        return -1;
+    }
+    memcpy(&qopt, RTA_DATA(opt), sizeof(qopt));
+
+    fprintf(f, "\n\tDCH tail %s", sprint_ticks(qopt.dch_tail, b1));
+    fprintf(f, " FACH tail %s\n", sprint_ticks(qopt.fach_tail, b1));
+    fprintf(f, "\tFACH to DCH promo %s", sprint_ticks(qopt.fach_dch_promo_mu, b1));
+    fprintf(f, " +- %s", sprint_ticks(qopt.fach_dch_promo_sigma, b1));
+    fprintf(f, " IDLE promo %s", sprint_ticks(qopt.idle_promo_mu, b1));
+    fprintf(f, " +- %s\n", sprint_ticks(qopt.idle_promo_sigma, b1));
+    fprintf(f, "\tRLC buffer threshold UL %d", qopt.rlc_buf_threshold_ul);
+    fprintf(f, " DL %d\n", qopt.rlc_buf_threshold_dl);
+    fprintf(f, "\tFrom IDLE to %s", (qopt.flags & RRC_FLAG_IDLE_TO_DCH) ? "DCH" : "FACH");
+    fprintf(f, " %s\n", (qopt.flags & RRC_FLAG_IF_INVERT) ? "-- interface inverted" : " ");
+    fprintf(f, "\tDELAY %s", sprint_ticks(qopt.delay, b1));
+    fprintf(f, " JITTER %s", sprint_ticks(qopt.jitter, b1));
+    fprintf(f, " DROP %s\n", sprint_percent(qopt.drop, b1));
+    fprintf(f, "\tDCH Rate DL %s", sprint_rate(qopt.dl_dch_rate, b1));
+    fprintf(f, " UL %s\n", sprint_rate(qopt.ul_dch_rate, b1));
+    fprintf(f, "\t**** STATS ****\n");
+    fprintf(f, "\tDCH (p/b) UL %d:%d DL %d:%d\n", qopt.dch_pkts_ul, qopt.dch_bytes_ul,
+                qopt.dch_pkts_dl, qopt.dch_bytes_dl);
+    fprintf(f, "\tFACH (p/b) UL %d:%d DL %d:%d\n", qopt.fach_pkts_ul, qopt.fach_bytes_ul,
+                qopt.fach_pkts_dl, qopt.fach_bytes_dl);
+    fprintf(f, "\tDCH Time %s", sprint_ticks(qopt.dch_ticks, b1));
+    fprintf(f, " FACH Time %s\n", sprint_ticks(qopt.fach_ticks, b1));
+    fprintf(f, "\tIDLE upswitches %d DCH downswitches %d FACH us %d ds %d\n",
+                qopt.idle_upswitch, qopt.dch_downswitch, qopt.fach_upswitch, qopt.fach_downswitch);
+    fprintf(f, "\tTransition Time IDLE %s", sprint_ticks(qopt.idle_trans_ticks, b1));
+    fprintf(f, " FACH %s\n", sprint_ticks(qopt.fach_trans_ticks, b1));
+    fprintf(f, "\tFD calls %d Drops (p/b) %d:%d Time total %s", qopt.fd_calls, 
+                qopt.fd_pkt_drops, qopt.fd_byte_drops, 
+                sprint_ticks(qopt.fd_sleep_ticks, b1));
+    fprintf(f, " avg %s\n", (qopt.fd_calls) ? 
+                        sprint_ticks(qopt.fd_sleep_ticks / qopt.fd_calls, b1) : "NaN");
+
+    return 0;
+}
+
+struct qdisc_util rrc_qdisc_util = {
+    .id           = "rrc",
+    .parse_qopt    = rrc_parse_opt,
+    .print_qopt    = rrc_print_opt,
+};
+
