diff --git a/include/utils.h b/include/utils.h index 8cb4349e..eba67b6e 100644 --- a/include/utils.h +++ b/include/utils.h @@ -46,6 +46,11 @@ void incomplete_command(void) __attribute__((noreturn)); #define NEXT_ARG_FWD() do { argv++; argc--; } while(0) #define PREV_ARG() do { argv--; argc++; } while(0) +#define TIME_UNITS_PER_SEC 1000000 +#define NSEC_PER_USEC 1000 +#define NSEC_PER_MSEC 1000000 +#define NSEC_PER_SEC 1000000000LL + typedef struct { __u16 flags; @@ -310,4 +315,11 @@ size_t strlcat(char *dst, const char *src, size_t size); void drop_cap(void); +int get_time(unsigned int *time, const char *str); +int get_time64(__s64 *time, const char *str); +void print_time(char *buf, int len, __u32 time); +void print_time64(char *buf, int len, __s64 time); +char *sprint_time(__u32 time, char *buf); +char *sprint_time64(__s64 time, char *buf); + #endif /* __UTILS_H__ */ diff --git a/lib/utils.c b/lib/utils.c index 02ce6772..802075fc 100644 --- a/lib/utils.c +++ b/lib/utils.c @@ -1633,3 +1633,104 @@ void drop_cap(void) } #endif } + +int get_time(unsigned int *time, const char *str) +{ + double t; + char *p; + + t = strtod(str, &p); + if (p == str) + return -1; + + if (*p) { + if (strcasecmp(p, "s") == 0 || strcasecmp(p, "sec") == 0 || + strcasecmp(p, "secs") == 0) + t *= TIME_UNITS_PER_SEC; + else if (strcasecmp(p, "ms") == 0 || strcasecmp(p, "msec") == 0 || + strcasecmp(p, "msecs") == 0) + t *= TIME_UNITS_PER_SEC/1000; + else if (strcasecmp(p, "us") == 0 || strcasecmp(p, "usec") == 0 || + strcasecmp(p, "usecs") == 0) + t *= TIME_UNITS_PER_SEC/1000000; + else + return -1; + } + + *time = t; + return 0; +} + + +void print_time(char *buf, int len, __u32 time) +{ + double tmp = time; + + if (tmp >= TIME_UNITS_PER_SEC) + snprintf(buf, len, "%.1fs", tmp/TIME_UNITS_PER_SEC); + else if (tmp >= TIME_UNITS_PER_SEC/1000) + snprintf(buf, len, "%.1fms", tmp/(TIME_UNITS_PER_SEC/1000)); + else + snprintf(buf, len, "%uus", time); +} + +char *sprint_time(__u32 time, char *buf) +{ + print_time(buf, SPRINT_BSIZE-1, time); + return buf; +} + +/* 64 bit times are represented internally in nanoseconds */ +int get_time64(__s64 *time, const char *str) +{ + double nsec; + char *p; + + nsec = strtod(str, &p); + if (p == str) + return -1; + + if (*p) { + if (strcasecmp(p, "s") == 0 || + strcasecmp(p, "sec") == 0 || + strcasecmp(p, "secs") == 0) + nsec *= NSEC_PER_SEC; + else if (strcasecmp(p, "ms") == 0 || + strcasecmp(p, "msec") == 0 || + strcasecmp(p, "msecs") == 0) + nsec *= NSEC_PER_MSEC; + else if (strcasecmp(p, "us") == 0 || + strcasecmp(p, "usec") == 0 || + strcasecmp(p, "usecs") == 0) + nsec *= NSEC_PER_USEC; + else if (strcasecmp(p, "ns") == 0 || + strcasecmp(p, "nsec") == 0 || + strcasecmp(p, "nsecs") == 0) + nsec *= 1; + else + return -1; + } + + *time = nsec; + return 0; +} + +void print_time64(char *buf, int len, __s64 time) +{ + double nsec = time; + + if (time >= NSEC_PER_SEC) + snprintf(buf, len, "%.3fs", nsec/NSEC_PER_SEC); + else if (time >= NSEC_PER_MSEC) + snprintf(buf, len, "%.3fms", nsec/NSEC_PER_MSEC); + else if (time >= NSEC_PER_USEC) + snprintf(buf, len, "%.3fus", nsec/NSEC_PER_USEC); + else + snprintf(buf, len, "%lldns", time); +} + +char *sprint_time64(__s64 time, char *buf) +{ + print_time64(buf, SPRINT_BSIZE-1, time); + return buf; +} diff --git a/man/man8/tc-netem.8 b/man/man8/tc-netem.8 index f2cd86b6..111109cf 100644 --- a/man/man8/tc-netem.8 +++ b/man/man8/tc-netem.8 @@ -8,7 +8,8 @@ NetEm \- Network Emulator .I OPTIONS .IR OPTIONS " := [ " LIMIT " ] [ " DELAY " ] [ " LOSS \ -" ] [ " CORRUPT " ] [ " DUPLICATION " ] [ " REORDERING " ][ " RATE " ]" +" ] [ " CORRUPT " ] [ " DUPLICATION " ] [ " REORDERING " ] [ " RATE \ +" ] [ " SLOT " ]" .IR LIMIT " := " .B limit @@ -51,6 +52,18 @@ NetEm \- Network Emulator .B rate .IR RATE " [ " PACKETOVERHEAD " [ " CELLSIZE " [ " CELLOVERHEAD " ]]]]" +.IR SLOT " := " +.BR slot " { " +.IR MIN_DELAY " [ " MAX_DELAY " ] |" +.br +.RB " " distribution " { "uniform " | " normal " | " pareto " | " paretonormal " | " +.IR FILE " } " DELAY " " JITTER " } " +.br +.RB " [ " packets +.IR PACKETS " ] [ " +.BR bytes +.IR BYTES " ]" + .SH DESCRIPTION NetEm is an enhancement of the Linux traffic control facilities @@ -162,6 +175,31 @@ granularity avoid a perfect shaping at a specific level. This will show up in an artificial packet compression (bursts). Another influence factor are network adapter buffers which can also add artificial delay. +.SS slot +defer delivering accumulated packets to within a slot. Each available slot can be +configured with a minimum delay to acquire, and an optional maximum delay. +Alternatively it can be configured with the distribution similar to +.BR distribution +for +.BR delay +option. Slot delays can be specified in nanoseconds, microseconds, milliseconds or seconds +(e.g. 800us). Values for the optional parameters +.I BYTES +will limit the number of bytes delivered per slot, and/or +.I PACKETS +will limit the number of packets delivered per slot. + +These slot options can provide a crude approximation of bursty MACs such as +DOCSIS, WiFi, and LTE. + +Note that slotting is limited by several factors: the kernel clock granularity, +as with a rate, and attempts to deliver many packets within a slot will be +smeared by the timer resolution, and by the underlying native bandwidth also. + +It is possible to combine slotting with a rate, in which case complex behaviors +where either the rate, or the slot limits on bytes or packets per slot, govern +the actual delivered rate. + .SH LIMITATIONS The main known limitation of Netem are related to timer granularity, since Linux is not a real-time operating system. diff --git a/tc/q_netem.c b/tc/q_netem.c index 9f9a9b3d..6e0e8a8c 100644 --- a/tc/q_netem.c +++ b/tc/q_netem.c @@ -40,7 +40,12 @@ static void explain(void) " [ loss gemodel PERCENT [R [1-H [1-K]]]\n" \ " [ ecn ]\n" \ " [ reorder PRECENT [CORRELATION] [ gap DISTANCE ]]\n" \ -" [ rate RATE [PACKETOVERHEAD] [CELLSIZE] [CELLOVERHEAD]]\n"); +" [ rate RATE [PACKETOVERHEAD] [CELLSIZE] [CELLOVERHEAD]]\n" \ +" [ slot MIN_DELAY [MAX_DELAY] [packets MAX_PACKETS]" \ +" [bytes MAX_BYTES]]\n" \ +" [ slot distribution" \ +" {uniform|normal|pareto|paretonormal|custom} DELAY JITTER" \ +" [packets MAX_PACKETS] [bytes MAX_BYTES]]\n"); } static void explain1(const char *arg) @@ -156,6 +161,7 @@ static int netem_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, const char *dev) { int dist_size = 0; + int slot_dist_size = 0; struct rtattr *tail; struct tc_netem_qopt opt = { .limit = 1000 }; struct tc_netem_corr cor = {}; @@ -164,7 +170,9 @@ static int netem_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct tc_netem_gimodel gimodel; struct tc_netem_gemodel gemodel; struct tc_netem_rate rate = {}; + struct tc_netem_slot slot = {}; __s16 *dist_data = NULL; + __s16 *slot_dist_data = NULL; __u16 loss_type = NETEM_LOSS_UNSPEC; int present[__TCA_NETEM_MAX] = {}; __u64 rate64 = 0; @@ -412,6 +420,79 @@ static int netem_parse_opt(struct qdisc_util *qu, int argc, char **argv, return -1; } } + } else if (matches(*argv, "slot") == 0) { + if (NEXT_IS_NUMBER()) { + NEXT_ARG(); + present[TCA_NETEM_SLOT] = 1; + if (get_time64(&slot.min_delay, *argv)) { + explain1("slot min_delay"); + return -1; + } + if (NEXT_IS_NUMBER()) { + NEXT_ARG(); + if (get_time64(&slot.max_delay, *argv) || + slot.max_delay < slot.min_delay) { + explain1("slot max_delay"); + return -1; + } + } else { + slot.max_delay = slot.min_delay; + } + } else { + NEXT_ARG(); + if (strcmp(*argv, "distribution") == 0) { + present[TCA_NETEM_SLOT] = 1; + NEXT_ARG(); + slot_dist_data = calloc(sizeof(slot_dist_data[0]), MAX_DIST); + if (!slot_dist_data) + return -1; + slot_dist_size = get_distribution(*argv, slot_dist_data, MAX_DIST); + if (slot_dist_size <= 0) { + free(slot_dist_data); + return -1; + } + NEXT_ARG(); + if (get_time64(&slot.dist_delay, *argv)) { + explain1("slot delay"); + return -1; + } + NEXT_ARG(); + if (get_time64(&slot.dist_jitter, *argv)) { + explain1("slot jitter"); + return -1; + } + if (slot.dist_jitter <= 0) { + fprintf(stderr, "Non-positive jitter\n"); + return -1; + } + } else { + fprintf(stderr, "Unknown slot parameter: %s\n", + *argv); + return -1; + } + } + if (NEXT_ARG_OK() && + matches(*(argv+1), "packets") == 0) { + NEXT_ARG(); + if (!NEXT_ARG_OK() || + get_s32(&slot.max_packets, *(argv+1), 0)) { + explain1("slot packets"); + return -1; + } + NEXT_ARG(); + } + if (NEXT_ARG_OK() && + matches(*(argv+1), "bytes") == 0) { + unsigned int max_bytes; + NEXT_ARG(); + if (!NEXT_ARG_OK() || + get_size(&max_bytes, *(argv+1))) { + explain1("slot bytes"); + return -1; + } + slot.max_bytes = (int) max_bytes; + NEXT_ARG(); + } } else if (strcmp(*argv, "help") == 0) { explain(); return -1; @@ -472,6 +553,10 @@ static int netem_parse_opt(struct qdisc_util *qu, int argc, char **argv, addattr_l(n, 1024, TCA_NETEM_CORRUPT, &corrupt, sizeof(corrupt)) < 0) return -1; + if (present[TCA_NETEM_SLOT] && + addattr_l(n, 1024, TCA_NETEM_SLOT, &slot, sizeof(slot)) < 0) + return -1; + if (loss_type != NETEM_LOSS_UNSPEC) { struct rtattr *start; @@ -512,6 +597,14 @@ static int netem_parse_opt(struct qdisc_util *qu, int argc, char **argv, return -1; free(dist_data); } + + if (slot_dist_data) { + if (addattr_l(n, MAX_DIST * sizeof(slot_dist_data[0]), + TCA_NETEM_SLOT_DIST, + slot_dist_data, slot_dist_size * sizeof(slot_dist_data[0])) < 0) + return -1; + free(slot_dist_data); + } tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail; return 0; } @@ -526,6 +619,7 @@ static int netem_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) int *ecn = NULL; struct tc_netem_qopt qopt; const struct tc_netem_rate *rate = NULL; + const struct tc_netem_slot *slot = NULL; int len; __u64 rate64 = 0; @@ -586,6 +680,11 @@ static int netem_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) return -1; rate64 = rta_getattr_u64(tb[TCA_NETEM_RATE64]); } + if (tb[TCA_NETEM_SLOT]) { + if (RTA_PAYLOAD(tb[TCA_NETEM_SLOT]) < sizeof(*slot)) + return -1; + slot = RTA_DATA(tb[TCA_NETEM_SLOT]); + } } fprintf(f, "limit %d", qopt.limit); @@ -659,6 +758,20 @@ static int netem_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) fprintf(f, " celloverhead %d", rate->cell_overhead); } + if (slot) { + if (slot->dist_jitter > 0) { + fprintf(f, " slot distribution %s", sprint_time64(slot->dist_delay, b1)); + fprintf(f, " %s", sprint_time64(slot->dist_jitter, b1)); + } else { + fprintf(f, " slot %s", sprint_time64(slot->min_delay, b1)); + fprintf(f, " %s", sprint_time64(slot->max_delay, b1)); + } + if (slot->max_packets) + fprintf(f, " packets %d", slot->max_packets); + if (slot->max_bytes) + fprintf(f, " bytes %d", slot->max_bytes); + } + if (ecn) fprintf(f, " ecn "); diff --git a/tc/tc_cbq.c b/tc/tc_cbq.c index 4cd584a9..c811456b 100644 --- a/tc/tc_cbq.c +++ b/tc/tc_cbq.c @@ -20,6 +20,7 @@ #include #include +#include "utils.h" #include "tc_core.h" #include "tc_cbq.h" diff --git a/tc/tc_core.c b/tc/tc_core.c index 1bde4d51..8eb11223 100644 --- a/tc/tc_core.c +++ b/tc/tc_core.c @@ -21,6 +21,7 @@ #include #include +#include "utils.h" #include "tc_core.h" #include diff --git a/tc/tc_core.h b/tc/tc_core.h index 1dfa9a4f..bd4a99f0 100644 --- a/tc/tc_core.h +++ b/tc/tc_core.h @@ -5,8 +5,6 @@ #include #include -#define TIME_UNITS_PER_SEC 1000000 - enum link_layer { LINKLAYER_UNSPEC, LINKLAYER_ETHERNET, diff --git a/tc/tc_estimator.c b/tc/tc_estimator.c index e4edfc7e..f494b7ca 100644 --- a/tc/tc_estimator.c +++ b/tc/tc_estimator.c @@ -20,6 +20,7 @@ #include #include +#include "utils.h" #include "tc_core.h" int tc_setup_estimator(unsigned int A, unsigned int time_const, struct tc_estimator *est) diff --git a/tc/tc_util.c b/tc/tc_util.c index d7578528..cafbe49f 100644 --- a/tc/tc_util.c +++ b/tc/tc_util.c @@ -334,52 +334,6 @@ char *sprint_rate(__u64 rate, char *buf) return buf; } -int get_time(unsigned int *time, const char *str) -{ - double t; - char *p; - - t = strtod(str, &p); - if (p == str) - return -1; - - if (*p) { - if (strcasecmp(p, "s") == 0 || strcasecmp(p, "sec") == 0 || - strcasecmp(p, "secs") == 0) - t *= TIME_UNITS_PER_SEC; - else if (strcasecmp(p, "ms") == 0 || strcasecmp(p, "msec") == 0 || - strcasecmp(p, "msecs") == 0) - t *= TIME_UNITS_PER_SEC/1000; - else if (strcasecmp(p, "us") == 0 || strcasecmp(p, "usec") == 0 || - strcasecmp(p, "usecs") == 0) - t *= TIME_UNITS_PER_SEC/1000000; - else - return -1; - } - - *time = t; - return 0; -} - - -void print_time(char *buf, int len, __u32 time) -{ - double tmp = time; - - if (tmp >= TIME_UNITS_PER_SEC) - snprintf(buf, len, "%.1fs", tmp/TIME_UNITS_PER_SEC); - else if (tmp >= TIME_UNITS_PER_SEC/1000) - snprintf(buf, len, "%.1fms", tmp/(TIME_UNITS_PER_SEC/1000)); - else - snprintf(buf, len, "%uus", time); -} - -char *sprint_time(__u32 time, char *buf) -{ - print_time(buf, SPRINT_BSIZE-1, time); - return buf; -} - char *sprint_ticks(__u32 ticks, char *buf) { return sprint_time(tc_core_tick2time(ticks), buf); diff --git a/tc/tc_util.h b/tc/tc_util.h index 6632c4f9..76fd986d 100644 --- a/tc/tc_util.h +++ b/tc/tc_util.h @@ -81,13 +81,11 @@ int get_rate64(__u64 *rate, const char *str); int get_percent_rate64(__u64 *rate, const char *str, const char *dev); int get_size(unsigned int *size, const char *str); int get_size_and_cell(unsigned int *size, int *cell_log, char *str); -int get_time(unsigned int *time, const char *str); int get_linklayer(unsigned int *val, const char *arg); void print_rate(char *buf, int len, __u64 rate); void print_size(char *buf, int len, __u32 size); void print_qdisc_handle(char *buf, int len, __u32 h); -void print_time(char *buf, int len, __u32 time); void print_linklayer(char *buf, int len, unsigned int linklayer); void print_devname(enum output_type type, int ifindex); @@ -95,7 +93,6 @@ char *sprint_rate(__u64 rate, char *buf); char *sprint_size(__u32 size, char *buf); char *sprint_qdisc_handle(__u32 h, char *buf); char *sprint_tc_classid(__u32 h, char *buf); -char *sprint_time(__u32 time, char *buf); char *sprint_ticks(__u32 ticks, char *buf); char *sprint_linklayer(unsigned int linklayer, char *buf);