mirror of
https://git.proxmox.com/git/mirror_iproute2
synced 2025-10-29 02:23:03 +00:00
Merge branch 'tc-qevents' into next
Petr Machata says: ==================== To allow configuring user-defined actions as a result of inner workings of a qdisc, a concept of qevents was recently introduced to the kernel. Qevents are attach points for TC blocks, where filters can be put that are executed as the packet hits well-defined points in the qdisc algorithms. The attached blocks can be shared, in a manner similar to clsact ingress and egress blocks, arbitrary classifiers with arbitrary actions can be put on them, etc. For example: # tc qdisc add dev eth0 root handle 1: \ red limit 500K avpkt 1K qevent early_drop block 10 # tc filter add block 10 \ matchall action mirred egress mirror dev eth1 This patch set introduces the corresponding iproute2 support. Patch #1 adds the new netlink attribute enumerators. Patch #2 adds a set of helpers to implement qevents, and #3 adds a generic documentation to tc.8. Patch #4 then adds two new qevents to the RED qdisc: mark and early_drop. ==================== Signed-off-by: David Ahern <dsahern@kernel.org>
This commit is contained in:
commit
79ea01927c
@ -17,7 +17,11 @@ packets
|
|||||||
rate
|
rate
|
||||||
.B ] [ probability
|
.B ] [ probability
|
||||||
chance
|
chance
|
||||||
.B ] [ adaptive ]
|
.B ] [ adaptive ] [ qevent early_drop block
|
||||||
|
index
|
||||||
|
.B ] [ qevent mark block
|
||||||
|
index
|
||||||
|
.B ]
|
||||||
|
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Random Early Detection is a classless qdisc which manages its queue size
|
Random Early Detection is a classless qdisc which manages its queue size
|
||||||
@ -134,6 +138,18 @@ Goal of Adaptive RED is to make 'probability' dynamic value between 1% and 50% t
|
|||||||
.B (max - min) / 2
|
.B (max - min) / 2
|
||||||
.fi
|
.fi
|
||||||
|
|
||||||
|
.SH QEVENTS
|
||||||
|
See tc (8) for some general notes about qevents. The RED qdisc supports the
|
||||||
|
following qevents:
|
||||||
|
|
||||||
|
.TP
|
||||||
|
early_drop
|
||||||
|
The associated block is executed when packets are early-dropped. This includes
|
||||||
|
non-ECT packets in ECN mode.
|
||||||
|
.TP
|
||||||
|
mark
|
||||||
|
The associated block is executed when packets are marked in ECN mode.
|
||||||
|
|
||||||
.SH EXAMPLE
|
.SH EXAMPLE
|
||||||
|
|
||||||
.P
|
.P
|
||||||
|
|||||||
@ -254,6 +254,25 @@ Traffic control filter that matches every packet. See
|
|||||||
.BR tc-matchall (8)
|
.BR tc-matchall (8)
|
||||||
for details.
|
for details.
|
||||||
|
|
||||||
|
.SH QEVENTS
|
||||||
|
Qdiscs may invoke user-configured actions when certain interesting events
|
||||||
|
take place in the qdisc. Each qevent can either be unused, or can have a
|
||||||
|
block attached to it. To this block are then attached filters using the "tc
|
||||||
|
block BLOCK_IDX" syntax. The block is executed when the qevent associated
|
||||||
|
with the attachment point takes place. For example, packet could be
|
||||||
|
dropped, or delayed, etc., depending on the qdisc and the qevent in
|
||||||
|
question.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
.PP
|
||||||
|
.RS
|
||||||
|
tc qdisc add dev eth0 root handle 1: red limit 500K avpkt 1K \\
|
||||||
|
qevent early_drop block 10
|
||||||
|
.RE
|
||||||
|
.RS
|
||||||
|
tc filter add block 10 matchall action mirred egress mirror dev eth1
|
||||||
|
.RE
|
||||||
|
|
||||||
.SH CLASSLESS QDISCS
|
.SH CLASSLESS QDISCS
|
||||||
The classless qdiscs are:
|
The classless qdiscs are:
|
||||||
.TP
|
.TP
|
||||||
|
|||||||
@ -122,6 +122,7 @@ TCLIB += tc_red.o
|
|||||||
TCLIB += tc_cbq.o
|
TCLIB += tc_cbq.o
|
||||||
TCLIB += tc_estimator.o
|
TCLIB += tc_estimator.o
|
||||||
TCLIB += tc_stab.o
|
TCLIB += tc_stab.o
|
||||||
|
TCLIB += tc_qevent.o
|
||||||
|
|
||||||
CFLAGS += -DCONFIG_GACT -DCONFIG_GACT_PROB
|
CFLAGS += -DCONFIG_GACT -DCONFIG_GACT_PROB
|
||||||
ifneq ($(IPT_LIB_DIR),)
|
ifneq ($(IPT_LIB_DIR),)
|
||||||
|
|||||||
30
tc/q_red.c
30
tc/q_red.c
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "tc_util.h"
|
#include "tc_util.h"
|
||||||
|
#include "tc_qevent.h"
|
||||||
|
|
||||||
#include "tc_red.h"
|
#include "tc_red.h"
|
||||||
|
|
||||||
@ -30,11 +31,20 @@ static void explain(void)
|
|||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Usage: ... red limit BYTES [min BYTES] [max BYTES] avpkt BYTES [burst PACKETS]\n"
|
"Usage: ... red limit BYTES [min BYTES] [max BYTES] avpkt BYTES [burst PACKETS]\n"
|
||||||
" [adaptive] [probability PROBABILITY] [bandwidth KBPS]\n"
|
" [adaptive] [probability PROBABILITY] [bandwidth KBPS]\n"
|
||||||
" [ecn] [harddrop] [nodrop]\n");
|
" [ecn] [harddrop] [nodrop]\n"
|
||||||
|
" [qevent early_drop block IDX] [qevent mark block IDX]\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
#define RED_SUPPORTED_FLAGS (TC_RED_HISTORIC_FLAGS | TC_RED_NODROP)
|
#define RED_SUPPORTED_FLAGS (TC_RED_HISTORIC_FLAGS | TC_RED_NODROP)
|
||||||
|
|
||||||
|
static struct qevent_plain qe_early_drop = {};
|
||||||
|
static struct qevent_plain qe_mark = {};
|
||||||
|
static struct qevent_util qevents[] = {
|
||||||
|
QEVENT("early_drop", plain, &qe_early_drop, TCA_RED_EARLY_DROP_BLOCK),
|
||||||
|
QEVENT("mark", plain, &qe_mark, TCA_RED_MARK_BLOCK),
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
|
||||||
static int red_parse_opt(struct qdisc_util *qu, int argc, char **argv,
|
static int red_parse_opt(struct qdisc_util *qu, int argc, char **argv,
|
||||||
struct nlmsghdr *n, const char *dev)
|
struct nlmsghdr *n, const char *dev)
|
||||||
{
|
{
|
||||||
@ -51,6 +61,8 @@ static int red_parse_opt(struct qdisc_util *qu, int argc, char **argv,
|
|||||||
__u32 max_P;
|
__u32 max_P;
|
||||||
struct rtattr *tail;
|
struct rtattr *tail;
|
||||||
|
|
||||||
|
qevents_init(qevents);
|
||||||
|
|
||||||
while (argc > 0) {
|
while (argc > 0) {
|
||||||
if (strcmp(*argv, "limit") == 0) {
|
if (strcmp(*argv, "limit") == 0) {
|
||||||
NEXT_ARG();
|
NEXT_ARG();
|
||||||
@ -109,6 +121,11 @@ static int red_parse_opt(struct qdisc_util *qu, int argc, char **argv,
|
|||||||
flags_bf.value |= TC_RED_ADAPTATIVE;
|
flags_bf.value |= TC_RED_ADAPTATIVE;
|
||||||
} else if (strcmp(*argv, "adaptive") == 0) {
|
} else if (strcmp(*argv, "adaptive") == 0) {
|
||||||
flags_bf.value |= TC_RED_ADAPTATIVE;
|
flags_bf.value |= TC_RED_ADAPTATIVE;
|
||||||
|
} else if (matches(*argv, "qevent") == 0) {
|
||||||
|
NEXT_ARG();
|
||||||
|
if (qevent_parse(qevents, &argc, &argv))
|
||||||
|
return -1;
|
||||||
|
continue;
|
||||||
} else if (strcmp(*argv, "help") == 0) {
|
} else if (strcmp(*argv, "help") == 0) {
|
||||||
explain();
|
explain();
|
||||||
return -1;
|
return -1;
|
||||||
@ -162,6 +179,8 @@ static int red_parse_opt(struct qdisc_util *qu, int argc, char **argv,
|
|||||||
max_P = probability * pow(2, 32);
|
max_P = probability * pow(2, 32);
|
||||||
addattr_l(n, 1024, TCA_RED_MAX_P, &max_P, sizeof(max_P));
|
addattr_l(n, 1024, TCA_RED_MAX_P, &max_P, sizeof(max_P));
|
||||||
addattr_l(n, 1024, TCA_RED_FLAGS, &flags_bf, sizeof(flags_bf));
|
addattr_l(n, 1024, TCA_RED_FLAGS, &flags_bf, sizeof(flags_bf));
|
||||||
|
if (qevents_dump(qevents, n))
|
||||||
|
return -1;
|
||||||
addattr_nest_end(n, tail);
|
addattr_nest_end(n, tail);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -203,12 +222,12 @@ static int red_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
|
|||||||
print_uint(PRINT_JSON, "min", NULL, qopt->qth_min);
|
print_uint(PRINT_JSON, "min", NULL, qopt->qth_min);
|
||||||
print_string(PRINT_FP, NULL, "min %s ", sprint_size(qopt->qth_min, b2));
|
print_string(PRINT_FP, NULL, "min %s ", sprint_size(qopt->qth_min, b2));
|
||||||
print_uint(PRINT_JSON, "max", NULL, qopt->qth_max);
|
print_uint(PRINT_JSON, "max", NULL, qopt->qth_max);
|
||||||
print_string(PRINT_FP, NULL, "max %s ", sprint_size(qopt->qth_max, b3));
|
print_string(PRINT_FP, NULL, "max %s", sprint_size(qopt->qth_max, b3));
|
||||||
|
|
||||||
tc_red_print_flags(qopt->flags);
|
tc_red_print_flags(qopt->flags);
|
||||||
|
|
||||||
if (show_details) {
|
if (show_details) {
|
||||||
print_uint(PRINT_ANY, "ewma", "ewma %u ", qopt->Wlog);
|
print_uint(PRINT_ANY, "ewma", " ewma %u ", qopt->Wlog);
|
||||||
if (max_P)
|
if (max_P)
|
||||||
print_float(PRINT_ANY, "probability",
|
print_float(PRINT_ANY, "probability",
|
||||||
"probability %lg ", max_P / pow(2, 32));
|
"probability %lg ", max_P / pow(2, 32));
|
||||||
@ -217,6 +236,11 @@ static int red_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
|
|||||||
print_uint(PRINT_ANY, "Scell_log", "Scell_log %u",
|
print_uint(PRINT_ANY, "Scell_log", "Scell_log %u",
|
||||||
qopt->Scell_log);
|
qopt->Scell_log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qevents_init(qevents);
|
||||||
|
if (qevents_read(qevents, tb))
|
||||||
|
return -1;
|
||||||
|
qevents_print(qevents, f);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
202
tc/tc_qevent.c
Normal file
202
tc/tc_qevent.c
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helpers for handling qevents.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "tc_qevent.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
void qevents_init(struct qevent_util *qevents)
|
||||||
|
{
|
||||||
|
if (!qevents)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (; qevents->id; qevents++)
|
||||||
|
memset(qevents->data, 0, qevents->data_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int qevent_parse(struct qevent_util *qevents, int *p_argc, char ***p_argv)
|
||||||
|
{
|
||||||
|
char **argv = *p_argv;
|
||||||
|
int argc = *p_argc;
|
||||||
|
const char *name = *argv;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (!qevents)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
for (; qevents->id; qevents++) {
|
||||||
|
if (strcmp(name, qevents->id) == 0) {
|
||||||
|
NEXT_ARG();
|
||||||
|
err = qevents->parse_qevent(qevents, &argc, &argv);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
*p_argc = argc;
|
||||||
|
*p_argv = argv;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
fprintf(stderr, "Unknown qevent `%s'\n", name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int qevents_read(struct qevent_util *qevents, struct rtattr **tb)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (!qevents)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (; qevents->id; qevents++) {
|
||||||
|
if (tb[qevents->attr]) {
|
||||||
|
err = qevents->read_qevent(qevents, tb);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void qevents_print(struct qevent_util *qevents, FILE *f)
|
||||||
|
{
|
||||||
|
int first = true;
|
||||||
|
|
||||||
|
if (!qevents)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (; qevents->id; qevents++) {
|
||||||
|
struct qevent_base *qeb = qevents->data;
|
||||||
|
|
||||||
|
if (qeb->block_idx) {
|
||||||
|
if (first) {
|
||||||
|
open_json_array(PRINT_JSON, "qevents");
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
open_json_object(NULL);
|
||||||
|
print_string(PRINT_ANY, "kind", " qevent %s", qevents->id);
|
||||||
|
qevents->print_qevent(qevents, f);
|
||||||
|
close_json_object();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!first)
|
||||||
|
close_json_array(PRINT_ANY, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
int qevents_dump(struct qevent_util *qevents, struct nlmsghdr *n)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (!qevents)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (; qevents->id; qevents++) {
|
||||||
|
struct qevent_base *qeb = qevents->data;
|
||||||
|
|
||||||
|
if (qeb->block_idx) {
|
||||||
|
err = qevents->dump_qevent(qevents, n);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_block_idx(const char *arg, struct qevent_base *qeb)
|
||||||
|
{
|
||||||
|
if (qeb->block_idx) {
|
||||||
|
fprintf(stderr, "Qevent block index already specified\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get_unsigned(&qeb->block_idx, arg, 10) || !qeb->block_idx) {
|
||||||
|
fprintf(stderr, "Illegal qevent block index\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int read_block_idx(struct rtattr *attr, struct qevent_base *qeb)
|
||||||
|
{
|
||||||
|
if (qeb->block_idx) {
|
||||||
|
fprintf(stderr, "Qevent block index already specified\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
qeb->block_idx = rta_getattr_u32(attr);
|
||||||
|
if (!qeb->block_idx) {
|
||||||
|
fprintf(stderr, "Illegal qevent block index\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_block_idx(FILE *f, __u32 block_idx)
|
||||||
|
{
|
||||||
|
print_uint(PRINT_ANY, "block", " block %u", block_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int qevent_parse_plain(struct qevent_util *qu, int *p_argc, char ***p_argv)
|
||||||
|
{
|
||||||
|
struct qevent_plain *qe = qu->data;
|
||||||
|
char **argv = *p_argv;
|
||||||
|
int argc = *p_argc;
|
||||||
|
|
||||||
|
if (qe->base.block_idx) {
|
||||||
|
fprintf(stderr, "Duplicate qevent\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (argc > 0) {
|
||||||
|
if (strcmp(*argv, "block") == 0) {
|
||||||
|
NEXT_ARG();
|
||||||
|
if (parse_block_idx(*argv, &qe->base))
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
NEXT_ARG_FWD();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!qe->base.block_idx) {
|
||||||
|
fprintf(stderr, "Unspecified qevent block index\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*p_argc = argc;
|
||||||
|
*p_argv = argv;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int qevent_read_plain(struct qevent_util *qu, struct rtattr **tb)
|
||||||
|
{
|
||||||
|
struct qevent_plain *qe = qu->data;
|
||||||
|
|
||||||
|
return read_block_idx(tb[qu->attr], &qe->base);
|
||||||
|
}
|
||||||
|
|
||||||
|
void qevent_print_plain(struct qevent_util *qu, FILE *f)
|
||||||
|
{
|
||||||
|
struct qevent_plain *qe = qu->data;
|
||||||
|
|
||||||
|
print_block_idx(f, qe->base.block_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int qevent_dump_plain(struct qevent_util *qu, struct nlmsghdr *n)
|
||||||
|
{
|
||||||
|
struct qevent_plain *qe = qu->data;
|
||||||
|
|
||||||
|
return addattr32(n, 1024, qu->attr, qe->base.block_idx);
|
||||||
|
}
|
||||||
49
tc/tc_qevent.h
Normal file
49
tc/tc_qevent.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
#ifndef _TC_QEVENT_H_
|
||||||
|
#define _TC_QEVENT_H_
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <libnetlink.h>
|
||||||
|
|
||||||
|
struct qevent_base {
|
||||||
|
__u32 block_idx;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct qevent_util {
|
||||||
|
const char *id;
|
||||||
|
int (*parse_qevent)(struct qevent_util *qu, int *argc, char ***argv);
|
||||||
|
int (*read_qevent)(struct qevent_util *qu, struct rtattr **tb);
|
||||||
|
void (*print_qevent)(struct qevent_util *qu, FILE *f);
|
||||||
|
int (*dump_qevent)(struct qevent_util *qu, struct nlmsghdr *n);
|
||||||
|
size_t data_size;
|
||||||
|
void *data;
|
||||||
|
int attr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define QEVENT(_name, _form, _data, _attr) \
|
||||||
|
{ \
|
||||||
|
.id = _name, \
|
||||||
|
.parse_qevent = qevent_parse_##_form, \
|
||||||
|
.read_qevent = qevent_read_##_form, \
|
||||||
|
.print_qevent = qevent_print_##_form, \
|
||||||
|
.dump_qevent = qevent_dump_##_form, \
|
||||||
|
.data_size = sizeof(struct qevent_##_form), \
|
||||||
|
.data = _data, \
|
||||||
|
.attr = _attr, \
|
||||||
|
}
|
||||||
|
|
||||||
|
void qevents_init(struct qevent_util *qevents);
|
||||||
|
int qevent_parse(struct qevent_util *qevents, int *p_argc, char ***p_argv);
|
||||||
|
int qevents_read(struct qevent_util *qevents, struct rtattr **tb);
|
||||||
|
int qevents_dump(struct qevent_util *qevents, struct nlmsghdr *n);
|
||||||
|
void qevents_print(struct qevent_util *qevents, FILE *f);
|
||||||
|
|
||||||
|
struct qevent_plain {
|
||||||
|
struct qevent_base base;
|
||||||
|
};
|
||||||
|
int qevent_parse_plain(struct qevent_util *qu, int *p_argc, char ***p_argv);
|
||||||
|
int qevent_read_plain(struct qevent_util *qu, struct rtattr **tb);
|
||||||
|
void qevent_print_plain(struct qevent_util *qu, FILE *f);
|
||||||
|
int qevent_dump_plain(struct qevent_util *qu, struct nlmsghdr *n);
|
||||||
|
|
||||||
|
#endif
|
||||||
Loading…
Reference in New Issue
Block a user