/* * q_netem.c NETEM. * * 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. * * Authors: Stephen Hemminger * */ #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "tc_util.h" #include "tc_common.h" static void explain(void) { fprintf(stderr, "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) { fprintf(stderr, "Illegal \"%s\"\n", 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) { #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(&req.opt, 0, sizeof(req.opt)); req.opt.limit = 1000; while (argc > 0) { if (matches(*argv, "limit") == 0) { NEXT_ARG(); if (get_size(&req.opt.limit, *argv)) { explain1("limit"); return -1; } } else if (matches(*argv, "latency") == 0 || matches(*argv, "delay") == 0) { NEXT_ARG(); if (get_ticks(&req.opt.latency, *argv)) { explain1("latency"); return -1; } 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(&req.opt.loss, *argv)) { explain1("loss"); return -1; } 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(&req.opt.gap, *argv, 0)) { explain1("gap"); return -1; } } else if (matches(*argv, "duplicate") == 0) { NEXT_ARG(); if (get_percent(&req.opt.duplicate, *argv)) { explain1("duplicate"); return -1; } 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(); count = get_distribution(*argv, req.opt.delay_dist, REQ_DIST_SIZE); if (count < 0) { explain1("distribution"); return -1; } size = sizeof(struct tc_netem_qopt) + count * sizeof(req.opt.delay_dist[0]); } else if (strcmp(*argv, "help") == 0) { explain(); return -1; } else { fprintf(stderr, "What is \"%s\"?\n", *argv); explain(); return -1; } argc--; argv++; } if (addattr_l(n, TCA_BUF_MAX, TCA_OPTIONS, &req, size)) { fprintf(stderr, "netem: options encoding problem\n"); return -1; } return 0; } static int netem_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) { struct tc_netem_qopt *qopt; SPRINT_BUF(b1); if (opt == NULL) return 0; 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, " 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; } static int netem_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats) { return 0; } struct qdisc_util netem_util = { .id = "netem", .parse_qopt = netem_parse_opt, .print_qopt = netem_print_opt, .print_xstats = netem_print_xstats, };