Merge branch 'netem-slot-param' into iproute2-next

Yousuk Seung  says:

====================

This series adds support for the new "slot" netem parameter for
slotting. Slotting is an approximation of shared media that gather up
packets within a varying delay window before delivering them nearly at
once.

====================

Signed-off-by: David Ahern <dsahern@gmail.com>
This commit is contained in:
David Ahern 2018-08-30 11:08:43 -07:00
commit b555ff737a
10 changed files with 269 additions and 53 deletions

View File

@ -46,6 +46,11 @@ void incomplete_command(void) __attribute__((noreturn));
#define NEXT_ARG_FWD() do { argv++; argc--; } while(0) #define NEXT_ARG_FWD() do { argv++; argc--; } while(0)
#define PREV_ARG() 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 typedef struct
{ {
__u16 flags; __u16 flags;
@ -310,4 +315,11 @@ size_t strlcat(char *dst, const char *src, size_t size);
void drop_cap(void); 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__ */ #endif /* __UTILS_H__ */

View File

@ -1633,3 +1633,104 @@ void drop_cap(void)
} }
#endif #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;
}

View File

@ -8,7 +8,8 @@ NetEm \- Network Emulator
.I OPTIONS .I OPTIONS
.IR OPTIONS " := [ " LIMIT " ] [ " DELAY " ] [ " LOSS \ .IR OPTIONS " := [ " LIMIT " ] [ " DELAY " ] [ " LOSS \
" ] [ " CORRUPT " ] [ " DUPLICATION " ] [ " REORDERING " ][ " RATE " ]" " ] [ " CORRUPT " ] [ " DUPLICATION " ] [ " REORDERING " ] [ " RATE \
" ] [ " SLOT " ]"
.IR LIMIT " := " .IR LIMIT " := "
.B limit .B limit
@ -51,6 +52,18 @@ NetEm \- Network Emulator
.B rate .B rate
.IR RATE " [ " PACKETOVERHEAD " [ " CELLSIZE " [ " CELLOVERHEAD " ]]]]" .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 .SH DESCRIPTION
NetEm is an enhancement of the Linux traffic control facilities 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 an artificial packet compression (bursts). Another influence factor are network
adapter buffers which can also add artificial delay. 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 .SH LIMITATIONS
The main known limitation of Netem are related to timer granularity, since The main known limitation of Netem are related to timer granularity, since
Linux is not a real-time operating system. Linux is not a real-time operating system.

View File

@ -40,7 +40,12 @@ static void explain(void)
" [ loss gemodel PERCENT [R [1-H [1-K]]]\n" \ " [ loss gemodel PERCENT [R [1-H [1-K]]]\n" \
" [ ecn ]\n" \ " [ ecn ]\n" \
" [ reorder PRECENT [CORRELATION] [ gap DISTANCE ]]\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) 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) struct nlmsghdr *n, const char *dev)
{ {
int dist_size = 0; int dist_size = 0;
int slot_dist_size = 0;
struct rtattr *tail; struct rtattr *tail;
struct tc_netem_qopt opt = { .limit = 1000 }; struct tc_netem_qopt opt = { .limit = 1000 };
struct tc_netem_corr cor = {}; 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_gimodel gimodel;
struct tc_netem_gemodel gemodel; struct tc_netem_gemodel gemodel;
struct tc_netem_rate rate = {}; struct tc_netem_rate rate = {};
struct tc_netem_slot slot = {};
__s16 *dist_data = NULL; __s16 *dist_data = NULL;
__s16 *slot_dist_data = NULL;
__u16 loss_type = NETEM_LOSS_UNSPEC; __u16 loss_type = NETEM_LOSS_UNSPEC;
int present[__TCA_NETEM_MAX] = {}; int present[__TCA_NETEM_MAX] = {};
__u64 rate64 = 0; __u64 rate64 = 0;
@ -412,6 +420,79 @@ static int netem_parse_opt(struct qdisc_util *qu, int argc, char **argv,
return -1; 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) { } else if (strcmp(*argv, "help") == 0) {
explain(); explain();
return -1; 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) addattr_l(n, 1024, TCA_NETEM_CORRUPT, &corrupt, sizeof(corrupt)) < 0)
return -1; 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) { if (loss_type != NETEM_LOSS_UNSPEC) {
struct rtattr *start; struct rtattr *start;
@ -512,6 +597,14 @@ static int netem_parse_opt(struct qdisc_util *qu, int argc, char **argv,
return -1; return -1;
free(dist_data); 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; tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
return 0; return 0;
} }
@ -526,6 +619,7 @@ static int netem_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
int *ecn = NULL; int *ecn = NULL;
struct tc_netem_qopt qopt; struct tc_netem_qopt qopt;
const struct tc_netem_rate *rate = NULL; const struct tc_netem_rate *rate = NULL;
const struct tc_netem_slot *slot = NULL;
int len; int len;
__u64 rate64 = 0; __u64 rate64 = 0;
@ -586,6 +680,11 @@ static int netem_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
return -1; return -1;
rate64 = rta_getattr_u64(tb[TCA_NETEM_RATE64]); 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); 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); 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) if (ecn)
fprintf(f, " ecn "); fprintf(f, " ecn ");

