q_netem: support delivering packets in delayed time slots

Slotting is a crude approximation of the behaviors of shared media such
as cable, wifi, and LTE, which gather up a bunch of packets within a
varying delay window and deliver them, relative to that, nearly all at
once.

It works within the existing loss, duplication, jitter and delay
parameters of netem. Some amount of inherent latency must be specified,
regardless.

The new "slot" parameter specifies a minimum and maximum delay between
transmission attempts.

The "bytes" and "packets" parameters can be used to limit the amount of
information transferred per slot.

Examples of use:

tc qdisc add dev eth0 root netem delay 200us \
        slot 800us 10ms bytes 64k packets 42

A more correct example, using stacked netem instances and a packet limit
to emulate a tail drop wifi queue with slots and variable packet
delivery, with a 200Mbit isochronous underlying rate, and 20ms path
delay:

tc qdisc add dev eth0 root handle 1: netem delay 20ms rate 200mbit \
         limit 10000
tc qdisc add dev eth0 parent 1:1 handle 10:1 netem delay 200us \
         slot 800us 10ms bytes 64k packets 42 limit 512

Signed-off-by: Yousuk Seung <ysseung@google.com>
Signed-off-by: Dave Taht <dave.taht@gmail.com>
Signed-off-by: Neal Cardwell <ncardwell@google.com>
Signed-off-by: David Ahern <dsahern@gmail.com>
This commit is contained in:
Dave Taht 2018-08-26 19:42:29 -07:00 committed by David Ahern
parent abf70ef494
commit b6268fbd58
2 changed files with 94 additions and 2 deletions

View File

@ -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,14 @@ NetEm \- Network Emulator
.B rate
.IR RATE " [ " PACKETOVERHEAD " [ " CELLSIZE " [ " CELLOVERHEAD " ]]]]"
.IR SLOT " := "
.BR slot
.IR MIN_DELAY " [ " MAX_DELAY " ] ["
.BR packets
.IR PACKETS " ] [ "
.BR bytes
.IR BYTES " ]"
.SH DESCRIPTION
NetEm is an enhancement of the Linux traffic control facilities
@ -162,6 +171,27 @@ 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, with each available slot
configured with a minimum delay to acquire, and an optional maximum delay. 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.

View File

@ -40,7 +40,10 @@ 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" \
);
}
static void explain1(const char *arg)
@ -164,6 +167,7 @@ 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;
__u16 loss_type = NETEM_LOSS_UNSPEC;
int present[__TCA_NETEM_MAX] = {};
@ -412,6 +416,45 @@ static int netem_parse_opt(struct qdisc_util *qu, int argc, char **argv,
return -1;
}
}
} else if (matches(*argv, "slot") == 0) {
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;
}
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 +515,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;
@ -526,6 +573,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 +634,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 +712,15 @@ static int netem_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
fprintf(f, " celloverhead %d", rate->cell_overhead);
}
if (slot) {
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 ");