diff --git a/tc/q_netem.c b/tc/q_netem.c index bcd16e29..5f0d493b 100644 --- a/tc/q_netem.c +++ b/tc/q_netem.c @@ -19,16 +19,21 @@ #include #include #include +#include #include "utils.h" #include "tc_util.h" +#include "tc_common.h" static void explain(void) { fprintf(stderr, -"Usage: ... netem latency TIME [ jitter TIME ] [ limit PACKETS] \n" \ -" [ loss PERCENT ] [ duplicate PERCENT ]\n" \ -" [ gap PACKETS]\n"); +"Usage: ... netem [ limit PACKETS ] \n" \ +" [ delay TIME [ JITTER [CORRELATION]]]\n" \ +" [ drop PERCENT [CORRELATION]] \n" \ +" [ duplicate PERCENT [CORRELATION]]\n" \ +" [ distribution {uniform|normal|pareto|paretonormal} ]\n" \ +" [ gap PACKETS ]\n"); } static void explain1(const char *arg) @@ -38,59 +43,158 @@ static void explain1(const char *arg) #define usage() return(-1) +static int get_distribution(const char *type, __s16 *data, int limit) +{ + FILE *f; + int n; + char *p, *endp, buf[256]; + + snprintf(buf, 256, "/usr/lib/tc/%s.dist", type); + f = fopen(buf, "r"); + if (!f) { + fprintf(stderr, "No distribution data for %s (%s: %s)\n", + type, buf, strerror(errno)); + return -1; + } + + n = 0; + while (fgets(buf, sizeof(buf), f) != NULL) { + if (*buf == '#') + continue; + + for (p = buf; *p && n < limit; p = endp) { + long x = strtol(p, &endp, 0); + if (endp == p) + break; /* no more digits */ + data[n++] = x; + } + } + fclose(f); + + if (n > limit) { + fprintf(stderr, "Too much distribution data for %s\n", type); + return -1; + } + + return n; +} + +static int isnumber(const char *arg) +{ + char *p; + (void) strtod(arg, &p); + return (p != arg); +} + +#define NEXT_IS_NUMBER() (NEXT_ARG_OK() && isnumber(argv[1])) + +/* 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_usecs(&t, str)) + return -1; + + *ticks = tc_core_usec2tick(t); + return 0; +} + +static char *sprint_ticks(__u32 ticks, char *buf) +{ + return sprint_usecs(tc_core_tick2usec(ticks), buf); +} + + static int netem_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n) { - struct tc_netem_qopt opt; - unsigned latency = 0; - int ok = 0; +#define REQ_DIST_SIZE (TCA_BUF_MAX/sizeof(__s16)) + struct { + struct tc_netem_qopt opt; + __s16 dbuf[REQ_DIST_SIZE]; + } req; + int size = sizeof(struct tc_netem_qopt); - memset(&opt, 0, sizeof(opt)); - opt.limit = 1000; + memset(&req.opt, 0, sizeof(req.opt)); + req.opt.limit = 1000; while (argc > 0) { if (matches(*argv, "limit") == 0) { NEXT_ARG(); - if (get_size(&opt.limit, *argv)) { + if (get_size(&req.opt.limit, *argv)) { explain1("limit"); return -1; } - ok++; - } else if (matches(*argv, "latency") == 0) { + } else if (matches(*argv, "latency") == 0 || + matches(*argv, "delay") == 0) { NEXT_ARG(); - if (get_usecs(&latency, *argv)) { + if (get_ticks(&req.opt.latency, *argv)) { explain1("latency"); return -1; } - ok++; - } else if (matches(*argv, "loss") == 0) { + + if (NEXT_IS_NUMBER()) { + NEXT_ARG(); + if (get_ticks(&req.opt.jitter, *argv)) { + explain1("latency"); + return -1; + } + + if (NEXT_IS_NUMBER()) { + NEXT_ARG(); + if (get_percent(&req.opt.delay_corr, + *argv)) { + explain1("latency"); + return -1; + } + } + } + } else if (matches(*argv, "loss") == 0 || + matches(*argv, "drop") == 0) { NEXT_ARG(); - if (get_percent(&opt.loss, *argv)) { + if (get_percent(&req.opt.loss, *argv)) { explain1("loss"); return -1; } - ok++; + if (NEXT_IS_NUMBER()) { + NEXT_ARG(); + if (get_percent(&req.opt.loss_corr, *argv)) { + explain1("loss"); + return -1; + } + } } else if (matches(*argv, "gap") == 0) { NEXT_ARG(); - if (get_u32(&opt.gap, *argv, 0)) { + if (get_u32(&req.opt.gap, *argv, 0)) { explain1("gap"); return -1; } - ok++; } else if (matches(*argv, "duplicate") == 0) { NEXT_ARG(); - if (get_percent(&opt.duplicate, *argv)) { + if (get_percent(&req.opt.duplicate, *argv)) { explain1("duplicate"); return -1; } - ok++; - } else if (matches(*argv, "jitter") == 0) { + if (NEXT_IS_NUMBER()) { + NEXT_ARG(); + if (get_percent(&req.opt.dup_corr, *argv)) { + explain1("duplicate"); + return -1; + } + } + } else if (matches(*argv, "distribution") == 0) { + int count; NEXT_ARG(); - if (get_usecs(&opt.jitter, *argv)) { - explain1("jitter"); + count = get_distribution(*argv, req.opt.delay_dist, + REQ_DIST_SIZE); + if (count < 0) { + explain1("distribution"); return -1; } - ok++; + size = sizeof(struct tc_netem_qopt) + + count * sizeof(req.opt.delay_dist[0]); } else if (strcmp(*argv, "help") == 0) { explain(); return -1; @@ -102,9 +206,12 @@ static int netem_parse_opt(struct qdisc_util *qu, int argc, char **argv, argc--; argv++; } - opt.latency = tc_core_usec2tick(latency); + if (addattr_l(n, TCA_BUF_MAX, TCA_OPTIONS, &req, size)) { + fprintf(stderr, "netem: options encoding problem\n"); + return -1; + } - return ok ? addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt)) : 0; + return 0; } static int netem_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) @@ -115,25 +222,42 @@ static int netem_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) if (opt == NULL) return 0; - if (RTA_PAYLOAD(opt) < sizeof(*qopt)) + if (RTA_PAYLOAD(opt) < sizeof(*qopt)) { + fprintf(stderr, "netem response too short\n"); return -1; + } qopt = RTA_DATA(opt); fprintf(f, "limit %d", qopt->limit); - if (qopt->latency) - fprintf(f, " latency %s", - sprint_usecs(tc_core_tick2usec(qopt->latency), b1)); - if (qopt->jitter) - fprintf(f, " jitter %s", sprint_usecs(qopt->jitter, b1)); - if (qopt->loss) + if (qopt->latency) { + fprintf(f, " delay %s", sprint_ticks(qopt->latency, b1)); + + if (qopt->jitter) { + fprintf(f, " %s", sprint_ticks(qopt->jitter, b1)); + if (qopt->delay_corr) + fprintf(f, " %s", sprint_percent(qopt->delay_corr, b1)); + } + } + + if (qopt->loss) { fprintf(f, " loss %s", sprint_percent(qopt->loss, b1)); + if (qopt->loss_corr) + fprintf(f, " %s", sprint_percent(qopt->loss_corr, b1)); + } + + if (qopt->duplicate) { + fprintf(f, " duplicate %s", + sprint_percent(qopt->duplicate, b1)); + if (qopt->dup_corr) + fprintf(f, " %s", sprint_percent(qopt->dup_corr, b1)); + } + if (qopt->gap) fprintf(f, " gap %lu", (unsigned long)qopt->gap); - return 0; } @@ -148,3 +272,4 @@ struct qdisc_util netem_util = { .print_qopt = netem_print_opt, .print_xstats = netem_print_xstats, }; +