aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnita Zhang <the.anitazha@gmail.com>2019-12-04 16:14:11 -0800
committerGitHub <noreply@github.com>2019-12-04 16:14:11 -0800
commitda4dd97405eac3f692f7bd032983adc8b780c8b6 (patch)
tree5e3737cec47d457655a0f6fe6381cd9e7f489ca6
parentMerge pull request #14219 from poettering/homed-preparatory-loop (diff)
parenttest-network: add a test case for SFQ (diff)
downloadsystemd-da4dd97405eac3f692f7bd032983adc8b780c8b6.tar.xz
systemd-da4dd97405eac3f692f7bd032983adc8b780c8b6.zip
Merge pull request #14173 from ssahani/tc-sfq
network: tc: introduce sfq and tbf
-rw-r--r--man/systemd.network.xml34
-rw-r--r--src/libsystemd/sd-netlink/netlink-message.c1
-rw-r--r--src/network/meson.build4
-rw-r--r--src/network/networkd-link.c4
-rw-r--r--src/network/networkd-network-gperf.gperf16
-rw-r--r--src/network/networkd-network.c7
-rw-r--r--src/network/tc/netem.c34
-rw-r--r--src/network/tc/netem.h4
-rw-r--r--src/network/tc/qdisc.c79
-rw-r--r--src/network/tc/qdisc.h20
-rw-r--r--src/network/tc/sfq.c87
-rw-r--r--src/network/tc/sfq.h17
-rw-r--r--src/network/tc/tbf.c167
-rw-r--r--src/network/tc/tbf.h21
-rw-r--r--test/fuzz/fuzz-network-parser/directives.network4
-rw-r--r--test/test-network/conf/25-qdisc-netem.network (renamed from test/test-network/conf/25-qdisc.network)0
-rw-r--r--test/test-network/conf/25-qdisc-tbf-and-sfq.network16
-rwxr-xr-xtest/test-network/systemd-networkd-tests.py15
18 files changed, 478 insertions, 52 deletions
diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index a26e08c99cc..a2ac24059a9 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -2368,6 +2368,40 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>TokenBufferFilterLatencySec=</varname></term>
+ <listitem>
+ <para>Specifies the latency parameter, which specifies the maximum amount of time a
+ packet can sit in the Token Buffer Filter (TBF). Defaults to unset.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>TokenBufferFilterBurst=</varname></term>
+ <listitem>
+ <para>Specifies the size of the bucket. This is the maximum amount of bytes that tokens
+ can be available for instantaneous transfer. When the size is suffixed with K, M, or G, it is
+ parsed as Kilobytes, Megabytes, or Gigabytes, respectively, to the base of 1000. Defaults to
+ unset.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>TokenBufferFilterRate=</varname></term>
+ <listitem>
+ <para>Specifies the device specific bandwidth. When suffixed with K, M, or G, the specified
+ bandwidth is parsed as Kilobytes, Megabytes, or Gigabytes, respectively, to the base of 1000.
+ Defaults to unset.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>StochasticFairnessQueueingPerturbPeriodSec=</varname></term>
+ <listitem>
+ <para>Specifies the interval in seconds for queue algorithm perturbation. Defaults to unset.</para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/libsystemd/sd-netlink/netlink-message.c b/src/libsystemd/sd-netlink/netlink-message.c
index 34b66e6fa60..1569f34cc41 100644
--- a/src/libsystemd/sd-netlink/netlink-message.c
+++ b/src/libsystemd/sd-netlink/netlink-message.c
@@ -532,7 +532,6 @@ int sd_netlink_message_open_array(sd_netlink_message *m, uint16_t type) {
assert_return(m, -EINVAL);
assert_return(!m->sealed, -EPERM);
- assert_return(m->n_containers > 0, -EINVAL);
r = add_rtattr(m, type | NLA_F_NESTED, NULL, 0);
if (r < 0)
diff --git a/src/network/meson.build b/src/network/meson.build
index d502279151c..e2324a01b38 100644
--- a/src/network/meson.build
+++ b/src/network/meson.build
@@ -109,6 +109,10 @@ sources = files('''
tc/netem.h
tc/qdisc.c
tc/qdisc.h
+ tc/sfq.c
+ tc/sfq.h
+ tc/tbf.c
+ tc/tbf.h
tc/tc-util.c
tc/tc-util.h
'''.split())
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 36d24fd0f3d..990f62850a3 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -2585,7 +2585,7 @@ static int link_drop_config(Link *link) {
}
static int link_configure_qdiscs(Link *link) {
- QDiscs *qdisc;
+ QDisc *qdisc;
Iterator i;
int r;
@@ -2601,7 +2601,7 @@ static int link_configure_qdiscs(Link *link) {
if (link->qdisc_messages == 0)
link->qdiscs_configured = true;
else
- log_link_debug(link, "Configuring QDiscs");
+ log_link_debug(link, "Configuring queuing discipline (qdisc)");
return 0;
}
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index f314b1ec16e..1bfd76ec735 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -244,12 +244,16 @@ CAN.BitRate, config_parse_si_size,
CAN.SamplePoint, config_parse_permille, 0, offsetof(Network, can_sample_point)
CAN.RestartSec, config_parse_sec, 0, offsetof(Network, can_restart_us)
CAN.TripleSampling, config_parse_tristate, 0, offsetof(Network, can_triple_sampling)
-TrafficControlQueueingDiscipline.Parent, config_parse_tc_qdiscs_parent, 0, 0
-TrafficControlQueueingDiscipline.NetworkEmulatorDelaySec, config_parse_tc_network_emulator_delay, 0, 0
-TrafficControlQueueingDiscipline.NetworkEmulatorDelayJitterSec, config_parse_tc_network_emulator_delay, 0, 0
-TrafficControlQueueingDiscipline.NetworkEmulatorLossRate, config_parse_tc_network_emulator_rate, 0, 0
-TrafficControlQueueingDiscipline.NetworkEmulatorDuplicateRate, config_parse_tc_network_emulator_rate, 0, 0
-TrafficControlQueueingDiscipline.NetworkEmulatorPacketLimit, config_parse_tc_network_emulator_packet_limit, 0, 0
+TrafficControlQueueingDiscipline.Parent, config_parse_tc_qdiscs_parent, 0, 0
+TrafficControlQueueingDiscipline.NetworkEmulatorDelaySec, config_parse_tc_network_emulator_delay, 0, 0
+TrafficControlQueueingDiscipline.NetworkEmulatorDelayJitterSec, config_parse_tc_network_emulator_delay, 0, 0
+TrafficControlQueueingDiscipline.NetworkEmulatorLossRate, config_parse_tc_network_emulator_rate, 0, 0
+TrafficControlQueueingDiscipline.NetworkEmulatorDuplicateRate, config_parse_tc_network_emulator_rate, 0, 0
+TrafficControlQueueingDiscipline.NetworkEmulatorPacketLimit, config_parse_tc_network_emulator_packet_limit, 0, 0
+TrafficControlQueueingDiscipline.TokenBufferFilterRate, config_parse_tc_token_buffer_filter_size, 0, 0
+TrafficControlQueueingDiscipline.TokenBufferFilterBurst, config_parse_tc_token_buffer_filter_size, 0, 0
+TrafficControlQueueingDiscipline.TokenBufferFilterLatencySec, config_parse_tc_token_buffer_filter_latency, 0, 0
+TrafficControlQueueingDiscipline.StochasticFairnessQueueingPerturbPeriodSec, config_parse_tc_stochastic_fairness_queueing_perturb_period, 0, 0
/* backwards compatibility: do not add new entries to this section */
Network.IPv4LL, config_parse_ipv4ll, 0, offsetof(Network, link_local)
DHCP.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 6e443975f17..181afbb4cd1 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -154,6 +154,8 @@ int network_verify(Network *network) {
Prefix *prefix, *prefix_next;
Route *route, *route_next;
FdbEntry *fdb, *fdb_next;
+ QDisc *qdisc;
+ Iterator i;
assert(network);
assert(network->filename);
@@ -313,6 +315,11 @@ int network_verify(Network *network) {
if (routing_policy_rule_section_verify(rule) < 0)
routing_policy_rule_free(rule);
+ bool has_root = false, has_clsact = false;
+ ORDERED_HASHMAP_FOREACH(qdisc, network->qdiscs_by_section, i)
+ if (qdisc_section_verify(qdisc, &has_root, &has_clsact) < 0)
+ qdisc_free(qdisc);
+
return 0;
}
diff --git a/src/network/tc/netem.c b/src/network/tc/netem.c
index 053af3e7dbb..25a53150b07 100644
--- a/src/network/tc/netem.c
+++ b/src/network/tc/netem.c
@@ -2,12 +2,9 @@
* Copyright © 2019 VMware, Inc. */
#include <linux/pkt_sched.h>
-#include <math.h>
#include "alloc-util.h"
#include "conf-parser.h"
-#include "hashmap.h"
-#include "in-addr-util.h"
#include "netem.h"
#include "netlink-util.h"
#include "networkd-manager.h"
@@ -15,7 +12,6 @@
#include "qdisc.h"
#include "string-util.h"
#include "tc-util.h"
-#include "util.h"
int network_emulator_new(NetworkEmulator **ret) {
NetworkEmulator *ne = NULL;
@@ -34,33 +30,33 @@ int network_emulator_new(NetworkEmulator **ret) {
return 0;
}
-int network_emulator_fill_message(Link *link, QDiscs *qdisc, sd_netlink_message *req) {
+int network_emulator_fill_message(Link *link, const NetworkEmulator *ne, sd_netlink_message *req) {
struct tc_netem_qopt opt = {
.limit = 1000,
};
int r;
assert(link);
- assert(qdisc);
+ assert(ne);
assert(req);
- if (qdisc->ne.limit > 0)
- opt.limit = qdisc->ne.limit;
+ if (ne->limit > 0)
+ opt.limit = ne->limit;
- if (qdisc->ne.loss > 0)
- opt.loss = qdisc->ne.loss;
+ if (ne->loss > 0)
+ opt.loss = ne->loss;
- if (qdisc->ne.duplicate > 0)
- opt.duplicate = qdisc->ne.duplicate;
+ if (ne->duplicate > 0)
+ opt.duplicate = ne->duplicate;
- if (qdisc->ne.delay != USEC_INFINITY) {
- r = tc_time_to_tick(qdisc->ne.delay, &opt.latency);
+ if (ne->delay != USEC_INFINITY) {
+ r = tc_time_to_tick(ne->delay, &opt.latency);
if (r < 0)
return log_link_error_errno(link, r, "Failed to calculate latency in TCA_OPTION: %m");
}
- if (qdisc->ne.jitter != USEC_INFINITY) {
- r = tc_time_to_tick(qdisc->ne.jitter, &opt.jitter);
+ if (ne->jitter != USEC_INFINITY) {
+ r = tc_time_to_tick(ne->jitter, &opt.jitter);
if (r < 0)
return log_link_error_errno(link, r, "Failed to calculate jitter in TCA_OPTION: %m");
}
@@ -84,7 +80,7 @@ int config_parse_tc_network_emulator_delay(
void *data,
void *userdata) {
- _cleanup_(qdisc_free_or_set_invalidp) QDiscs *qdisc = NULL;
+ _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
Network *network = data;
usec_t u;
int r;
@@ -139,7 +135,7 @@ int config_parse_tc_network_emulator_rate(
void *data,
void *userdata) {
- _cleanup_(qdisc_free_or_set_invalidp) QDiscs *qdisc = NULL;
+ _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
Network *network = data;
uint32_t rate;
int r;
@@ -189,7 +185,7 @@ int config_parse_tc_network_emulator_packet_limit(
void *data,
void *userdata) {
- _cleanup_(qdisc_free_or_set_invalidp) QDiscs *qdisc = NULL;
+ _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
Network *network = data;
int r;
diff --git a/src/network/tc/netem.h b/src/network/tc/netem.h
index 43abf20af87..66c3476a6be 100644
--- a/src/network/tc/netem.h
+++ b/src/network/tc/netem.h
@@ -8,8 +8,6 @@
#include "networkd-link.h"
#include "time-util.h"
-typedef struct QDiscs QDiscs;
-
typedef struct NetworkEmulator {
usec_t delay;
usec_t jitter;
@@ -20,7 +18,7 @@ typedef struct NetworkEmulator {
} NetworkEmulator;
int network_emulator_new(NetworkEmulator **ret);
-int network_emulator_fill_message(Link *link, QDiscs *qdisc, sd_netlink_message *req);
+int network_emulator_fill_message(Link *link, const NetworkEmulator *ne, sd_netlink_message *req);
CONFIG_PARSER_PROTOTYPE(config_parse_tc_network_emulator_delay);
CONFIG_PARSER_PROTOTYPE(config_parse_tc_network_emulator_rate);
diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c
index ed9bd9167ae..05c0ebbc186 100644
--- a/src/network/tc/qdisc.c
+++ b/src/network/tc/qdisc.c
@@ -12,16 +12,15 @@
#include "qdisc.h"
#include "set.h"
#include "string-util.h"
-#include "util.h"
-static int qdisc_new(QDiscs **ret) {
- QDiscs *qdisc;
+static int qdisc_new(QDisc **ret) {
+ QDisc *qdisc;
- qdisc = new(QDiscs, 1);
+ qdisc = new(QDisc, 1);
if (!qdisc)
return -ENOMEM;
- *qdisc = (QDiscs) {
+ *qdisc = (QDisc) {
.family = AF_UNSPEC,
.parent = TC_H_ROOT,
};
@@ -31,9 +30,9 @@ static int qdisc_new(QDiscs **ret) {
return 0;
}
-int qdisc_new_static(Network *network, const char *filename, unsigned section_line, QDiscs **ret) {
+int qdisc_new_static(Network *network, const char *filename, unsigned section_line, QDisc **ret) {
_cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
- _cleanup_(qdisc_freep) QDiscs *qdisc = NULL;
+ _cleanup_(qdisc_freep) QDisc *qdisc = NULL;
int r;
assert(network);
@@ -76,7 +75,7 @@ int qdisc_new_static(Network *network, const char *filename, unsigned section_li
return 0;
}
-void qdisc_free(QDiscs *qdisc) {
+void qdisc_free(QDisc *qdisc) {
if (!qdisc)
return;
@@ -106,7 +105,7 @@ static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
}
if (link->route_messages == 0) {
- log_link_debug(link, "QDiscs configured");
+ log_link_debug(link, "QDisc configured");
link->qdiscs_configured = true;
link_check_ready(link);
}
@@ -114,7 +113,7 @@ static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
return 1;
}
-int qdisc_configure(Link *link, QDiscs *qdisc) {
+int qdisc_configure(Link *link, QDisc *qdisc) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
_cleanup_free_ char *tca_kind = NULL;
int r;
@@ -147,7 +146,27 @@ int qdisc_configure(Link *link, QDiscs *qdisc) {
if (r < 0)
return log_oom();
- r = network_emulator_fill_message(link, qdisc, req);
+ r = network_emulator_fill_message(link, &qdisc->ne, req);
+ if (r < 0)
+ return r;
+ }
+
+ if (qdisc->has_token_buffer_filter) {
+ r = free_and_strdup(&tca_kind, "tbf");
+ if (r < 0)
+ return log_oom();
+
+ r = token_buffer_filter_fill_message(link, &qdisc->tbf, req);
+ if (r < 0)
+ return r;
+ }
+
+ if (qdisc->has_stochastic_fairness_queueing) {
+ r = free_and_strdup(&tca_kind, "sfq");
+ if (r < 0)
+ return log_oom();
+
+ r = stochastic_fairness_queueing_fill_message(link, &qdisc->sfq, req);
if (r < 0)
return r;
}
@@ -168,6 +187,42 @@ int qdisc_configure(Link *link, QDiscs *qdisc) {
return 0;
}
+int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact) {
+ unsigned i;
+
+ assert(qdisc);
+ assert(has_root);
+ assert(has_clsact);
+
+ if (section_is_invalid(qdisc->section))
+ return -EINVAL;
+
+ i = qdisc->has_network_emulator + qdisc->has_token_buffer_filter + qdisc->has_stochastic_fairness_queueing;
+ if (i > 1)
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: TrafficControlQueueingDiscipline section has more than one type of discipline. "
+ "Ignoring [TrafficControlQueueingDiscipline] section from line %u.",
+ qdisc->section->filename, qdisc->section->line);
+
+ if (qdisc->parent == TC_H_ROOT) {
+ if (*has_root)
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: More than one root TrafficControlQueueingDiscipline sections are defined. "
+ "Ignoring [TrafficControlQueueingDiscipline] section from line %u.",
+ qdisc->section->filename, qdisc->section->line);
+ *has_root = true;
+ } else if (qdisc->parent == TC_H_CLSACT) {
+ if (*has_clsact)
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: More than one clsact TrafficControlQueueingDiscipline sections are defined. "
+ "Ignoring [TrafficControlQueueingDiscipline] section from line %u.",
+ qdisc->section->filename, qdisc->section->line);
+ *has_clsact = true;
+ }
+
+ return 0;
+}
+
int config_parse_tc_qdiscs_parent(
const char *unit,
const char *filename,
@@ -180,7 +235,7 @@ int config_parse_tc_qdiscs_parent(
void *data,
void *userdata) {
- _cleanup_(qdisc_free_or_set_invalidp) QDiscs *qdisc = NULL;
+ _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
Network *network = data;
int r;
diff --git a/src/network/tc/qdisc.h b/src/network/tc/qdisc.h
index 95ff829b9e3..1d06dc53f44 100644
--- a/src/network/tc/qdisc.h
+++ b/src/network/tc/qdisc.h
@@ -7,8 +7,10 @@
#include "networkd-link.h"
#include "networkd-network.h"
#include "networkd-util.h"
+#include "sfq.h"
+#include "tbf.h"
-typedef struct QDiscs {
+typedef struct QDisc {
NetworkConfigSection *section;
Network *network;
@@ -20,15 +22,21 @@ typedef struct QDiscs {
uint32_t parent;
bool has_network_emulator:1;
+ bool has_token_buffer_filter:1;
+ bool has_stochastic_fairness_queueing:1;
NetworkEmulator ne;
-} QDiscs;
+ TokenBufferFilter tbf;
+ StochasticFairnessQueueing sfq;
+} QDisc;
-void qdisc_free(QDiscs *qdisc);
-int qdisc_new_static(Network *network, const char *filename, unsigned section_line, QDiscs **ret);
+void qdisc_free(QDisc *qdisc);
+int qdisc_new_static(Network *network, const char *filename, unsigned section_line, QDisc **ret);
-int qdisc_configure(Link *link, QDiscs *qdisc);
+int qdisc_configure(Link *link, QDisc *qdisc);
-DEFINE_NETWORK_SECTION_FUNCTIONS(QDiscs, qdisc_free);
+int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact);
+
+DEFINE_NETWORK_SECTION_FUNCTIONS(QDisc, qdisc_free);
CONFIG_PARSER_PROTOTYPE(config_parse_tc_qdiscs_parent);
diff --git a/src/network/tc/sfq.c b/src/network/tc/sfq.c
new file mode 100644
index 00000000000..393b0e12e1d
--- /dev/null
+++ b/src/network/tc/sfq.c
@@ -0,0 +1,87 @@
+/* SPDX-License-Identifier: LGPL-2.1+
+ * Copyright © 2019 VMware, Inc. */
+
+#include <linux/pkt_sched.h>
+
+#include "alloc-util.h"
+#include "conf-parser.h"
+#include "netlink-util.h"
+#include "parse-util.h"
+#include "qdisc.h"
+#include "sfq.h"
+#include "string-util.h"
+
+int stochastic_fairness_queueing_new(StochasticFairnessQueueing **ret) {
+ StochasticFairnessQueueing *sfq = NULL;
+
+ sfq = new0(StochasticFairnessQueueing, 1);
+ if (!sfq)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(sfq);
+
+ return 0;
+}
+
+int stochastic_fairness_queueing_fill_message(Link *link, const StochasticFairnessQueueing *sfq, sd_netlink_message *req) {
+ struct tc_sfq_qopt_v1 opt = {};
+ int r;
+
+ assert(link);
+ assert(sfq);
+ assert(req);
+
+ opt.v0.perturb_period = sfq->perturb_period / USEC_PER_SEC;
+
+ r = sd_netlink_message_append_data(req, TCA_OPTIONS, &opt, sizeof(struct tc_sfq_qopt_v1));
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append TCA_OPTIONS attribute: %m");
+
+ return 0;
+}
+
+int config_parse_tc_stochastic_fairness_queueing_perturb_period(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
+ Network *network = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = qdisc_new_static(network, filename, section_line, &qdisc);
+ if (r < 0)
+ return r;
+
+ if (isempty(rvalue)) {
+ qdisc->sfq.perturb_period = 0;
+
+ qdisc = NULL;
+ return 0;
+ }
+
+ r = parse_sec(rvalue, &qdisc->sfq.perturb_period);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to parse '%s=', ignoring assignment: %s",
+ lvalue, rvalue);
+ return 0;
+ }
+
+ qdisc->has_stochastic_fairness_queueing = true;
+ qdisc = NULL;
+
+ return 0;
+}
diff --git a/src/network/tc/sfq.h b/src/network/tc/sfq.h
new file mode 100644
index 00000000000..8c00e0e7133
--- /dev/null
+++ b/src/network/tc/sfq.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: LGPL-2.1+
+ * Copyright © 2019 VMware, Inc. */
+#pragma once
+
+#include "sd-netlink.h"
+
+#include "conf-parser.h"
+#include "networkd-link.h"
+
+typedef struct StochasticFairnessQueueing {
+ usec_t perturb_period;
+} StochasticFairnessQueueing;
+
+int stochastic_fairness_queueing_new(StochasticFairnessQueueing **ret);
+int stochastic_fairness_queueing_fill_message(Link *link, const StochasticFairnessQueueing *sfq, sd_netlink_message *req);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_tc_stochastic_fairness_queueing_perturb_period);
diff --git a/src/network/tc/tbf.c b/src/network/tc/tbf.c
new file mode 100644
index 00000000000..a4ef9ab2992
--- /dev/null
+++ b/src/network/tc/tbf.c
@@ -0,0 +1,167 @@
+/* SPDX-License-Identifier: LGPL-2.1+
+ * Copyright © 2019 VMware, Inc. */
+
+#include <linux/pkt_sched.h>
+#include <math.h>
+
+#include "alloc-util.h"
+#include "conf-parser.h"
+#include "netem.h"
+#include "netlink-util.h"
+#include "networkd-manager.h"
+#include "parse-util.h"
+#include "qdisc.h"
+#include "string-util.h"
+#include "util.h"
+
+int token_buffer_filter_new(TokenBufferFilter **ret) {
+ TokenBufferFilter *ne = NULL;
+
+ ne = new0(TokenBufferFilter, 1);
+ if (!ne)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(ne);
+
+ return 0;
+}
+
+int token_buffer_filter_fill_message(Link *link, const TokenBufferFilter *tbf, sd_netlink_message *req) {
+ struct tc_tbf_qopt opt = {};
+ int r;
+
+ assert(link);
+ assert(tbf);
+ assert(req);
+
+ opt.rate.rate = tbf->rate >= (1ULL << 32) ? ~0U : tbf->rate;
+ opt.limit = tbf->rate * (double) tbf->latency / USEC_PER_SEC + tbf->burst;
+
+ r = sd_netlink_message_open_array(req, TCA_OPTIONS);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m");
+
+ r = sd_netlink_message_append_data(req, TCA_TBF_PARMS, &opt, sizeof(struct tc_tbf_qopt));
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append TCA_TBF_PARMS attribute: %m");
+
+ r = sd_netlink_message_append_data(req, TCA_TBF_BURST, &tbf->burst, sizeof(tbf->burst));
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append TCA_TBF_BURST attribute: %m");
+
+ if (tbf->rate >= (1ULL << 32)) {
+ r = sd_netlink_message_append_data(req, TCA_TBF_RATE64, &tbf->rate, sizeof(tbf->rate));
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append TCA_TBF_RATE64 attribute: %m");
+ }
+
+ r = sd_netlink_message_close_container(req);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not close container TCA_OPTIONS: %m");
+
+ return 0;
+}
+
+int config_parse_tc_token_buffer_filter_size(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
+ Network *network = data;
+ uint64_t k;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = qdisc_new_static(network, filename, section_line, &qdisc);
+ if (r < 0)
+ return r;
+
+ if (isempty(rvalue)) {
+ if (streq(lvalue, "TokenBufferFilterRate"))
+ qdisc->tbf.rate = 0;
+ else if (streq(lvalue, "TokenBufferFilterBurst"))
+ qdisc->tbf.burst = 0;
+
+ qdisc = NULL;
+ return 0;
+ }
+
+ r = parse_size(rvalue, 1000, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to parse '%s=', ignoring assignment: %s",
+ lvalue, rvalue);
+ return 0;
+ }
+
+ if (streq(lvalue, "TokenBufferFilterRate"))
+ qdisc->tbf.rate = k / 8;
+ else if (streq(lvalue, "TokenBufferFilterBurst"))
+ qdisc->tbf.burst = k;
+
+ qdisc->has_token_buffer_filter = true;
+ qdisc = NULL;
+
+ return 0;
+}
+
+int config_parse_tc_token_buffer_filter_latency(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
+ Network *network = data;
+ usec_t u;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = qdisc_new_static(network, filename, section_line, &qdisc);
+ if (r < 0)
+ return r;
+
+ if (isempty(rvalue)) {
+ qdisc->tbf.latency = 0;
+
+ qdisc = NULL;
+ return 0;
+ }
+
+ r = parse_sec(rvalue, &u);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to parse '%s=', ignoring assignment: %s",
+ lvalue, rvalue);
+ return 0;
+ }
+
+ qdisc->tbf.latency = u;
+
+ qdisc->has_token_buffer_filter = true;
+ qdisc = NULL;
+
+ return 0;
+}
diff --git a/src/network/tc/tbf.h b/src/network/tc/tbf.h
new file mode 100644
index 00000000000..c8ae6d057d2
--- /dev/null
+++ b/src/network/tc/tbf.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: LGPL-2.1+
+ * Copyright © 2019 VMware, Inc. */
+#pragma once
+
+#include "sd-netlink.h"
+
+#include "conf-parser.h"
+#include "networkd-link.h"
+
+typedef struct TokenBufferFilter {
+ uint64_t rate;
+
+ uint32_t burst;
+ uint32_t latency;
+} TokenBufferFilter;
+
+int token_buffer_filter_new(TokenBufferFilter **ret);
+int token_buffer_filter_fill_message(Link *link, const TokenBufferFilter *tbf, sd_netlink_message *req);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_tc_token_buffer_filter_latency);
+CONFIG_PARSER_PROTOTYPE(config_parse_tc_token_buffer_filter_size);
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index cb10ca306a4..2a6f111d83b 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -270,3 +270,7 @@ NetworkEmulatorDelayJitterSec=
NetworkEmulatorLossRate=
NetworkEmulatorDuplicateRate=
NetworkEmulatorPacketLimit=
+TokenBufferFilterRate=
+TokenBufferFilterBurst=
+TokenBufferFilterLatencySec=
+StochasticFairnessQueueingPerturbPeriodSec=
diff --git a/test/test-network/conf/25-qdisc.network b/test/test-network/conf/25-qdisc-netem.network
index de8f7243ce8..de8f7243ce8 100644
--- a/test/test-network/conf/25-qdisc.network
+++ b/test/test-network/conf/25-qdisc-netem.network
diff --git a/test/test-network/conf/25-qdisc-tbf-and-sfq.network b/test/test-network/conf/25-qdisc-tbf-and-sfq.network
new file mode 100644
index 00000000000..7a6d3315a18
--- /dev/null
+++ b/test/test-network/conf/25-qdisc-tbf-and-sfq.network
@@ -0,0 +1,16 @@
+[Match]
+Name=test1
+
+[Network]
+IPv6AcceptRA=no
+Address=10.1.2.4/16
+
+[TrafficControlQueueingDiscipline]
+Parent=root
+TokenBufferFilterRate=0.5M
+TokenBufferFilterBurst=5K
+TokenBufferFilterLatencySec=70msec
+
+[TrafficControlQueueingDiscipline]
+Parent=clsact
+StochasticFairnessQueueingPerturbPeriodSec=5sec
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index ab6e11e0e52..f47463956e7 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -1498,7 +1498,8 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
'25-neighbor-ip-dummy.network',
'25-neighbor-ip.network',
'25-nexthop.network',
- '25-qdisc.network',
+ '25-qdisc-netem.network',
+ '25-qdisc-tbf-and-sfq.network',
'25-route-ipv6-src.network',
'25-route-static.network',
'25-gateway-static.network',
@@ -2057,15 +2058,23 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
self.assertRegex(output, '192.168.5.1')
def test_qdisc(self):
- copy_unit_to_networkd_unit_path('25-qdisc.network', '12-dummy.netdev')
+ copy_unit_to_networkd_unit_path('25-qdisc-netem.network', '12-dummy.netdev',
+ '25-qdisc-tbf-and-sfq.network', '11-dummy.netdev')
start_networkd()
- self.wait_online(['dummy98:routable'])
+ self.wait_online(['dummy98:routable', 'test1:routable'])
output = check_output('tc qdisc show dev dummy98')
print(output)
+ self.assertRegex(output, 'qdisc netem')
self.assertRegex(output, 'limit 100 delay 50.0ms 10.0ms loss 20%')
self.assertRegex(output, 'limit 200 delay 100.0ms 13.0ms loss 20.5%')
+ output = check_output('tc qdisc show dev test1')
+ print(output)
+ self.assertRegex(output, 'qdisc tbf')
+ self.assertRegex(output, 'rate 500Kbit burst 5000b lat 70.0ms')
+ self.assertRegex(output, 'qdisc sfq')
+ self.assertRegex(output, 'perturb 5sec')
class NetworkdStateFileTests(unittest.TestCase, Utilities):
links = [