View File

@ -20,6 +20,7 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <string.h> #include <string.h>
#include "utils.h"
#include "tc_core.h" #include "tc_core.h"
#include "tc_cbq.h" #include "tc_cbq.h"

View File

@ -21,6 +21,7 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <string.h> #include <string.h>
#include "utils.h"
#include "tc_core.h" #include "tc_core.h"
#include <linux/atm.h> #include <linux/atm.h>

View File

@ -5,8 +5,6 @@
#include <asm/types.h> #include <asm/types.h>
#include <linux/pkt_sched.h> #include <linux/pkt_sched.h>
#define TIME_UNITS_PER_SEC 1000000
enum link_layer { enum link_layer {
LINKLAYER_UNSPEC, LINKLAYER_UNSPEC,
LINKLAYER_ETHERNET, LINKLAYER_ETHERNET,

View File

@ -20,6 +20,7 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <string.h> #include <string.h>
#include "utils.h"
#include "tc_core.h" #include "tc_core.h"
int tc_setup_estimator(unsigned int A, unsigned int time_const, struct tc_estimator *est) int tc_setup_estimator(unsigned int A, unsigned int time_const, struct tc_estimator *est)

View File

@ -334,52 +334,6 @@ char *sprint_rate(__u64 rate, char *buf)
return 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) char *sprint_ticks(__u32 ticks, char *buf)
{ {
return sprint_time(tc_core_tick2time(ticks), buf); return sprint_time(tc_core_tick2time(ticks), buf);

View File

@ -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_percent_rate64(__u64 *rate, const char *str, const char *dev);
int get_size(unsigned int *size, const char *str); int get_size(unsigned int *size, const char *str);
int get_size_and_cell(unsigned int *size, int *cell_log, 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); int get_linklayer(unsigned int *val, const char *arg);
void print_rate(char *buf, int len, __u64 rate); void print_rate(char *buf, int len, __u64 rate);
void print_size(char *buf, int len, __u32 size); void print_size(char *buf, int len, __u32 size);
void print_qdisc_handle(char *buf, int len, __u32 h); 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_linklayer(char *buf, int len, unsigned int linklayer);
void print_devname(enum output_type type, int ifindex); 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_size(__u32 size, char *buf);
char *sprint_qdisc_handle(__u32 h, char *buf); char *sprint_qdisc_handle(__u32 h, char *buf);
char *sprint_tc_classid(__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_ticks(__u32 ticks, char *buf);
char *sprint_linklayer(unsigned int linklayer, char *buf); char *sprint_linklayer(unsigned int linklayer, char *buf);