From ba5841b5206dcd35ea68c75ec4172bf7e8b8b337 Mon Sep 17 00:00:00 2001 From: Susant Sahani Date: Sun, 17 Nov 2019 07:30:03 +0100 Subject: [PATCH 1/9] networkd tc: introduce tbf See https://linux.die.net/man/8/tc-tbf --- man/systemd.network.xml | 27 +++ src/libsystemd/sd-netlink/netlink-message.c | 1 - src/network/meson.build | 2 + src/network/networkd-network-gperf.gperf | 3 + src/network/tc/qdisc.c | 10 ++ src/network/tc/qdisc.h | 3 + src/network/tc/tbf.c | 167 ++++++++++++++++++ src/network/tc/tbf.h | 21 +++ .../fuzz-network-parser/directives.network | 3 + 9 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 src/network/tc/tbf.c create mode 100644 src/network/tc/tbf.h diff --git a/man/systemd.network.xml b/man/systemd.network.xml index a26e08c99c..20723bfbfa 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2368,6 +2368,33 @@ + + TokenBufferFilterLatencySec= + + Specifies the latency parameter, which specifies the maximum amount of time a + packet can sit in the Token Buffer Filter (TBF). Defaults to unset. + + + + + TokenBufferFilterBurst= + + 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. + + + + + TokenBufferFilterRate= + + 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. + + + diff --git a/src/libsystemd/sd-netlink/netlink-message.c b/src/libsystemd/sd-netlink/netlink-message.c index 34b66e6fa6..1569f34cc4 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 d502279151..3633694577 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -109,6 +109,8 @@ sources = files(''' tc/netem.h tc/qdisc.c tc/qdisc.h + tc/tbf.c + tc/tbf.h tc/tc-util.c tc/tc-util.h '''.split()) diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index f314b1ec16..32c6afc49e 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -250,6 +250,9 @@ TrafficControlQueueingDiscipline.NetworkEmulatorDelayJitterSec, config_pars 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 /* 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/tc/qdisc.c b/src/network/tc/qdisc.c index ed9bd9167a..9baf723461 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -152,6 +152,16 @@ int qdisc_configure(Link *link, QDiscs *qdisc) { 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 (tca_kind) { r = sd_netlink_message_append_string(req, TCA_KIND, tca_kind); if (r < 0) diff --git a/src/network/tc/qdisc.h b/src/network/tc/qdisc.h index 95ff829b9e..0453c85c9d 100644 --- a/src/network/tc/qdisc.h +++ b/src/network/tc/qdisc.h @@ -7,6 +7,7 @@ #include "networkd-link.h" #include "networkd-network.h" #include "networkd-util.h" +#include "tbf.h" typedef struct QDiscs { NetworkConfigSection *section; @@ -20,8 +21,10 @@ typedef struct QDiscs { uint32_t parent; bool has_network_emulator:1; + bool has_token_buffer_filter:1; NetworkEmulator ne; + TokenBufferFilter tbf; } QDiscs; void qdisc_free(QDiscs *qdisc); diff --git a/src/network/tc/tbf.c b/src/network/tc/tbf.c new file mode 100644 index 0000000000..47c999e7c7 --- /dev/null +++ b/src/network/tc/tbf.c @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: LGPL-2.1+ + * Copyright © 2019 VMware, Inc. */ + +#include +#include + +#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) QDiscs *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) QDiscs *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 0000000000..c8ae6d057d --- /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 cb10ca306a..1cdbc07a24 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -270,3 +270,6 @@ NetworkEmulatorDelayJitterSec= NetworkEmulatorLossRate= NetworkEmulatorDuplicateRate= NetworkEmulatorPacketLimit= +TokenBufferFilterRate= +TokenBufferFilterBurst= +TokenBufferFilterLatencySec= From 8efb93f02dd6f529df7ba5c310c99e87fcb2c1e3 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 24 Nov 2019 20:31:38 +0900 Subject: [PATCH 2/9] network: ignore sections which have both NetworkEmulator and TokenBufferFilter settings --- src/network/networkd-network.c | 7 +++++++ src/network/tc/qdisc.c | 33 +++++++++++++++++++++++++++++++++ src/network/tc/qdisc.h | 2 ++ 3 files changed, 42 insertions(+) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 6e443975f1..6bf979e922 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; + QDiscs *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/qdisc.c b/src/network/tc/qdisc.c index 9baf723461..9b966f3d37 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -178,6 +178,39 @@ int qdisc_configure(Link *link, QDiscs *qdisc) { return 0; } +int qdisc_section_verify(QDiscs *qdisc, bool *has_root, bool *has_clsact) { + assert(qdisc); + assert(has_root); + assert(has_clsact); + + if (section_is_invalid(qdisc->section)) + return -EINVAL; + + if (qdisc->has_network_emulator && qdisc->has_token_buffer_filter) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: TrafficControlQueueingDiscipline section has both NetworkEmulator and TokenBufferFilter settings. " + "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, diff --git a/src/network/tc/qdisc.h b/src/network/tc/qdisc.h index 0453c85c9d..f108c60301 100644 --- a/src/network/tc/qdisc.h +++ b/src/network/tc/qdisc.h @@ -32,6 +32,8 @@ int qdisc_new_static(Network *network, const char *filename, unsigned section_li int qdisc_configure(Link *link, QDiscs *qdisc); +int qdisc_section_verify(QDiscs *qdisc, bool *has_root, bool *has_clsact); + DEFINE_NETWORK_SECTION_FUNCTIONS(QDiscs, qdisc_free); CONFIG_PARSER_PROTOTYPE(config_parse_tc_qdiscs_parent); From edc54f2f753b929d78258eb26b3f95b5bb3137c9 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 25 Nov 2019 23:07:58 +0900 Subject: [PATCH 3/9] network: rename QDiscs to QDisc --- src/network/networkd-link.c | 4 ++-- src/network/networkd-network.c | 2 +- src/network/tc/netem.c | 8 ++++---- src/network/tc/netem.h | 4 ++-- src/network/tc/qdisc.c | 22 +++++++++++----------- src/network/tc/qdisc.h | 14 +++++++------- src/network/tc/tbf.c | 4 ++-- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 36d24fd0f3..990f62850a 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.c b/src/network/networkd-network.c index 6bf979e922..181afbb4cd 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -154,7 +154,7 @@ int network_verify(Network *network) { Prefix *prefix, *prefix_next; Route *route, *route_next; FdbEntry *fdb, *fdb_next; - QDiscs *qdisc; + QDisc *qdisc; Iterator i; assert(network); diff --git a/src/network/tc/netem.c b/src/network/tc/netem.c index 053af3e7db..ea44fd1a9c 100644 --- a/src/network/tc/netem.c +++ b/src/network/tc/netem.c @@ -34,7 +34,7 @@ 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, QDisc *qdisc, sd_netlink_message *req) { struct tc_netem_qopt opt = { .limit = 1000, }; @@ -84,7 +84,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 +139,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 +189,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 43abf20af8..ac018f9960 100644 --- a/src/network/tc/netem.h +++ b/src/network/tc/netem.h @@ -8,7 +8,7 @@ #include "networkd-link.h" #include "time-util.h" -typedef struct QDiscs QDiscs; +typedef struct QDisc QDisc; typedef struct NetworkEmulator { usec_t delay; @@ -20,7 +20,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, QDisc *qdisc, 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 9b966f3d37..5a130d9779 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -14,14 +14,14 @@ #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 +31,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 +76,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 +106,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 +114,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; @@ -178,7 +178,7 @@ int qdisc_configure(Link *link, QDiscs *qdisc) { return 0; } -int qdisc_section_verify(QDiscs *qdisc, bool *has_root, bool *has_clsact) { +int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact) { assert(qdisc); assert(has_root); assert(has_clsact); @@ -223,7 +223,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 f108c60301..4810b87600 100644 --- a/src/network/tc/qdisc.h +++ b/src/network/tc/qdisc.h @@ -9,7 +9,7 @@ #include "networkd-util.h" #include "tbf.h" -typedef struct QDiscs { +typedef struct QDisc { NetworkConfigSection *section; Network *network; @@ -25,15 +25,15 @@ typedef struct QDiscs { NetworkEmulator ne; TokenBufferFilter tbf; -} QDiscs; +} 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); -int qdisc_section_verify(QDiscs *qdisc, bool *has_root, bool *has_clsact); +int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact); -DEFINE_NETWORK_SECTION_FUNCTIONS(QDiscs, qdisc_free); +DEFINE_NETWORK_SECTION_FUNCTIONS(QDisc, qdisc_free); CONFIG_PARSER_PROTOTYPE(config_parse_tc_qdiscs_parent); diff --git a/src/network/tc/tbf.c b/src/network/tc/tbf.c index 47c999e7c7..a4ef9ab299 100644 --- a/src/network/tc/tbf.c +++ b/src/network/tc/tbf.c @@ -74,7 +74,7 @@ int config_parse_tc_token_buffer_filter_size( 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; uint64_t k; int r; @@ -129,7 +129,7 @@ int config_parse_tc_token_buffer_filter_latency( 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; From 6483f04381dbd8acd76386880db80f208dc7c73a Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 25 Nov 2019 23:13:54 +0900 Subject: [PATCH 4/9] network: make network_emulator_fill_message() take NetworkEmulator --- src/network/tc/netem.c | 24 ++++++++++++------------ src/network/tc/netem.h | 4 +--- src/network/tc/qdisc.c | 2 +- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/network/tc/netem.c b/src/network/tc/netem.c index ea44fd1a9c..0088bd706c 100644 --- a/src/network/tc/netem.c +++ b/src/network/tc/netem.c @@ -34,33 +34,33 @@ int network_emulator_new(NetworkEmulator **ret) { return 0; } -int network_emulator_fill_message(Link *link, QDisc *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"); } diff --git a/src/network/tc/netem.h b/src/network/tc/netem.h index ac018f9960..66c3476a6b 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 QDisc QDisc; - 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, QDisc *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 5a130d9779..3c233975ff 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -147,7 +147,7 @@ int qdisc_configure(Link *link, QDisc *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; } From f1dba55565640ce4a5a0714dd57c0ac9b8ef8358 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 25 Nov 2019 23:15:08 +0900 Subject: [PATCH 5/9] network: drop unnecessary headers --- src/network/tc/netem.c | 4 ---- src/network/tc/qdisc.c | 1 - 2 files changed, 5 deletions(-) diff --git a/src/network/tc/netem.c b/src/network/tc/netem.c index 0088bd706c..25a53150b0 100644 --- a/src/network/tc/netem.c +++ b/src/network/tc/netem.c @@ -2,12 +2,9 @@ * Copyright © 2019 VMware, Inc. */ #include -#include #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; diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c index 3c233975ff..e8703399a7 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -12,7 +12,6 @@ #include "qdisc.h" #include "set.h" #include "string-util.h" -#include "util.h" static int qdisc_new(QDisc **ret) { QDisc *qdisc; From 1b628c4f64e942c65a9703dbcb2f6e57c4818f55 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 24 Nov 2019 21:23:48 +0900 Subject: [PATCH 6/9] test-network: add test case for TBF --- .../{25-qdisc.network => 25-qdisc-netem.network} | 0 test/test-network/conf/25-qdisc-tbf.network | 12 ++++++++++++ test/test-network/systemd-networkd-tests.py | 11 ++++++++--- 3 files changed, 20 insertions(+), 3 deletions(-) rename test/test-network/conf/{25-qdisc.network => 25-qdisc-netem.network} (100%) create mode 100644 test/test-network/conf/25-qdisc-tbf.network diff --git a/test/test-network/conf/25-qdisc.network b/test/test-network/conf/25-qdisc-netem.network similarity index 100% rename from test/test-network/conf/25-qdisc.network rename to test/test-network/conf/25-qdisc-netem.network diff --git a/test/test-network/conf/25-qdisc-tbf.network b/test/test-network/conf/25-qdisc-tbf.network new file mode 100644 index 0000000000..17ca42b920 --- /dev/null +++ b/test/test-network/conf/25-qdisc-tbf.network @@ -0,0 +1,12 @@ +[Match] +Name=test1 + +[Network] +IPv6AcceptRA=no +Address=10.1.2.4/16 + +[TrafficControlQueueingDiscipline] +Parent=root +TokenBufferFilterRate=0.5M +TokenBufferFilterBurst=5K +TokenBufferFilterLatencySec=70msec diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index ab6e11e0e5..9e91caf763 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.network', '25-route-ipv6-src.network', '25-route-static.network', '25-gateway-static.network', @@ -2057,15 +2058,19 @@ 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.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, '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, 'rate 500Kbit burst 5000b lat 70.0ms') class NetworkdStateFileTests(unittest.TestCase, Utilities): links = [ From 9942b71089aa0c71d67d876dbb9355891d222574 Mon Sep 17 00:00:00 2001 From: Susant Sahani Date: Wed, 27 Nov 2019 12:42:21 +0100 Subject: [PATCH 7/9] network: tc introduce sfq - Stochastic Fairness Queueing Stochastic Fairness Queueing is a classless queueing discipline. SFQ does not shape traffic but only schedules the transmission of packets, based on 'flows'. The goal is to ensure fairness so that each flow is able to send data in turn, thus preventing any single flow from drowning out the rest. --- man/systemd.network.xml | 7 ++ src/network/meson.build | 2 + src/network/networkd-network-gperf.gperf | 19 ++-- src/network/tc/qdisc.c | 10 +++ src/network/tc/qdisc.h | 3 + src/network/tc/sfq.c | 87 +++++++++++++++++++ src/network/tc/sfq.h | 17 ++++ .../fuzz-network-parser/directives.network | 1 + 8 files changed, 137 insertions(+), 9 deletions(-) create mode 100644 src/network/tc/sfq.c create mode 100644 src/network/tc/sfq.h diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 20723bfbfa..a2ac24059a 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2395,6 +2395,13 @@ + + StochasticFairnessQueueingPerturbPeriodSec= + + Specifies the interval in seconds for queue algorithm perturbation. Defaults to unset. + + + diff --git a/src/network/meson.build b/src/network/meson.build index 3633694577..e2324a01b3 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -109,6 +109,8 @@ 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 diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 32c6afc49e..1bfd76ec73 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -244,15 +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.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.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/tc/qdisc.c b/src/network/tc/qdisc.c index e8703399a7..717a0cfd38 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -161,6 +161,16 @@ int qdisc_configure(Link *link, QDisc *qdisc) { 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; + } + if (tca_kind) { r = sd_netlink_message_append_string(req, TCA_KIND, tca_kind); if (r < 0) diff --git a/src/network/tc/qdisc.h b/src/network/tc/qdisc.h index 4810b87600..1d06dc53f4 100644 --- a/src/network/tc/qdisc.h +++ b/src/network/tc/qdisc.h @@ -7,6 +7,7 @@ #include "networkd-link.h" #include "networkd-network.h" #include "networkd-util.h" +#include "sfq.h" #include "tbf.h" typedef struct QDisc { @@ -22,9 +23,11 @@ typedef struct QDisc { bool has_network_emulator:1; bool has_token_buffer_filter:1; + bool has_stochastic_fairness_queueing:1; NetworkEmulator ne; TokenBufferFilter tbf; + StochasticFairnessQueueing sfq; } QDisc; void qdisc_free(QDisc *qdisc); diff --git a/src/network/tc/sfq.c b/src/network/tc/sfq.c new file mode 100644 index 0000000000..393b0e12e1 --- /dev/null +++ b/src/network/tc/sfq.c @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: LGPL-2.1+ + * Copyright © 2019 VMware, Inc. */ + +#include + +#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 0000000000..8c00e0e713 --- /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/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 1cdbc07a24..2a6f111d83 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -273,3 +273,4 @@ NetworkEmulatorPacketLimit= TokenBufferFilterRate= TokenBufferFilterBurst= TokenBufferFilterLatencySec= +StochasticFairnessQueueingPerturbPeriodSec= From b2340fbb5ab3a3f37f1290d5ac1d604819e32ffd Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 3 Dec 2019 23:12:00 +0900 Subject: [PATCH 8/9] network: SFQ cannot be configured with netem or TBF --- src/network/tc/qdisc.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c index 717a0cfd38..05c0ebbc18 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -188,6 +188,8 @@ int qdisc_configure(Link *link, QDisc *qdisc) { } int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact) { + unsigned i; + assert(qdisc); assert(has_root); assert(has_clsact); @@ -195,9 +197,10 @@ int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact) { if (section_is_invalid(qdisc->section)) return -EINVAL; - if (qdisc->has_network_emulator && qdisc->has_token_buffer_filter) + 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 both NetworkEmulator and TokenBufferFilter settings. " + "%s: TrafficControlQueueingDiscipline section has more than one type of discipline. " "Ignoring [TrafficControlQueueingDiscipline] section from line %u.", qdisc->section->filename, qdisc->section->line); From 0ab92791327177d39001ea2f4cc95bc78d75c572 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 3 Dec 2019 23:20:50 +0900 Subject: [PATCH 9/9] test-network: add a test case for SFQ --- ...{25-qdisc-tbf.network => 25-qdisc-tbf-and-sfq.network} | 4 ++++ test/test-network/systemd-networkd-tests.py | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) rename test/test-network/conf/{25-qdisc-tbf.network => 25-qdisc-tbf-and-sfq.network} (67%) diff --git a/test/test-network/conf/25-qdisc-tbf.network b/test/test-network/conf/25-qdisc-tbf-and-sfq.network similarity index 67% rename from test/test-network/conf/25-qdisc-tbf.network rename to test/test-network/conf/25-qdisc-tbf-and-sfq.network index 17ca42b920..7a6d3315a1 100644 --- a/test/test-network/conf/25-qdisc-tbf.network +++ b/test/test-network/conf/25-qdisc-tbf-and-sfq.network @@ -10,3 +10,7 @@ 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 9e91caf763..f47463956e 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -1499,7 +1499,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): '25-neighbor-ip.network', '25-nexthop.network', '25-qdisc-netem.network', - '25-qdisc-tbf.network', + '25-qdisc-tbf-and-sfq.network', '25-route-ipv6-src.network', '25-route-static.network', '25-gateway-static.network', @@ -2059,18 +2059,22 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): def test_qdisc(self): copy_unit_to_networkd_unit_path('25-qdisc-netem.network', '12-dummy.netdev', - '25-qdisc-tbf.network', '11-dummy.netdev') + '25-qdisc-tbf-and-sfq.network', '11-dummy.netdev') start_networkd() 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 = [