diff options
author | Anita Zhang <the.anitazha@gmail.com> | 2019-12-04 16:14:11 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-12-04 16:14:11 -0800 |
commit | da4dd97405eac3f692f7bd032983adc8b780c8b6 (patch) | |
tree | 5e3737cec47d457655a0f6fe6381cd9e7f489ca6 | |
parent | Merge pull request #14219 from poettering/homed-preparatory-loop (diff) | |
parent | test-network: add a test case for SFQ (diff) | |
download | systemd-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.xml | 34 | ||||
-rw-r--r-- | src/libsystemd/sd-netlink/netlink-message.c | 1 | ||||
-rw-r--r-- | src/network/meson.build | 4 | ||||
-rw-r--r-- | src/network/networkd-link.c | 4 | ||||
-rw-r--r-- | src/network/networkd-network-gperf.gperf | 16 | ||||
-rw-r--r-- | src/network/networkd-network.c | 7 | ||||
-rw-r--r-- | src/network/tc/netem.c | 34 | ||||
-rw-r--r-- | src/network/tc/netem.h | 4 | ||||
-rw-r--r-- | src/network/tc/qdisc.c | 79 | ||||
-rw-r--r-- | src/network/tc/qdisc.h | 20 | ||||
-rw-r--r-- | src/network/tc/sfq.c | 87 | ||||
-rw-r--r-- | src/network/tc/sfq.h | 17 | ||||
-rw-r--r-- | src/network/tc/tbf.c | 167 | ||||
-rw-r--r-- | src/network/tc/tbf.h | 21 | ||||
-rw-r--r-- | test/fuzz/fuzz-network-parser/directives.network | 4 | ||||
-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.network | 16 | ||||
-rwxr-xr-x | test/test-network/systemd-networkd-tests.py | 15 |
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 = [ |