From 2a0963311795677a43fdb2b9ae9c3dfbfee83ac6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 10 Feb 2020 17:45:24 +0900 Subject: [PATCH 01/17] network: tc: make Parent= take class id --- man/systemd.network.xml | 28 +++++++++++++++++++++------- src/network/tc/qdisc.c | 20 ++++++++++++-------- src/network/tc/tc-util.c | 30 ++++++++++++++++++++++++++++++ src/network/tc/tc-util.h | 1 + 4 files changed, 64 insertions(+), 15 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 209787bf1b..c3608394c8 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2322,7 +2322,9 @@ Parent= Specifies the parent Queueing Discipline (qdisc). Takes one of root, - clsact or ingress. Defaults to root. + clsact, ingress or a class id. The class id takes the + major and minor number in hexadecimal ranges 1 to ffff separated with a colon + (major:minor). Defaults to root. @@ -2386,7 +2388,9 @@ Parent= Specifies the parent Queueing Discipline (qdisc). Takes one of root, - clsact or ingress. Defaults to root. + clsact, ingress or a class id. The class id takes the + major and minor number in hexadecimal ranges 1 to ffff separated with a colon + (major:minor). Defaults to root. @@ -2473,7 +2477,9 @@ Parent= Specifies the parent Queueing Discipline (qdisc). Takes one of root, - clsact or ingress. Defaults to root. + clsact, ingress or a class id. The class id takes the + major and minor number in hexadecimal ranges 1 to ffff separated with a colon + (major:minor). Defaults to root. @@ -2504,7 +2510,9 @@ Parent= Specifies the parent Queueing Discipline (qdisc). Takes one of root, - clsact or ingress. Defaults to root. + clsact, ingress or a class id. The class id takes the + major and minor number in hexadecimal ranges 1 to ffff separated with a colon + (major:minor). Defaults to root. @@ -2568,7 +2576,9 @@ Parent= Specifies the parent Queueing Discipline (qdisc). Takes one of root, - clsact or ingress. Defaults to root. + clsact, ingress or a class id. The class id takes the + major and minor number in hexadecimal ranges 1 to ffff separated with a colon + (major:minor). Defaults to root. @@ -2658,7 +2668,9 @@ Parent= Specifies the parent Queueing Discipline (qdisc). Takes one of root, - clsact or ingress. Defaults to root. + clsact, ingress or a class id. The class id takes the + major and minor number in hexadecimal ranges 1 to ffff separated with a colon + (major:minor). Defaults to root. @@ -2760,7 +2772,9 @@ Parent= Specifies the parent Queueing Discipline (qdisc). Takes one of root, - clsact or ingress. Defaults to root. + clsact, ingress or a class id. The class id takes the + major and minor number in hexadecimal ranges 1 to ffff separated with a colon + (major:minor). Defaults to root. diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c index 0619e894cc..988167f8b4 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -12,6 +12,8 @@ #include "qdisc.h" #include "set.h" #include "string-util.h" +#include "strv.h" +#include "tc-util.h" const QDiscVTable * const qdisc_vtable[_QDISC_KIND_MAX] = { [QDISC_KIND_CODEL] = &codel_vtable, @@ -279,19 +281,21 @@ int config_parse_qdisc_parent( qdisc->parent = TC_H_INGRESS; qdisc->handle = TC_H_MAKE(TC_H_INGRESS, 0); } else { - log_syntax(unit, LOG_ERR, filename, line, r, - "Failed to parse 'Parent=', ignoring assignment: %s", - rvalue); - return 0; + r = parse_handle(rvalue, &qdisc->parent); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to parse 'Parent=', ignoring assignment: %s", + rvalue); + return 0; + } } - if (streq(rvalue, "root")) - qdisc->tca_kind = mfree(qdisc->tca_kind); - else { + if (STR_IN_SET(rvalue, "clsact", "ingress")) { r = free_and_strdup(&qdisc->tca_kind, rvalue); if (r < 0) return log_oom(); - } + } else + qdisc->tca_kind = mfree(qdisc->tca_kind); qdisc = NULL; diff --git a/src/network/tc/tc-util.c b/src/network/tc/tc-util.c index c46550f955..47371a841b 100644 --- a/src/network/tc/tc-util.c +++ b/src/network/tc/tc-util.c @@ -2,6 +2,7 @@ * Copyright © 2019 VMware, Inc. */ #include "alloc-util.h" +#include "extract-word.h" #include "fileio.h" #include "parse-util.h" #include "tc-util.h" @@ -92,3 +93,32 @@ int tc_fill_ratespec_and_table(struct tc_ratespec *rate, uint32_t *rtab, uint32_ rate->linklayer = TC_LINKLAYER_ETHERNET; return 0; } + +int parse_handle(const char *t, uint32_t *ret) { + _cleanup_free_ char *word = NULL; + uint16_t major, minor; + int r; + + assert(t); + assert(ret); + + /* Extract the major number. */ + r = extract_first_word(&t, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + if (!t) + return -EINVAL; + + r = safe_atou16_full(word, 16, &major); + if (r < 0) + return r; + + r = safe_atou16_full(t, 16, &minor); + if (r < 0) + return r; + + *ret = ((uint32_t) major << 16) | minor; + return 0; +} diff --git a/src/network/tc/tc-util.h b/src/network/tc/tc-util.h index c901f50691..38b9d0786d 100644 --- a/src/network/tc/tc-util.h +++ b/src/network/tc/tc-util.h @@ -10,3 +10,4 @@ int tc_time_to_tick(usec_t t, uint32_t *ret); int parse_tc_percent(const char *s, uint32_t *percent); int tc_transmit_time(uint64_t rate, uint32_t size, uint32_t *ret); int tc_fill_ratespec_and_table(struct tc_ratespec *rate, uint32_t *rtab, uint32_t mtu); +int parse_handle(const char *t, uint32_t *ret); From b934ac3d6e7dcad114776ef30ee9098693e7ab7e Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 10 Feb 2020 19:41:12 +0900 Subject: [PATCH 02/17] network: tc: support Hierarchy Token Bucket (HTB) --- man/systemd.network.xml | 34 +++++++ src/basic/parse-util.h | 8 +- src/libsystemd/sd-netlink/netlink-types.c | 7 ++ src/libsystemd/sd-netlink/netlink-types.h | 1 + src/network/meson.build | 2 + src/network/networkd-network-gperf.gperf | 3 + src/network/networkd-network.c | 1 + src/network/tc/htb.c | 98 +++++++++++++++++++ src/network/tc/htb.h | 16 +++ src/network/tc/qdisc.c | 1 + src/network/tc/qdisc.h | 2 + .../fuzz-network-parser/directives.network | 4 + 12 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 src/network/tc/htb.c create mode 100644 src/network/tc/htb.h diff --git a/man/systemd.network.xml b/man/systemd.network.xml index c3608394c8..2e5ac9a28b 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2798,6 +2798,40 @@ + + [HierarchyTokenBucket] Section Options + The [HierarchyTokenBucket] section manages the queueing discipline (qdisc) of + hierarchy token bucket (htb). + + + + Parent= + + Specifies the parent Queueing Discipline (qdisc). Takes one of root, + clsact, ingress or a class id. The class id takes the + major and minor number in hexadecimal ranges 1 to ffff separated with a colon + (major:minor). Defaults to root. + + + + + Handle= + + Specifies the major number of unique identifier of the qdisc, known as the handle. + Takes a number in hexadecimal ranges 1 to ffff. Defaults to unset. + + + + + DefaultClass= + + Takes the minor id in hexadecimal of the default class. Unclassified traffic gets sent + to the class. Defaults to unset. + + + + + [BridgeVLAN] Section Options The [BridgeVLAN] section manages the VLAN ID configuration of a bridge port and accepts diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h index 6b30c727e3..36d76ba576 100644 --- a/src/basic/parse-util.h +++ b/src/basic/parse-util.h @@ -45,9 +45,13 @@ static inline int safe_atoux16(const char *s, uint16_t *ret) { int safe_atoi16(const char *s, int16_t *ret); -static inline int safe_atou32(const char *s, uint32_t *ret_u) { +static inline int safe_atou32_full(const char *s, unsigned base, uint32_t *ret_u) { assert_cc(sizeof(uint32_t) == sizeof(unsigned)); - return safe_atou(s, (unsigned*) ret_u); + return safe_atou_full(s, base, (unsigned*) ret_u); +} + +static inline int safe_atou32(const char *s, uint32_t *ret_u) { + return safe_atou32_full(s, 0, (unsigned*) ret_u); } static inline int safe_atoi32(const char *s, int32_t *ret_i) { diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c index e35127a4cd..d4e9873bba 100644 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -780,6 +780,10 @@ static const NLType rtnl_tca_option_data_fq_codel_types[] = { [TCA_FQ_CODEL_MEMORY_LIMIT] = { .type = NETLINK_TYPE_U32 }, }; +static const NLType rtnl_tca_option_data_htb_types[] = { + [TCA_HTB_INIT] = { .size = sizeof(struct tc_htb_glob) }, +}; + static const NLType rtnl_tca_option_data_tbf_types[] = { [TCA_TBF_PARMS] = { .size = sizeof(struct tc_tbf_qopt) }, [TCA_TBF_RTAB] = { .size = TC_RTAB_SIZE }, @@ -794,6 +798,7 @@ static const char* const nl_union_tca_option_data_table[] = { [NL_UNION_TCA_OPTION_DATA_CODEL] = "codel", [NL_UNION_TCA_OPTION_DATA_FQ] = "fq", [NL_UNION_TCA_OPTION_DATA_FQ_CODEL] = "fq_codel", + [NL_UNION_TCA_OPTION_DATA_HTB] = "htb", [NL_UNION_TCA_OPTION_DATA_TBF] = "tbf", }; @@ -806,6 +811,8 @@ static const NLTypeSystem rtnl_tca_option_data_type_systems[] = { .types = rtnl_tca_option_data_fq_types }, [NL_UNION_TCA_OPTION_DATA_FQ_CODEL] = { .count = ELEMENTSOF(rtnl_tca_option_data_fq_codel_types), .types = rtnl_tca_option_data_fq_codel_types }, + [NL_UNION_TCA_OPTION_DATA_HTB] = { .count = ELEMENTSOF(rtnl_tca_option_data_htb_types), + .types = rtnl_tca_option_data_htb_types }, [NL_UNION_TCA_OPTION_DATA_TBF] = { .count = ELEMENTSOF(rtnl_tca_option_data_tbf_types), .types = rtnl_tca_option_data_tbf_types }, }; diff --git a/src/libsystemd/sd-netlink/netlink-types.h b/src/libsystemd/sd-netlink/netlink-types.h index b2fa8c96e6..a3dd4aa447 100644 --- a/src/libsystemd/sd-netlink/netlink-types.h +++ b/src/libsystemd/sd-netlink/netlink-types.h @@ -95,6 +95,7 @@ typedef enum NLUnionTCAOptionData { NL_UNION_TCA_OPTION_DATA_CODEL, NL_UNION_TCA_OPTION_DATA_FQ, NL_UNION_TCA_OPTION_DATA_FQ_CODEL, + NL_UNION_TCA_OPTION_DATA_HTB, NL_UNION_TCA_OPTION_DATA_TBF, _NL_UNION_TCA_OPTION_DATA_MAX, _NL_UNION_TCA_OPTION_DATA_INVALID = -1, diff --git a/src/network/meson.build b/src/network/meson.build index c1c02cfda1..1f5175e053 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -113,6 +113,8 @@ sources = files(''' tc/fq.h tc/fq-codel.c tc/fq-codel.h + tc/htb.c + tc/htb.h tc/netem.c tc/netem.h tc/qdisc.c diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 134f1535d6..4ebf285a6e 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -281,6 +281,9 @@ FairQueueingControlledDelay.TargetSec, config_parse_fair_queueing_controll FairQueueingControlledDelay.IntervalSec, config_parse_fair_queueing_controlled_delay_usec, QDISC_KIND_FQ_CODEL, 0 FairQueueingControlledDelay.CEThresholdSec, config_parse_fair_queueing_controlled_delay_usec, QDISC_KIND_FQ_CODEL, 0 FairQueueingControlledDelay.ECN, config_parse_fair_queueing_controlled_delay_bool, QDISC_KIND_FQ_CODEL, 0 +HierarchyTokenBucket.Parent, config_parse_qdisc_parent, QDISC_KIND_HTB, 0 +HierarchyTokenBucket.Handle, config_parse_qdisc_handle, QDISC_KIND_HTB, 0 +HierarchyTokenBucket.DefaultClass, config_parse_hierarchy_token_bucket_default_class, QDISC_KIND_HTB, 0 NetworkEmulator.Parent, config_parse_qdisc_parent, QDISC_KIND_NETEM, 0 NetworkEmulator.Handle, config_parse_qdisc_handle, QDISC_KIND_NETEM, 0 NetworkEmulator.DelaySec, config_parse_network_emulator_delay, QDISC_KIND_NETEM, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index b06ae75c05..e6be07d9a1 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -486,6 +486,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi "ControlledDelay\0" "FairQueueing\0" "FairQueueingControlledDelay\0" + "HierarchyTokenBucket\0" "NetworkEmulator\0" "StochasticFairnessQueueing\0" "TokenBucketFilter\0" diff --git a/src/network/tc/htb.c b/src/network/tc/htb.c new file mode 100644 index 0000000000..06dd5cbc78 --- /dev/null +++ b/src/network/tc/htb.c @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include + +#include "alloc-util.h" +#include "conf-parser.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "qdisc.h" +#include "htb.h" +#include "string-util.h" + +static int hierarchy_token_bucket_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + HierarchyTokenBucket *htb; + struct tc_htb_glob opt = { + .rate2quantum = 10, + .version = 3, + }; + int r; + + assert(link); + assert(qdisc); + assert(req); + + htb = HTB(qdisc); + + opt.defcls = htb->default_class; + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "htb"); + 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_HTB_INIT, &opt, sizeof(opt)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_HTB_INIT 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_hierarchy_token_bucket_default_class( + 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; + HierarchyTokenBucket *htb; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_HTB, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_syntax(unit, LOG_ERR, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + + htb = HTB(qdisc); + + if (isempty(rvalue)) { + htb->default_class = 0; + + qdisc = NULL; + return 0; + } + + r = safe_atou32_full(rvalue, 16, &htb->default_class); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qdisc = NULL; + + return 0; +} + +const QDiscVTable htb_vtable = { + .object_size = sizeof(HierarchyTokenBucket), + .tca_kind = "htb", + .fill_message = hierarchy_token_bucket_fill_message, +}; diff --git a/src/network/tc/htb.h b/src/network/tc/htb.h new file mode 100644 index 0000000000..6b5ef8cfb4 --- /dev/null +++ b/src/network/tc/htb.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" + +typedef struct HierarchyTokenBucket { + QDisc meta; + + uint32_t default_class; +} HierarchyTokenBucket; + +DEFINE_QDISC_CAST(HTB, HierarchyTokenBucket); +extern const QDiscVTable htb_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_hierarchy_token_bucket_default_class); diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c index 988167f8b4..9f60888aa5 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -19,6 +19,7 @@ const QDiscVTable * const qdisc_vtable[_QDISC_KIND_MAX] = { [QDISC_KIND_CODEL] = &codel_vtable, [QDISC_KIND_FQ] = &fq_vtable, [QDISC_KIND_FQ_CODEL] = &fq_codel_vtable, + [QDISC_KIND_HTB] = &htb_vtable, [QDISC_KIND_NETEM] = &netem_vtable, [QDISC_KIND_SFQ] = &sfq_vtable, [QDISC_KIND_TBF] = &tbf_vtable, diff --git a/src/network/tc/qdisc.h b/src/network/tc/qdisc.h index 8e4a70de53..7d9e0ddb06 100644 --- a/src/network/tc/qdisc.h +++ b/src/network/tc/qdisc.h @@ -11,6 +11,7 @@ typedef enum QDiscKind { QDISC_KIND_CODEL, QDISC_KIND_FQ, QDISC_KIND_FQ_CODEL, + QDISC_KIND_HTB, QDISC_KIND_NETEM, QDISC_KIND_SFQ, QDISC_KIND_TBF, @@ -71,6 +72,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_qdisc_handle); #include "codel.h" #include "fq-codel.h" #include "fq.h" +#include "htb.h" #include "netem.h" #include "sfq.h" #include "tbf.h" diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 2b41239b74..06e971110a 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -337,3 +337,7 @@ NetworkEmulatorPacketLimit= Parent= Handle= Id= +[HierarchyTokenBucket] +Parent= +Handle= +DefaultClass= From 931c8c824abd684e05552d335d13edfb2f086abf Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 10 Feb 2020 19:41:39 +0900 Subject: [PATCH 03/17] test-network: add a test case for HTB --- ...5-qdisc-teql.network => 25-qdisc-teql-and-htb.network} | 6 +++++- test/test-network/systemd-networkd-tests.py | 8 +++++--- 2 files changed, 10 insertions(+), 4 deletions(-) rename test/test-network/conf/{25-qdisc-teql.network => 25-qdisc-teql-and-htb.network} (68%) diff --git a/test/test-network/conf/25-qdisc-teql.network b/test/test-network/conf/25-qdisc-teql-and-htb.network similarity index 68% rename from test/test-network/conf/25-qdisc-teql.network rename to test/test-network/conf/25-qdisc-teql-and-htb.network index c8bb9034a2..d6e7201326 100644 --- a/test/test-network/conf/25-qdisc-teql.network +++ b/test/test-network/conf/25-qdisc-teql-and-htb.network @@ -6,6 +6,10 @@ IPv6AcceptRA=no Address=10.1.2.3/16 [TrivialLinkEqualizer] +Parent=ingress +Id=1 + +[HierarchyTokenBucket] Parent=root Handle=0002 -Id=1 +DefaultClass=30 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 6cc09235c6..b1af0bc291 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -1626,7 +1626,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): '25-qdisc-ingress-root.network', '25-qdisc-netem-and-fqcodel.network', '25-qdisc-tbf-and-sfq.network', - '25-qdisc-teql.network', + '25-qdisc-teql-and-htb.network', '25-route-ipv6-src.network', '25-route-static.network', '25-route-vrf.network', @@ -2309,7 +2309,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): self.assertRegex(output, 'qdisc ingress') def test_qdisc4(self): - copy_unit_to_networkd_unit_path('25-qdisc-teql.network', '12-dummy.netdev') + copy_unit_to_networkd_unit_path('25-qdisc-teql-and-htb.network', '12-dummy.netdev') check_output('modprobe sch_teql max_equalizers=2') start_networkd() @@ -2317,7 +2317,9 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): output = check_output('tc qdisc show dev dummy98') print(output) - self.assertRegex(output, 'qdisc teql1 2:') + self.assertRegex(output, 'qdisc teql1') + self.assertRegex(output, 'qdisc htb 2:') + self.assertRegex(output, r'default (0x30|30)') class NetworkdStateFileTests(unittest.TestCase, Utilities): links = [ From 34658df256f48626ac8370842e798991de7a842b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 10 Feb 2020 20:53:00 +0900 Subject: [PATCH 04/17] network: introduce TrafficControlKind to prepare for supporting tc class --- src/network/meson.build | 2 ++ src/network/networkd-link.c | 24 ++++++++-------- src/network/networkd-link.h | 4 +-- src/network/networkd-network.c | 11 +++---- src/network/networkd-network.h | 3 +- src/network/tc/qdisc.c | 52 ++++++++++++++++++++-------------- src/network/tc/qdisc.h | 5 ++++ src/network/tc/tc.c | 41 +++++++++++++++++++++++++++ src/network/tc/tc.h | 32 +++++++++++++++++++++ 9 files changed, 131 insertions(+), 43 deletions(-) create mode 100644 src/network/tc/tc.c create mode 100644 src/network/tc/tc.h diff --git a/src/network/meson.build b/src/network/meson.build index 1f5175e053..ce0e7fb0dd 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -125,6 +125,8 @@ sources = files(''' tc/tbf.h tc/tc-util.c tc/tc-util.h + tc/tc.c + tc/tc.h tc/teql.c tc/teql.h '''.split()) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 3dca043d99..bd9d37aad2 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -34,7 +34,6 @@ #include "networkd-radv.h" #include "networkd-routing-policy-rule.h" #include "networkd-wifi.h" -#include "qdisc.h" #include "set.h" #include "socket-util.h" #include "stat-util.h" @@ -42,6 +41,7 @@ #include "string-table.h" #include "strv.h" #include "sysctl-util.h" +#include "tc.h" #include "tmpfile-util.h" #include "udev-util.h" #include "util.h" @@ -1116,7 +1116,7 @@ void link_check_ready(Link *link) { if (!link->routing_policy_rules_configured) return; - if (!link->qdiscs_configured) + if (!link->tc_configured) return; if (link_has_carrier(link) || !link->network->configure_without_carrier) { @@ -2702,24 +2702,24 @@ static int link_configure_ipv4_dad(Link *link) { return 0; } -static int link_configure_qdiscs(Link *link) { - QDisc *qdisc; +static int link_configure_traffic_control(Link *link) { + TrafficControl *tc; Iterator i; int r; - link->qdiscs_configured = false; - link->qdisc_messages = 0; + link->tc_configured = false; + link->tc_messages = 0; - ORDERED_HASHMAP_FOREACH(qdisc, link->network->qdiscs_by_section, i) { - r = qdisc_configure(link, qdisc); + ORDERED_HASHMAP_FOREACH(tc, link->network->tc_by_section, i) { + r = traffic_control_configure(link, tc); if (r < 0) return r; } - if (link->qdisc_messages == 0) - link->qdiscs_configured = true; + if (link->tc_messages == 0) + link->tc_configured = true; else - log_link_debug(link, "Configuring queuing discipline (qdisc)"); + log_link_debug(link, "Configuring traffic control"); return 0; } @@ -2731,7 +2731,7 @@ static int link_configure(Link *link) { assert(link->network); assert(link->state == LINK_STATE_INITIALIZED); - r = link_configure_qdiscs(link); + r = link_configure_traffic_control(link); if (r < 0) return r; diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 59a5107311..3d07d882cd 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -80,7 +80,7 @@ typedef struct Link { unsigned nexthop_messages; unsigned routing_policy_rule_messages; unsigned routing_policy_rule_remove_messages; - unsigned qdisc_messages; + unsigned tc_messages; unsigned enslaving; Set *addresses; @@ -116,7 +116,7 @@ typedef struct Link { bool static_routes_ready:1; bool static_nexthops_configured:1; bool routing_policy_rules_configured:1; - bool qdiscs_configured:1; + bool tc_configured:1; bool setting_mtu:1; bool setting_genmode:1; bool ipv6_mtu_set:1; diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index e6be07d9a1..5ca5ad32cd 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -22,6 +22,7 @@ #include "string-table.h" #include "string-util.h" #include "strv.h" +#include "tc.h" #include "util.h" /* Let's assume that anything above this number is a user misconfiguration. */ @@ -154,7 +155,7 @@ int network_verify(Network *network) { Prefix *prefix, *prefix_next; Route *route, *route_next; FdbEntry *fdb, *fdb_next; - QDisc *qdisc; + TrafficControl *tc; Iterator i; assert(network); @@ -316,9 +317,9 @@ int network_verify(Network *network) { 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); + ORDERED_HASHMAP_FOREACH(tc, network->tc_by_section, i) + if (traffic_control_section_verify(tc, &has_root, &has_clsact) < 0) + traffic_control_free(tc); return 0; } @@ -691,7 +692,7 @@ static Network *network_free(Network *network) { hashmap_free(network->prefixes_by_section); hashmap_free(network->route_prefixes_by_section); hashmap_free(network->rules_by_section); - ordered_hashmap_free_with_destructor(network->qdiscs_by_section, qdisc_free); + ordered_hashmap_free_with_destructor(network->tc_by_section, traffic_control_free); if (network->manager && network->manager->duids_requesting_uuid) diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 66f010a793..3ae1752acc 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -30,7 +30,6 @@ #include "networkd-routing-policy-rule.h" #include "networkd-util.h" #include "ordered-set.h" -#include "qdisc.h" #include "resolve-util.h" typedef enum IPv6PrivacyExtensions { @@ -274,7 +273,7 @@ struct Network { Hashmap *prefixes_by_section; Hashmap *route_prefixes_by_section; Hashmap *rules_by_section; - OrderedHashmap *qdiscs_by_section; + OrderedHashmap *tc_by_section; /* All kinds of DNS configuration */ struct in_addr_data *dns; diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c index 9f60888aa5..2168234a71 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -36,6 +36,7 @@ static int qdisc_new(QDiscKind kind, QDisc **ret) { return -ENOMEM; *qdisc = (QDisc) { + .meta.kind = TC_KIND_QDISC, .family = AF_UNSPEC, .parent = TC_H_ROOT, .kind = kind, @@ -45,6 +46,7 @@ static int qdisc_new(QDiscKind kind, QDisc **ret) { if (!qdisc) return -ENOMEM; + qdisc->meta.kind = TC_KIND_QDISC, qdisc->family = AF_UNSPEC; qdisc->parent = TC_H_ROOT; qdisc->kind = kind; @@ -64,7 +66,8 @@ static int qdisc_new(QDiscKind kind, QDisc **ret) { int qdisc_new_static(QDiscKind kind, Network *network, const char *filename, unsigned section_line, QDisc **ret) { _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; _cleanup_(qdisc_freep) QDisc *qdisc = NULL; - QDisc *existing; + TrafficControl *existing; + QDisc *q = NULL; int r; assert(network); @@ -76,15 +79,20 @@ int qdisc_new_static(QDiscKind kind, Network *network, const char *filename, uns if (r < 0) return r; - existing = ordered_hashmap_get(network->qdiscs_by_section, n); + existing = ordered_hashmap_get(network->tc_by_section, n); if (existing) { - if (existing->kind != _QDISC_KIND_INVALID && - kind != _QDISC_KIND_INVALID && - existing->kind != kind) + if (existing->kind != TC_KIND_QDISC) return -EINVAL; - if (existing->kind == kind || kind == _QDISC_KIND_INVALID) { - *ret = existing; + q = TC_TO_QDISC(existing); + + if (q->kind != _QDISC_KIND_INVALID && + kind != _QDISC_KIND_INVALID && + q->kind != kind) + return -EINVAL; + + if (q->kind == kind || kind == _QDISC_KIND_INVALID) { + *ret = q; return 0; } } @@ -93,23 +101,23 @@ int qdisc_new_static(QDiscKind kind, Network *network, const char *filename, uns if (r < 0) return r; - if (existing) { - qdisc->family = existing->family; - qdisc->handle = existing->handle; - qdisc->parent = existing->parent; - qdisc->tca_kind = TAKE_PTR(existing->tca_kind); + if (q) { + qdisc->family = q->family; + qdisc->handle = q->handle; + qdisc->parent = q->parent; + qdisc->tca_kind = TAKE_PTR(q->tca_kind); - qdisc_free(ordered_hashmap_remove(network->qdiscs_by_section, n)); + qdisc_free(q); } qdisc->network = network; qdisc->section = TAKE_PTR(n); - r = ordered_hashmap_ensure_allocated(&network->qdiscs_by_section, &network_config_hash_ops); + r = ordered_hashmap_ensure_allocated(&network->tc_by_section, &network_config_hash_ops); if (r < 0) return r; - r = ordered_hashmap_put(network->qdiscs_by_section, qdisc->section, qdisc); + r = ordered_hashmap_put(network->tc_by_section, qdisc->section, TC(qdisc)); if (r < 0) return r; @@ -122,7 +130,7 @@ void qdisc_free(QDisc *qdisc) { return; if (qdisc->network && qdisc->section) - ordered_hashmap_remove(qdisc->network->qdiscs_by_section, qdisc->section); + ordered_hashmap_remove(qdisc->network->tc_by_section, qdisc->section); network_config_section_free(qdisc->section); @@ -134,8 +142,8 @@ static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { int r; assert(link); - assert(link->qdisc_messages > 0); - link->qdisc_messages--; + assert(link->tc_messages > 0); + link->tc_messages--; if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) return 1; @@ -147,9 +155,9 @@ static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { return 1; } - if (link->qdisc_messages == 0) { - log_link_debug(link, "QDisc configured"); - link->qdiscs_configured = true; + if (link->tc_messages == 0) { + log_link_debug(link, "Traffic control configured"); + link->tc_configured = true; link_check_ready(link); } @@ -206,7 +214,7 @@ int qdisc_configure(Link *link, QDisc *qdisc) { return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); link_ref(link); - link->qdisc_messages++; + link->tc_messages++; return 0; } diff --git a/src/network/tc/qdisc.h b/src/network/tc/qdisc.h index 7d9e0ddb06..f33939d197 100644 --- a/src/network/tc/qdisc.h +++ b/src/network/tc/qdisc.h @@ -6,6 +6,7 @@ #include "networkd-link.h" #include "networkd-network.h" #include "networkd-util.h" +#include "tc.h" typedef enum QDiscKind { QDISC_KIND_CODEL, @@ -21,6 +22,8 @@ typedef enum QDiscKind { } QDiscKind; typedef struct QDisc { + TrafficControl meta; + NetworkConfigSection *section; Network *network; @@ -66,6 +69,8 @@ int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact); DEFINE_NETWORK_SECTION_FUNCTIONS(QDisc, qdisc_free); +DEFINE_TC_CAST(QDISC, QDisc); + CONFIG_PARSER_PROTOTYPE(config_parse_qdisc_parent); CONFIG_PARSER_PROTOTYPE(config_parse_qdisc_handle); diff --git a/src/network/tc/tc.c b/src/network/tc/tc.c new file mode 100644 index 0000000000..e90ebf30a8 --- /dev/null +++ b/src/network/tc/tc.c @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "macro.h" +#include "qdisc.h" +#include "tc.h" + +void traffic_control_free(TrafficControl *tc) { + if (!tc) + return; + + switch (tc->kind) { + case TC_KIND_QDISC: + qdisc_free(TC_TO_QDISC(tc)); + break; + default: + assert_not_reached("Invalid traffic control type"); + } +} + +int traffic_control_configure(Link *link, TrafficControl *tc) { + assert(link); + assert(tc); + + switch(tc->kind) { + case TC_KIND_QDISC: + return qdisc_configure(link, TC_TO_QDISC(tc)); + default: + assert_not_reached("Invalid traffic control type"); + } +} + +int traffic_control_section_verify(TrafficControl *tc, bool *qdisc_has_root, bool *qdisc_has_clsact) { + assert(tc); + + switch(tc->kind) { + case TC_KIND_QDISC: + return qdisc_section_verify(TC_TO_QDISC(tc), qdisc_has_root, qdisc_has_clsact); + default: + assert_not_reached("Invalid traffic control type"); + } +} diff --git a/src/network/tc/tc.h b/src/network/tc/tc.h new file mode 100644 index 0000000000..defa0b774a --- /dev/null +++ b/src/network/tc/tc.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include "networkd-link.h" + +typedef enum TrafficControlKind { + TC_KIND_QDISC, + TC_KIND_TCLASS, + TC_KIND_FILTER, + _TC_KIND_MAX, + _TC_KIND_INVALID = -1, +} TrafficControlKind; + +typedef struct TrafficControl { + TrafficControlKind kind; +} TrafficControl; + +/* For casting a tc into the various tc kinds */ +#define DEFINE_TC_CAST(UPPERCASE, MixedCase) \ + static inline MixedCase* TC_TO_##UPPERCASE(TrafficControl *tc) { \ + if (_unlikely_(!tc || tc->kind != TC_KIND_##UPPERCASE)) \ + return NULL; \ + \ + return (MixedCase*) tc; \ + } + +/* For casting the various tc kinds into a tc */ +#define TC(tc) (&(tc)->meta) + +void traffic_control_free(TrafficControl *tc); +int traffic_control_configure(Link *link, TrafficControl *tc); +int traffic_control_section_verify(TrafficControl *tc, bool *qdisc_has_root, bool *qdisc_has_clsact); From 0ebb76de8b4269a3f045797b8477dba921bf7278 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 11 Feb 2020 23:12:24 +0900 Subject: [PATCH 05/17] sd-netlink: introduce sd_rtnl_message_new_tclass() and friends --- src/libsystemd/sd-netlink/netlink-types.c | 17 +++++---- src/libsystemd/sd-netlink/netlink-util.h | 4 +++ src/libsystemd/sd-netlink/rtnl-message.c | 43 +++++++++++++++++++++++ src/systemd/sd-netlink.h | 4 +++ 4 files changed, 61 insertions(+), 7 deletions(-) diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c index d4e9873bba..42211ac0d2 100644 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -825,16 +825,16 @@ static const NLTypeSystemUnion rtnl_tca_option_data_type_system_union = { .match = TCA_KIND, }; -static const NLType rtnl_qdisc_types[] = { +static const NLType rtnl_tca_types[] = { [TCA_KIND] = { .type = NETLINK_TYPE_STRING }, [TCA_OPTIONS] = { .type = NETLINK_TYPE_UNION, .type_system_union = &rtnl_tca_option_data_type_system_union }, [TCA_INGRESS_BLOCK] = { .type = NETLINK_TYPE_U32 }, [TCA_EGRESS_BLOCK] = { .type = NETLINK_TYPE_U32 }, }; -static const NLTypeSystem rtnl_qdisc_type_system = { - .count = ELEMENTSOF(rtnl_qdisc_types), - .types = rtnl_qdisc_types, +static const NLTypeSystem rtnl_tca_type_system = { + .count = ELEMENTSOF(rtnl_tca_types), + .types = rtnl_tca_types, }; static const NLType error_types[] = { @@ -875,9 +875,12 @@ static const NLType rtnl_types[] = { [RTM_NEWNEXTHOP] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_nexthop_type_system, .size = sizeof(struct nhmsg) }, [RTM_DELNEXTHOP] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_nexthop_type_system, .size = sizeof(struct nhmsg) }, [RTM_GETNEXTHOP] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_nexthop_type_system, .size = sizeof(struct nhmsg) }, - [RTM_NEWQDISC] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_qdisc_type_system, .size = sizeof(struct tcmsg) }, - [RTM_DELQDISC] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_qdisc_type_system, .size = sizeof(struct tcmsg) }, - [RTM_GETQDISC] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_qdisc_type_system, .size = sizeof(struct tcmsg) }, + [RTM_NEWQDISC] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_tca_type_system, .size = sizeof(struct tcmsg) }, + [RTM_DELQDISC] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_tca_type_system, .size = sizeof(struct tcmsg) }, + [RTM_GETQDISC] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_tca_type_system, .size = sizeof(struct tcmsg) }, + [RTM_NEWTCLASS] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_tca_type_system, .size = sizeof(struct tcmsg) }, + [RTM_DELTCLASS] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_tca_type_system, .size = sizeof(struct tcmsg) }, + [RTM_GETTCLASS] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_tca_type_system, .size = sizeof(struct tcmsg) }, }; const NLTypeSystem rtnl_type_system_root = { diff --git a/src/libsystemd/sd-netlink/netlink-util.h b/src/libsystemd/sd-netlink/netlink-util.h index 55bc12712a..5414d17cd8 100644 --- a/src/libsystemd/sd-netlink/netlink-util.h +++ b/src/libsystemd/sd-netlink/netlink-util.h @@ -47,6 +47,10 @@ static inline bool rtnl_message_type_is_qdisc(uint16_t type) { return IN_SET(type, RTM_NEWQDISC, RTM_DELQDISC, RTM_GETQDISC); } +static inline bool rtnl_message_type_is_tclass(uint16_t type) { + return IN_SET(type, RTM_NEWTCLASS, RTM_DELTCLASS, RTM_GETTCLASS); +} + int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name); int rtnl_set_link_properties(sd_netlink **rtnl, int ifindex, const char *alias, const struct ether_addr *mac, uint32_t mtu); int rtnl_set_link_alternative_names(sd_netlink **rtnl, int ifindex, char * const *alternative_names); diff --git a/src/libsystemd/sd-netlink/rtnl-message.c b/src/libsystemd/sd-netlink/rtnl-message.c index 182a666746..7689bf6621 100644 --- a/src/libsystemd/sd-netlink/rtnl-message.c +++ b/src/libsystemd/sd-netlink/rtnl-message.c @@ -1077,3 +1077,46 @@ int sd_rtnl_message_set_qdisc_handle(sd_netlink_message *m, uint32_t handle) { return 0; } + +int sd_rtnl_message_new_tclass(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type, int tcm_family, int tcm_ifindex) { + struct tcmsg *tcm; + int r; + + assert_return(rtnl_message_type_is_tclass(nlmsg_type), -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_NEWTCLASS) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; + + tcm = NLMSG_DATA((*ret)->hdr); + tcm->tcm_family = tcm_family; + tcm->tcm_ifindex = tcm_ifindex; + + return 0; +} + +int sd_rtnl_message_set_tclass_parent(sd_netlink_message *m, uint32_t parent) { + struct tcmsg *tcm; + + assert_return(rtnl_message_type_is_tclass(m->hdr->nlmsg_type), -EINVAL); + + tcm = NLMSG_DATA(m->hdr); + tcm->tcm_parent = parent; + + return 0; +} + +int sd_rtnl_message_set_tclass_handle(sd_netlink_message *m, uint32_t handle) { + struct tcmsg *tcm; + + assert_return(rtnl_message_type_is_tclass(m->hdr->nlmsg_type), -EINVAL); + + tcm = NLMSG_DATA(m->hdr); + tcm->tcm_handle = handle; + + return 0; +} diff --git a/src/systemd/sd-netlink.h b/src/systemd/sd-netlink.h index 644d462b65..ff06dc0cac 100644 --- a/src/systemd/sd-netlink.h +++ b/src/systemd/sd-netlink.h @@ -208,6 +208,10 @@ int sd_rtnl_message_new_qdisc(sd_netlink *rtnl, sd_netlink_message **ret, uint16 int sd_rtnl_message_set_qdisc_parent(sd_netlink_message *m, uint32_t parent); int sd_rtnl_message_set_qdisc_handle(sd_netlink_message *m, uint32_t handle); +int sd_rtnl_message_new_tclass(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type, int tcm_family, int tcm_ifindex); +int sd_rtnl_message_set_tclass_parent(sd_netlink_message *m, uint32_t parent); +int sd_rtnl_message_set_tclass_handle(sd_netlink_message *m, uint32_t handle); + /* genl */ int sd_genl_socket_open(sd_netlink **nl); int sd_genl_message_new(sd_netlink *nl, sd_genl_family family, uint8_t cmd, sd_netlink_message **m); From 4666f63bb815a279d7ef19f691da7f410da59bd4 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 11 Feb 2020 23:52:24 +0900 Subject: [PATCH 06/17] network: introduce struct TClass to prepare for supporting tc class --- src/network/meson.build | 2 + src/network/networkd-network-gperf.gperf | 1 + src/network/tc/tc.c | 8 + src/network/tc/tclass.c | 276 +++++++++++++++++++++++ src/network/tc/tclass.h | 67 ++++++ 5 files changed, 354 insertions(+) create mode 100644 src/network/tc/tclass.c create mode 100644 src/network/tc/tclass.h diff --git a/src/network/meson.build b/src/network/meson.build index ce0e7fb0dd..407ec246c1 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -127,6 +127,8 @@ sources = files(''' tc/tc-util.h tc/tc.c tc/tc.h + tc/tclass.c + tc/tclass.h tc/teql.c tc/teql.h '''.split()) diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 4ebf285a6e..4f8a654cc3 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -14,6 +14,7 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") #include "networkd-ndisc.h" #include "networkd-network.h" #include "qdisc.h" +#include "tclass.h" #include "vlan-util.h" %} struct ConfigPerfItem; diff --git a/src/network/tc/tc.c b/src/network/tc/tc.c index e90ebf30a8..30a00133d6 100644 --- a/src/network/tc/tc.c +++ b/src/network/tc/tc.c @@ -3,6 +3,7 @@ #include "macro.h" #include "qdisc.h" #include "tc.h" +#include "tclass.h" void traffic_control_free(TrafficControl *tc) { if (!tc) @@ -12,6 +13,9 @@ void traffic_control_free(TrafficControl *tc) { case TC_KIND_QDISC: qdisc_free(TC_TO_QDISC(tc)); break; + case TC_KIND_TCLASS: + tclass_free(TC_TO_TCLASS(tc)); + break; default: assert_not_reached("Invalid traffic control type"); } @@ -24,6 +28,8 @@ int traffic_control_configure(Link *link, TrafficControl *tc) { switch(tc->kind) { case TC_KIND_QDISC: return qdisc_configure(link, TC_TO_QDISC(tc)); + case TC_KIND_TCLASS: + return tclass_configure(link, TC_TO_TCLASS(tc)); default: assert_not_reached("Invalid traffic control type"); } @@ -35,6 +41,8 @@ int traffic_control_section_verify(TrafficControl *tc, bool *qdisc_has_root, boo switch(tc->kind) { case TC_KIND_QDISC: return qdisc_section_verify(TC_TO_QDISC(tc), qdisc_has_root, qdisc_has_clsact); + case TC_KIND_TCLASS: + return tclass_section_verify(TC_TO_TCLASS(tc)); default: assert_not_reached("Invalid traffic control type"); } diff --git a/src/network/tc/tclass.c b/src/network/tc/tclass.c new file mode 100644 index 0000000000..236437929e --- /dev/null +++ b/src/network/tc/tclass.c @@ -0,0 +1,276 @@ +/* SPDX-License-Identifier: LGPL-2.1+ + * Copyright © 2019 VMware, Inc. */ + +#include + +#include "alloc-util.h" +#include "conf-parser.h" +#include "in-addr-util.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "parse-util.h" +#include "set.h" +#include "string-util.h" +#include "strv.h" +#include "tc-util.h" +#include "tclass.h" + +const TClassVTable * const tclass_vtable[_TCLASS_KIND_MAX] = { +}; + +static int tclass_new(TClassKind kind, TClass **ret) { + TClass *tclass; + int r; + + tclass = malloc0(tclass_vtable[kind]->object_size); + if (!tclass) + return -ENOMEM; + + tclass->meta.kind = TC_KIND_TCLASS, + tclass->parent = TC_H_ROOT; + tclass->kind = kind; + + if (TCLASS_VTABLE(tclass)->init) { + r = TCLASS_VTABLE(tclass)->init(tclass); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(tclass); + + return 0; +} + +int tclass_new_static(TClassKind kind, Network *network, const char *filename, unsigned section_line, TClass **ret) { + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(tclass_freep) TClass *tclass = NULL; + TrafficControl *existing; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + existing = ordered_hashmap_get(network->tc_by_section, n); + if (existing) { + TClass *t; + + if (existing->kind != TC_KIND_TCLASS) + return -EINVAL; + + t = TC_TO_TCLASS(existing); + + if (t->kind != kind) + return -EINVAL; + + *ret = t; + return 0; + } + + r = tclass_new(kind, &tclass); + if (r < 0) + return r; + + tclass->network = network; + tclass->section = TAKE_PTR(n); + + r = ordered_hashmap_ensure_allocated(&network->tc_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_put(network->tc_by_section, tclass->section, tclass); + if (r < 0) + return r; + + *ret = TAKE_PTR(tclass); + return 0; +} + +void tclass_free(TClass *tclass) { + if (!tclass) + return; + + if (tclass->network && tclass->section) + ordered_hashmap_remove(tclass->network->tc_by_section, tclass->section); + + network_config_section_free(tclass->section); + + free(tclass); +} + +static int tclass_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + assert(link->tc_messages > 0); + link->tc_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_message_error_errno(link, m, r, "Could not set TClass"); + link_enter_failed(link); + return 1; + } + + if (link->tc_messages == 0) { + log_link_debug(link, "Traffic control configured"); + link->tc_configured = true; + link_check_ready(link); + } + + return 1; +} + +int tclass_configure(Link *link, TClass *tclass) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + assert(link->ifindex > 0); + + r = sd_rtnl_message_new_tclass(link->manager->rtnl, &req, RTM_NEWTCLASS, AF_UNSPEC, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not create RTM_NEWTCLASS message: %m"); + + r = sd_rtnl_message_set_tclass_parent(req, tclass->parent); + if (r < 0) + return log_link_error_errno(link, r, "Could not create tcm_parent message: %m"); + + if (tclass->classid != TC_H_UNSPEC) { + r = sd_rtnl_message_set_tclass_handle(req, tclass->classid); + if (r < 0) + return log_link_error_errno(link, r, "Could not set tcm_handle message: %m"); + } + + r = sd_netlink_message_append_string(req, TCA_KIND, TCLASS_VTABLE(tclass)->tca_kind); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_KIND attribute: %m"); + + if (TCLASS_VTABLE(tclass)->fill_message) { + r = TCLASS_VTABLE(tclass)->fill_message(link, tclass, req); + if (r < 0) + return r; + } + + r = netlink_call_async(link->manager->rtnl, NULL, req, tclass_handler, link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + link->tc_messages++; + + return 0; +} + +int tclass_section_verify(TClass *tclass) { + int r; + + assert(tclass); + + if (section_is_invalid(tclass->section)) + return -EINVAL; + + if (TCLASS_VTABLE(tclass)->verify) { + r = TCLASS_VTABLE(tclass)->verify(tclass); + if (r < 0) + return r; + } + + return 0; +} + +int config_parse_tclass_parent( + 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_(tclass_free_or_set_invalidp) TClass *tclass = NULL; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = tclass_new_static(ltype, network, filename, section_line, &tclass); + if (r < 0) + return r; + + if (streq(rvalue, "root")) + tclass->parent = TC_H_ROOT; + else { + r = parse_handle(rvalue, &tclass->parent); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to parse 'Parent=', ignoring assignment: %s", + rvalue); + return 0; + } + } + + tclass = NULL; + + return 0; +} + +int config_parse_tclass_classid( + 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_(tclass_free_or_set_invalidp) TClass *tclass = NULL; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = tclass_new_static(ltype, network, filename, section_line, &tclass); + if (r < 0) + return r; + + if (isempty(rvalue)) { + tclass->classid = TC_H_UNSPEC; + tclass = NULL; + return 0; + } + + r = parse_handle(rvalue, &tclass->classid); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to parse 'ClassId=', ignoring assignment: %s", + rvalue); + return 0; + } + + tclass = NULL; + + return 0; +} diff --git a/src/network/tc/tclass.h b/src/network/tc/tclass.h new file mode 100644 index 0000000000..9698e582fa --- /dev/null +++ b/src/network/tc/tclass.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: LGPL-2.1+ + * Copyright © 2019 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "networkd-link.h" +#include "networkd-network.h" +#include "networkd-util.h" +#include "tc.h" + +typedef enum TClassKind { + TCLASS_KIND_HTB, + _TCLASS_KIND_MAX, + _TCLASS_KIND_INVALID = -1, +} TClassKind; + +typedef struct TClass { + TrafficControl meta; + + NetworkConfigSection *section; + Network *network; + + uint32_t classid; + uint32_t parent; + + TClassKind kind; +} TClass; + +typedef struct TClassVTable { + size_t object_size; + const char *tca_kind; + /* called in tclass_new() */ + int (*init)(TClass *tclass); + int (*fill_message)(Link *link, TClass *tclass, sd_netlink_message *m); + int (*verify)(TClass *tclass); +} TClassVTable; + +extern const TClassVTable * const tclass_vtable[_TCLASS_KIND_MAX]; + +#define TCLASS_VTABLE(t) ((t)->kind != _TCLASS_KIND_INVALID ? tclass_vtable[(t)->kind] : NULL) + +/* For casting a tclass into the various tclass kinds */ +#define DEFINE_TCLASS_CAST(UPPERCASE, MixedCase) \ + static inline MixedCase* TCLASS_TO_##UPPERCASE(TClass *t) { \ + if (_unlikely_(!t || t->kind != TCLASS_KIND_##UPPERCASE)) \ + return NULL; \ + \ + return (MixedCase*) t; \ + } + +/* For casting the various tclass kinds into a tclass */ +#define TCLASS(t) (&(t)->meta) + +void tclass_free(TClass *tclass); +int tclass_new_static(TClassKind kind, Network *network, const char *filename, unsigned section_line, TClass **ret); + +int tclass_configure(Link *link, TClass *tclass); +int tclass_section_verify(TClass *tclass); + +DEFINE_NETWORK_SECTION_FUNCTIONS(TClass, tclass_free); + +DEFINE_TC_CAST(TCLASS, TClass); + +CONFIG_PARSER_PROTOTYPE(config_parse_tclass_parent); +CONFIG_PARSER_PROTOTYPE(config_parse_tclass_classid); + +#include "htb.h" From 19f86a63515715387e367e99f76c8ecce4405adb Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 12 Feb 2020 09:52:56 +0900 Subject: [PATCH 07/17] network: tc: support HTB class --- man/systemd.network.xml | 54 ++++++ src/libsystemd/sd-netlink/netlink-types.c | 7 +- src/network/networkd-network-gperf.gperf | 5 + src/network/networkd-network.c | 1 + src/network/tc/htb.c | 181 ++++++++++++++++++ src/network/tc/htb.h | 15 ++ src/network/tc/tclass.c | 1 + .../fuzz-network-parser/directives.network | 6 + 8 files changed, 269 insertions(+), 1 deletion(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 2e5ac9a28b..27c837d25c 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2832,6 +2832,60 @@ + + [HierarchyTokenBucketClass] Section Options + The [HierarchyTokenBucketClass] section manages the traffic control class of + hierarchy token bucket (htb). + + + + Parent= + + Specifies the parent Queueing Discipline (qdisc). Takes one of root, + or a qdisc id. The qdisc id takes the major and minor number in hexadecimal ranges 1 to ffff + separated with a colon (major:minor). Defaults to root. + + + + + + ClassId= + + Specifies the major and minur number of unique identifier of the class, known as the + class ID. Each number is in hexadecimal ranges 1 to ffff. Defaults to unset. + + + + + Priority= + + Specifies the priority of the class. In the round-robin process, classes with the lowest + priority field are tried for packets first. This setting is mandatory. + + + + + Rate= + + Specifies the maximum rate this class and all its children are guaranteed. When suffixed + with K, M, or G, the specified size is parsed as Kilobits, Megabits, or Gigabits, respectively, + to the base of 1000. This setting is mandatory. + + + + + CeilRate= + + Specifies the maximum rate at which a class can send, if its parent has bandwidth to spare. + When suffixed with K, M, or G, the specified size is parsed as Kilobits, Megabits, or Gigabits, + respectively, to the base of 1000. When unset, the value specified with Rate= + is used. + + + + + + [BridgeVLAN] Section Options The [BridgeVLAN] section manages the VLAN ID configuration of a bridge port and accepts diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c index 42211ac0d2..ebfb47fc5d 100644 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -781,7 +781,12 @@ static const NLType rtnl_tca_option_data_fq_codel_types[] = { }; static const NLType rtnl_tca_option_data_htb_types[] = { - [TCA_HTB_INIT] = { .size = sizeof(struct tc_htb_glob) }, + [TCA_HTB_PARMS] = { .size = sizeof(struct tc_htb_opt) }, + [TCA_HTB_INIT] = { .size = sizeof(struct tc_htb_glob) }, + [TCA_HTB_CTAB] = { .size = TC_RTAB_SIZE }, + [TCA_HTB_RTAB] = { .size = TC_RTAB_SIZE }, + [TCA_HTB_RATE64] = { .type = NETLINK_TYPE_U64 }, + [TCA_HTB_CEIL64] = { .type = NETLINK_TYPE_U64 }, }; static const NLType rtnl_tca_option_data_tbf_types[] = { diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 4f8a654cc3..3081de878f 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -285,6 +285,11 @@ FairQueueingControlledDelay.ECN, config_parse_fair_queueing_controll HierarchyTokenBucket.Parent, config_parse_qdisc_parent, QDISC_KIND_HTB, 0 HierarchyTokenBucket.Handle, config_parse_qdisc_handle, QDISC_KIND_HTB, 0 HierarchyTokenBucket.DefaultClass, config_parse_hierarchy_token_bucket_default_class, QDISC_KIND_HTB, 0 +HierarchyTokenBucketClass.Parent, config_parse_tclass_parent, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.ClassId, config_parse_tclass_classid, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.Priority, config_parse_hierarchy_token_bucket_u32, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.Rate, config_parse_hierarchy_token_bucket_rate, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.CeilRate, config_parse_hierarchy_token_bucket_rate, TCLASS_KIND_HTB, 0 NetworkEmulator.Parent, config_parse_qdisc_parent, QDISC_KIND_NETEM, 0 NetworkEmulator.Handle, config_parse_qdisc_handle, QDISC_KIND_NETEM, 0 NetworkEmulator.DelaySec, config_parse_network_emulator_delay, QDISC_KIND_NETEM, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 5ca5ad32cd..4774132d10 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -488,6 +488,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi "FairQueueing\0" "FairQueueingControlledDelay\0" "HierarchyTokenBucket\0" + "HierarchyTokenBucketClass\0" "NetworkEmulator\0" "StochasticFairnessQueueing\0" "TokenBucketFilter\0" diff --git a/src/network/tc/htb.c b/src/network/tc/htb.c index 06dd5cbc78..f2b9c4507e 100644 --- a/src/network/tc/htb.c +++ b/src/network/tc/htb.c @@ -9,6 +9,7 @@ #include "qdisc.h" #include "htb.h" #include "string-util.h" +#include "tc-util.h" static int hierarchy_token_bucket_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { HierarchyTokenBucket *htb; @@ -96,3 +97,183 @@ const QDiscVTable htb_vtable = { .tca_kind = "htb", .fill_message = hierarchy_token_bucket_fill_message, }; + +static int hierarchy_token_bucket_class_fill_message(Link *link, TClass *tclass, sd_netlink_message *req) { + HierarchyTokenBucketClass *htb; + struct tc_htb_opt opt = {}; + uint32_t rtab[256], ctab[256], mtu = 1600; /* Ethernet packet length */ + int r; + + assert(link); + assert(tclass); + assert(req); + + htb = TCLASS_TO_HTB(tclass); + + if (htb->ceil_rate == 0) + htb->ceil_rate = htb->rate; + + opt.prio = htb->priority; + opt.rate.rate = (htb->rate >= (1ULL << 32)) ? ~0U : htb->rate; + opt.ceil.rate = (htb->ceil_rate >= (1ULL << 32)) ? ~0U : htb->ceil_rate; + r = tc_transmit_time(htb->rate, mtu, &opt.buffer); + if (r < 0) + return log_link_error_errno(link, r, "Failed to calculate buffer size: %m"); + + r = tc_transmit_time(htb->ceil_rate, mtu, &opt.cbuffer); + if (r < 0) + return log_link_error_errno(link, r, "Failed to calculate ceil buffer size: %m"); + + r = tc_fill_ratespec_and_table(&opt.rate, rtab, mtu); + if (r < 0) + return log_link_error_errno(link, r, "Failed to calculate rate table: %m"); + + r = tc_fill_ratespec_and_table(&opt.ceil, ctab, mtu); + if (r < 0) + return log_link_error_errno(link, r, "Failed to calculate ceil rate table: %m"); + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "htb"); + 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_HTB_PARMS, &opt, sizeof(opt)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_HTB_PARMS attribute: %m"); + + if (htb->rate >= (1ULL << 32)) { + r = sd_netlink_message_append_u64(req, TCA_HTB_RATE64, htb->rate); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_HTB_RATE64 attribute: %m"); + } + + if (htb->ceil_rate >= (1ULL << 32)) { + r = sd_netlink_message_append_u64(req, TCA_HTB_CEIL64, htb->ceil_rate); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_HTB_CEIL64 attribute: %m"); + } + + r = sd_netlink_message_append_data(req, TCA_HTB_RTAB, rtab, sizeof(rtab)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_HTB_RTAB attribute: %m"); + + r = sd_netlink_message_append_data(req, TCA_HTB_CTAB, ctab, sizeof(ctab)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_HTB_CTAB 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_hierarchy_token_bucket_u32( + 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_(tclass_free_or_set_invalidp) TClass *tclass = NULL; + HierarchyTokenBucketClass *htb; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = tclass_new_static(TCLASS_KIND_HTB, network, filename, section_line, &tclass); + if (r < 0) + return log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to create traffic control class, ignoring assignment: %m"); + + htb = TCLASS_TO_HTB(tclass); + + if (isempty(rvalue)) { + htb->priority = 0; + + tclass = NULL; + return 0; + } + + r = safe_atou32(rvalue, &htb->priority); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + tclass = NULL; + + return 0; +} + +int config_parse_hierarchy_token_bucket_rate( + 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_(tclass_free_or_set_invalidp) TClass *tclass = NULL; + HierarchyTokenBucketClass *htb; + Network *network = data; + uint64_t *v; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = tclass_new_static(TCLASS_KIND_HTB, network, filename, section_line, &tclass); + if (r < 0) + return log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to create traffic control class, ignoring assignment: %m"); + + htb = TCLASS_TO_HTB(tclass); + if (streq(lvalue, "Rate")) + v = &htb->rate; + else if (streq(lvalue, "CeilRate")) + v = &htb->ceil_rate; + else + assert_not_reached("Invalid lvalue"); + + if (isempty(rvalue)) { + *v = 0; + + tclass = NULL; + return 0; + } + + r = parse_size(rvalue, 1000, v); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + *v /= 8; + tclass = NULL; + + return 0; +} + +const TClassVTable htb_tclass_vtable = { + .object_size = sizeof(HierarchyTokenBucketClass), + .tca_kind = "htb", + .fill_message = hierarchy_token_bucket_class_fill_message, +}; diff --git a/src/network/tc/htb.h b/src/network/tc/htb.h index 6b5ef8cfb4..c8dce2c1e3 100644 --- a/src/network/tc/htb.h +++ b/src/network/tc/htb.h @@ -3,6 +3,7 @@ #include "conf-parser.h" #include "qdisc.h" +#include "tclass.h" typedef struct HierarchyTokenBucket { QDisc meta; @@ -14,3 +15,17 @@ DEFINE_QDISC_CAST(HTB, HierarchyTokenBucket); extern const QDiscVTable htb_vtable; CONFIG_PARSER_PROTOTYPE(config_parse_hierarchy_token_bucket_default_class); + +typedef struct HierarchyTokenBucketClass { + TClass meta; + + uint32_t priority; + uint64_t rate; + uint64_t ceil_rate; +} HierarchyTokenBucketClass; + +DEFINE_TCLASS_CAST(HTB, HierarchyTokenBucketClass); +extern const TClassVTable htb_tclass_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_hierarchy_token_bucket_u32); +CONFIG_PARSER_PROTOTYPE(config_parse_hierarchy_token_bucket_rate); diff --git a/src/network/tc/tclass.c b/src/network/tc/tclass.c index 236437929e..87f5bcf704 100644 --- a/src/network/tc/tclass.c +++ b/src/network/tc/tclass.c @@ -16,6 +16,7 @@ #include "tclass.h" const TClassVTable * const tclass_vtable[_TCLASS_KIND_MAX] = { + [TCLASS_KIND_HTB] = &htb_tclass_vtable, }; static int tclass_new(TClassKind kind, TClass **ret) { diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 06e971110a..6d1107ee9e 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -341,3 +341,9 @@ Id= Parent= Handle= DefaultClass= +[HierarchyTokenBucketClass] +Parent= +ClassId= +Priority= +Rate= +CeilRate= From f1de1eb3e317c16fcf157e222e9ae4bbaef1beab Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 12 Feb 2020 09:53:15 +0900 Subject: [PATCH 08/17] test-network: add test case for HTB class --- .../conf/25-qdisc-clsact-and-htb.network | 124 ++++++++++++++++++ .../conf/25-qdisc-clsact-root-compat.network | 12 -- .../conf/25-qdisc-fq-codel.network | 27 ---- .../conf/25-qdisc-ingress-root.network | 12 -- .../conf/25-qdisc-netem-and-fqcodel.network | 25 ---- .../conf/25-qdisc-tbf-and-sfq.network | 19 --- .../conf/25-qdisc-teql-and-htb.network | 15 --- test/test-network/systemd-networkd-tests.py | 101 ++++++-------- 8 files changed, 166 insertions(+), 169 deletions(-) create mode 100644 test/test-network/conf/25-qdisc-clsact-and-htb.network delete mode 100644 test/test-network/conf/25-qdisc-clsact-root-compat.network delete mode 100644 test/test-network/conf/25-qdisc-fq-codel.network delete mode 100644 test/test-network/conf/25-qdisc-ingress-root.network delete mode 100644 test/test-network/conf/25-qdisc-netem-and-fqcodel.network delete mode 100644 test/test-network/conf/25-qdisc-tbf-and-sfq.network delete mode 100644 test/test-network/conf/25-qdisc-teql-and-htb.network diff --git a/test/test-network/conf/25-qdisc-clsact-and-htb.network b/test/test-network/conf/25-qdisc-clsact-and-htb.network new file mode 100644 index 0000000000..c8757aec99 --- /dev/null +++ b/test/test-network/conf/25-qdisc-clsact-and-htb.network @@ -0,0 +1,124 @@ +[Match] +Name=dummy98 + +[Network] +IPv6AcceptRA=no +Address=10.1.2.3/16 + +[QDisc] +Parent=clsact + +[HierarchyTokenBucket] +Parent=root +Handle=0002 +DefaultClass=30 + +[HierarchyTokenBucketClass] +Parent=root +ClassId=0002:0030 +Priority=1 +Rate=1M +CeilRate=0.5M + +[NetworkEmulator] +Parent=2:30 +Handle=0030 +DelaySec=50ms +DelayJitterSec=10ms +LossRate=20% +PacketLimit=100 + +[HierarchyTokenBucketClass] +Parent=root +ClassId=0002:0031 +Priority=1 +Rate=1M +CeilRate=0.5M + +[TrivialLinkEqualizer] +Parent=2:31 +Handle=0031 +Id=1 + +[HierarchyTokenBucketClass] +Parent=root +ClassId=0002:0032 +Priority=1 +Rate=1M +CeilRate=0.5M + +[FairQueueing] +Parent=2:32 +Handle=0032 +PacketLimit=1000 +FlowLimit=200 +Quantum=1500 +InitialQuantum=13000 +MaximumRate=1M +Buckets=512 +OrphanMask=511 +Pacing=yes +CEThresholdSec=100ms + +[HierarchyTokenBucketClass] +Parent=root +ClassId=0002:0033 +Priority=1 +Rate=1M +CeilRate=0.5M + +[ControlledDelay] +Parent=2:33 +Handle=0033 +PacketLimit=2000 +TargetSec=10ms +IntervalSec=50ms +ECN=yes +CEThresholdSec=100ms + +[HierarchyTokenBucketClass] +Parent=root +ClassId=0002:0034 +Priority=1 +Rate=1M +CeilRate=0.5M + +[FairQueueingControlledDelay] +Parent=2:34 +Handle=0034 +PacketLimit=20480 +MemoryLimit=64M +Flows=2048 +TargetSec=10ms +IntervalSec=200ms +Quantum=1400 +ECN=yes +CEThresholdSec=100ms + +[HierarchyTokenBucketClass] +Parent=root +ClassId=0002:0035 +Priority=1 +Rate=1M +CeilRate=0.5M + +[TokenBucketFilter] +Parent=2:35 +Handle=0035 +Rate=1G +Burst=5K +LatencySec=70msec +PeakRate=100G +MTUBytes=1M + +[HierarchyTokenBucketClass] +Parent=root +ClassId=0002:0036 +Priority=1 +Rate=1M +CeilRate=0.5M + +[StochasticFairnessQueueing] +Parent=2:36 +Handle=0036 +PerturbPeriodSec=5sec diff --git a/test/test-network/conf/25-qdisc-clsact-root-compat.network b/test/test-network/conf/25-qdisc-clsact-root-compat.network deleted file mode 100644 index 1f0dea46fb..0000000000 --- a/test/test-network/conf/25-qdisc-clsact-root-compat.network +++ /dev/null @@ -1,12 +0,0 @@ -[Match] -Name=dummy98 - -[Network] -IPv6AcceptRA=no -Address=10.1.2.3/16 - -#[TrafficControlQueueingDiscipline] -#Parent=root - -[TrafficControlQueueingDiscipline] -Parent=clsact diff --git a/test/test-network/conf/25-qdisc-fq-codel.network b/test/test-network/conf/25-qdisc-fq-codel.network deleted file mode 100644 index 42c8dfa7f9..0000000000 --- a/test/test-network/conf/25-qdisc-fq-codel.network +++ /dev/null @@ -1,27 +0,0 @@ -[Match] -Name=dummy98 - -[Network] -IPv6AcceptRA=no -Address=10.1.2.3/16 - -[FairQueueing] -Parent=root -Handle=0003 -PacketLimit=1000 -FlowLimit=200 -Quantum=1500 -InitialQuantum=13000 -MaximumRate=1M -Buckets=512 -OrphanMask=511 -Pacing=yes -CEThresholdSec=100ms - -[ControlledDelay] -Parent=clsact -PacketLimit=2000 -TargetSec=10ms -IntervalSec=50ms -ECN=yes -CEThresholdSec=100ms diff --git a/test/test-network/conf/25-qdisc-ingress-root.network b/test/test-network/conf/25-qdisc-ingress-root.network deleted file mode 100644 index f72b70114b..0000000000 --- a/test/test-network/conf/25-qdisc-ingress-root.network +++ /dev/null @@ -1,12 +0,0 @@ -[Match] -Name=test1 - -[Network] -IPv6AcceptRA=no -Address=10.1.2.4/16 - -#[QDisc] -#Parent=root - -[QDisc] -Parent=ingress diff --git a/test/test-network/conf/25-qdisc-netem-and-fqcodel.network b/test/test-network/conf/25-qdisc-netem-and-fqcodel.network deleted file mode 100644 index de03d0d887..0000000000 --- a/test/test-network/conf/25-qdisc-netem-and-fqcodel.network +++ /dev/null @@ -1,25 +0,0 @@ -[Match] -Name=dummy98 - -[Network] -IPv6AcceptRA=no -Address=10.1.2.3/16 - -[NetworkEmulator] -Parent=root -Handle=001f -DelaySec=50ms -DelayJitterSec=10ms -LossRate=20% -PacketLimit=100 - -[FairQueueingControlledDelay] -Parent=ingress -PacketLimit=20480 -MemoryLimit=64M -Flows=2048 -TargetSec=10ms -IntervalSec=200ms -Quantum=1400 -ECN=yes -CEThresholdSec=100ms diff --git a/test/test-network/conf/25-qdisc-tbf-and-sfq.network b/test/test-network/conf/25-qdisc-tbf-and-sfq.network deleted file mode 100644 index c960886b8e..0000000000 --- a/test/test-network/conf/25-qdisc-tbf-and-sfq.network +++ /dev/null @@ -1,19 +0,0 @@ -[Match] -Name=test1 - -[Network] -IPv6AcceptRA=no -Address=10.1.2.4/16 - -[TokenBucketFilter] -Parent=root -Handle=003f -Rate=1G -Burst=5K -LatencySec=70msec -PeakRate=100G -MTUBytes=1M - -[StochasticFairnessQueueing] -Parent=clsact -PerturbPeriodSec=5sec diff --git a/test/test-network/conf/25-qdisc-teql-and-htb.network b/test/test-network/conf/25-qdisc-teql-and-htb.network deleted file mode 100644 index d6e7201326..0000000000 --- a/test/test-network/conf/25-qdisc-teql-and-htb.network +++ /dev/null @@ -1,15 +0,0 @@ -[Match] -Name=dummy98 - -[Network] -IPv6AcceptRA=no -Address=10.1.2.3/16 - -[TrivialLinkEqualizer] -Parent=ingress -Id=1 - -[HierarchyTokenBucket] -Parent=root -Handle=0002 -DefaultClass=30 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index b1af0bc291..5ff69d7222 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -1620,13 +1620,8 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): '25-neighbor-ip-dummy.network', '25-neighbor-ip.network', '25-nexthop.network', - '25-qdisc-clsact-root-compat.network', - '25-qdisc-fq-codel.network', + '25-qdisc-clsact-and-htb.network', '25-qdisc-ingress-netem-compat.network', - '25-qdisc-ingress-root.network', - '25-qdisc-netem-and-fqcodel.network', - '25-qdisc-tbf-and-sfq.network', - '25-qdisc-teql-and-htb.network', '25-route-ipv6-src.network', '25-route-static.network', '25-route-vrf.network', @@ -2253,74 +2248,62 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): self.assertRegex(output, '192.168.5.1') def test_qdisc(self): - copy_unit_to_networkd_unit_path('25-qdisc-netem-and-fqcodel.network', '12-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 1f:') - self.assertRegex(output, 'limit 100 delay 50.0ms 10.0ms loss 20%') - self.assertRegex(output, 'qdisc fq_codel') - self.assertRegex(output, 'limit 20480p flows 2048 quantum 1400 target 10.0ms ce_threshold 100.0ms interval 200.0ms memory_limit 64Mb ecn') - output = check_output('tc qdisc show dev test1') - print(output) - self.assertRegex(output, 'qdisc tbf 3f:') - self.assertRegex(output, 'rate 1Gbit burst 5000b peakrate 100Gbit minburst 987500b lat 70.0ms') - self.assertRegex(output, 'qdisc sfq') - self.assertRegex(output, 'perturb 5sec') - - def test_qdisc2(self): - copy_unit_to_networkd_unit_path('25-qdisc-fq-codel.network', '12-dummy.netdev', - '25-qdisc-ingress-root.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 fq 3:') - self.assertRegex(output, 'limit 1000p flow_limit 200p buckets 512 orphan_mask 511') - self.assertRegex(output, 'quantum 1500') - self.assertRegex(output, 'initial_quantum 13000') - self.assertRegex(output, 'maxrate 1Mbit') - self.assertRegex(output, 'qdisc codel') - self.assertRegex(output, 'limit 2000p target 10.0ms ce_threshold 100.0ms interval 50.0ms ecn') - output = check_output('tc qdisc show dev test1') - print(output) - self.assertRegex(output, 'qdisc ingress') - - def test_qdisc3(self): - copy_unit_to_networkd_unit_path('25-qdisc-clsact-root-compat.network', '12-dummy.netdev', + copy_unit_to_networkd_unit_path('25-qdisc-clsact-and-htb.network', '12-dummy.netdev', '25-qdisc-ingress-netem-compat.network', '11-dummy.netdev') + check_output('modprobe sch_teql max_equalizers=2') start_networkd() self.wait_online(['dummy98:routable', 'test1:routable']) - output = check_output('tc qdisc show dev dummy98') - print(output) - self.assertRegex(output, 'qdisc clsact') output = check_output('tc qdisc show dev test1') print(output) self.assertRegex(output, 'qdisc netem') self.assertRegex(output, 'limit 100 delay 50.0ms 10.0ms loss 20%') self.assertRegex(output, 'qdisc ingress') - def test_qdisc4(self): - copy_unit_to_networkd_unit_path('25-qdisc-teql-and-htb.network', '12-dummy.netdev') - check_output('modprobe sch_teql max_equalizers=2') - start_networkd() - - self.wait_online(['dummy98:routable']) - output = check_output('tc qdisc show dev dummy98') print(output) - self.assertRegex(output, 'qdisc teql1') - self.assertRegex(output, 'qdisc htb 2:') + self.assertRegex(output, 'qdisc clsact') + + self.assertRegex(output, 'qdisc htb 2: root') self.assertRegex(output, r'default (0x30|30)') + self.assertRegex(output, 'qdisc netem 30: parent 2:30') + self.assertRegex(output, 'limit 100 delay 50.0ms 10.0ms loss 20%') + self.assertRegex(output, 'qdisc fq_codel') + self.assertRegex(output, 'limit 20480p flows 2048 quantum 1400 target 10.0ms ce_threshold 100.0ms interval 200.0ms memory_limit 64Mb ecn') + + self.assertRegex(output, 'qdisc teql1 31: parent 2:31') + + self.assertRegex(output, 'qdisc fq 32: parent 2:32') + self.assertRegex(output, 'limit 1000p flow_limit 200p buckets 512 orphan_mask 511') + self.assertRegex(output, 'quantum 1500') + self.assertRegex(output, 'initial_quantum 13000') + self.assertRegex(output, 'maxrate 1Mbit') + + self.assertRegex(output, 'qdisc codel 33: parent 2:33') + self.assertRegex(output, 'limit 2000p target 10.0ms ce_threshold 100.0ms interval 50.0ms ecn') + + self.assertRegex(output, 'qdisc fq_codel 34: parent 2:34') + self.assertRegex(output, 'limit 20480p flows 2048 quantum 1400 target 10.0ms ce_threshold 100.0ms interval 200.0ms memory_limit 64Mb ecn') + + self.assertRegex(output, 'qdisc tbf 35: parent 2:35') + self.assertRegex(output, 'rate 1Gbit burst 5000b peakrate 100Gbit minburst 987500b lat 70.0ms') + + self.assertRegex(output, 'qdisc sfq 36: parent 2:36') + self.assertRegex(output, 'perturb 5sec') + + output = check_output('tc class show dev dummy98') + print(output) + self.assertRegex(output, 'class htb 2:30 root leaf 30:') + self.assertRegex(output, 'class htb 2:31 root leaf 31:') + self.assertRegex(output, 'class htb 2:32 root leaf 32:') + self.assertRegex(output, 'class htb 2:33 root leaf 33:') + self.assertRegex(output, 'class htb 2:34 root leaf 34:') + self.assertRegex(output, 'class htb 2:35 root leaf 35:') + self.assertRegex(output, 'class htb 2:36 root leaf 36:') + self.assertRegex(output, 'prio 1 rate 1Mbit ceil 500Kbit') + class NetworkdStateFileTests(unittest.TestCase, Utilities): links = [ 'dummy98', From a74760653c2fb8533ef40ab507ca6eaf11ee950e Mon Sep 17 00:00:00 2001 From: Susant Sahani Date: Mon, 10 Feb 2020 16:12:21 +0100 Subject: [PATCH 09/17] network: TC introduce PFIFO --- man/systemd.network.xml | 34 ++++++++ src/network/meson.build | 2 + src/network/networkd-network-gperf.gperf | 3 + src/network/networkd-network.c | 1 + src/network/tc/fifo.c | 87 +++++++++++++++++++ src/network/tc/fifo.h | 17 ++++ src/network/tc/qdisc.c | 1 + src/network/tc/qdisc.h | 2 + .../fuzz-network-parser/directives.network | 4 + 9 files changed, 151 insertions(+) create mode 100644 src/network/tc/fifo.c create mode 100644 src/network/tc/fifo.h diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 27c837d25c..9ab541223a 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2500,6 +2500,40 @@ + + [PFIFO] Section Options + The [PFIFO] section manages the queueing discipline (qdisc) of + Packet First In First Out (pfifo). + + + + Parent= + + Specifies the parent Queueing Discipline (qdisc). Takes one of root, + clsact or ingress. Defaults to root. + + + + + Handle= + + Specifies the major number of unique identifier of the qdisc, known as the handle. + Takes a number in hexadecimal ranges 1 to ffff. Defaults to unset. + + + + + PacketLimit= + + Specifies the hard limit on the FIFO size in number of packets. The size limit (a buffer size) to prevent it + from overflowing in case it is unable to dequeue packets as quickly as it receives them. When this limit is reached, + incoming packets are dropped. An unsigned integer ranges 0 to 4294967294. Defaults to unset and kernel's default is used. + + + + + + [ControlledDelay] Section Options The [ControlledDelay] section manages the queueing discipline (qdisc) of diff --git a/src/network/meson.build b/src/network/meson.build index 407ec246c1..fe10826f4a 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -109,6 +109,8 @@ sources = files(''' networkd-wifi.h tc/codel.c tc/codel.h + tc/fifo.c + tc/fifo.h tc/fq.c tc/fq.h tc/fq-codel.c diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 3081de878f..9744d11992 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -261,6 +261,9 @@ ControlledDelay.TargetSec, config_parse_controlled_delay_usec, ControlledDelay.IntervalSec, config_parse_controlled_delay_usec, QDISC_KIND_CODEL, 0 ControlledDelay.CEThresholdSec, config_parse_controlled_delay_usec, QDISC_KIND_CODEL, 0 ControlledDelay.ECN, config_parse_controlled_delay_bool, QDISC_KIND_CODEL, 0 +PFIFO.Parent, config_parse_qdisc_parent, QDISC_KIND_PFIFO, 0 +PFIFO.Handle, config_parse_qdisc_handle, QDISC_KIND_PFIFO, 0 +PFIFO.PacketLimit, config_parse_fifo_size, QDISC_KIND_PFIFO, 0 FairQueueing.Parent, config_parse_qdisc_parent, QDISC_KIND_FQ, 0 FairQueueing.Handle, config_parse_qdisc_handle, QDISC_KIND_FQ, 0 FairQueueing.PacketLimit, config_parse_fair_queueing_u32, QDISC_KIND_FQ, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 4774132d10..ad734e1f9b 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -485,6 +485,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi "CAN\0" "QDisc\0" "ControlledDelay\0" + "PFIFO\0" "FairQueueing\0" "FairQueueingControlledDelay\0" "HierarchyTokenBucket\0" diff --git a/src/network/tc/fifo.c b/src/network/tc/fifo.c new file mode 100644 index 0000000000..2d2a863e3a --- /dev/null +++ b/src/network/tc/fifo.c @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: LGPL-2.1+ + * Copyright © 2020 VMware, Inc. */ + +#include + +#include "alloc-util.h" +#include "conf-parser.h" +#include "fifo.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "string-util.h" + +static int fifo_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + struct tc_fifo_qopt opt = {}; + FirstInFirstOut *fifo; + int r; + + assert(link); + assert(qdisc); + assert(req); + + fifo = PFIFO(qdisc); + + opt.limit = fifo->limit; + + r = sd_netlink_message_append_data(req, TCA_OPTIONS, &opt, sizeof(struct tc_fifo_qopt)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_OPTIONS attribute: %m"); + + return 0; +} + +int config_parse_fifo_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; + FirstInFirstOut *fifo; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_PFIFO, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_syntax(unit, LOG_ERR, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + + fifo = PFIFO(qdisc); + + if (isempty(rvalue)) { + fifo->limit = 0; + + qdisc = NULL; + return 0; + } + + r = safe_atou32(rvalue, &fifo->limit); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qdisc = NULL; + return 0; +} + +const QDiscVTable pfifo_vtable = { + .object_size = sizeof(FirstInFirstOut), + .tca_kind = "pfifo", + .fill_message = fifo_fill_message, +}; diff --git a/src/network/tc/fifo.h b/src/network/tc/fifo.h new file mode 100644 index 0000000000..02976568dd --- /dev/null +++ b/src/network/tc/fifo.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1+ + * Copyright © 2020 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" + +typedef struct FirstInFirstOut { + QDisc meta; + + uint32_t limit; +} FirstInFirstOut; + +DEFINE_QDISC_CAST(PFIFO, FirstInFirstOut); +extern const QDiscVTable pfifo_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_fifo_size); diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c index 2168234a71..01b89ca5ad 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -21,6 +21,7 @@ const QDiscVTable * const qdisc_vtable[_QDISC_KIND_MAX] = { [QDISC_KIND_FQ_CODEL] = &fq_codel_vtable, [QDISC_KIND_HTB] = &htb_vtable, [QDISC_KIND_NETEM] = &netem_vtable, + [QDISC_KIND_PFIFO] = &pfifo_vtable, [QDISC_KIND_SFQ] = &sfq_vtable, [QDISC_KIND_TBF] = &tbf_vtable, [QDISC_KIND_TEQL] = &teql_vtable, diff --git a/src/network/tc/qdisc.h b/src/network/tc/qdisc.h index f33939d197..0de13bd51a 100644 --- a/src/network/tc/qdisc.h +++ b/src/network/tc/qdisc.h @@ -14,6 +14,7 @@ typedef enum QDiscKind { QDISC_KIND_FQ_CODEL, QDISC_KIND_HTB, QDISC_KIND_NETEM, + QDISC_KIND_PFIFO, QDISC_KIND_SFQ, QDISC_KIND_TBF, QDISC_KIND_TEQL, @@ -75,6 +76,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_qdisc_parent); CONFIG_PARSER_PROTOTYPE(config_parse_qdisc_handle); #include "codel.h" +#include "fifo.h" #include "fq-codel.h" #include "fq.h" #include "htb.h" diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 6d1107ee9e..675849cf15 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -347,3 +347,7 @@ ClassId= Priority= Rate= CeilRate= +[PFIFO] +Parent= +Handle= +PacketLimit= From bc0769c9f75d5e397f2d9ce468bbbf7c6fdc9e5f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 17 Feb 2020 00:37:03 +0900 Subject: [PATCH 10/17] test-network: add a test case for PFIFO --- .../conf/25-qdisc-clsact-and-htb.network | 12 ++++++++++++ test/test-network/systemd-networkd-tests.py | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/test/test-network/conf/25-qdisc-clsact-and-htb.network b/test/test-network/conf/25-qdisc-clsact-and-htb.network index c8757aec99..8d145028ff 100644 --- a/test/test-network/conf/25-qdisc-clsact-and-htb.network +++ b/test/test-network/conf/25-qdisc-clsact-and-htb.network @@ -122,3 +122,15 @@ CeilRate=0.5M Parent=2:36 Handle=0036 PerturbPeriodSec=5sec + +[HierarchyTokenBucketClass] +Parent=root +ClassId=0002:0037 +Priority=1 +Rate=1M +CeilRate=0.5M + +[PFIFO] +Parent=2:37 +Handle=0037 +PacketLimit=100000 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 5ff69d7222..db606e96ab 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -2293,6 +2293,9 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): self.assertRegex(output, 'qdisc sfq 36: parent 2:36') self.assertRegex(output, 'perturb 5sec') + self.assertRegex(output, 'qdisc pfifo 37: parent 2:37') + self.assertRegex(output, 'limit 100000p') + output = check_output('tc class show dev dummy98') print(output) self.assertRegex(output, 'class htb 2:30 root leaf 30:') @@ -2302,6 +2305,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): self.assertRegex(output, 'class htb 2:34 root leaf 34:') self.assertRegex(output, 'class htb 2:35 root leaf 35:') self.assertRegex(output, 'class htb 2:36 root leaf 36:') + self.assertRegex(output, 'class htb 2:37 root leaf 37:') self.assertRegex(output, 'prio 1 rate 1Mbit ceil 500Kbit') class NetworkdStateFileTests(unittest.TestCase, Utilities): From 609e8340bbe603053e39eae6a629a39e6ca9cc68 Mon Sep 17 00:00:00 2001 From: Susant Sahani Date: Mon, 10 Feb 2020 13:24:02 +0100 Subject: [PATCH 11/17] network: TC introduce GRED, Generic Random Early Detection http://tldp.org/en/Traffic-Control-HOWTO/ar01s06.html --- man/systemd.network.xml | 47 +++++ src/libsystemd/sd-netlink/netlink-types.c | 7 + src/libsystemd/sd-netlink/netlink-types.h | 1 + src/network/meson.build | 2 + src/network/networkd-network-gperf.gperf | 5 + src/network/networkd-network.c | 1 + src/network/tc/gred.c | 193 ++++++++++++++++++ src/network/tc/gred.h | 20 ++ src/network/tc/qdisc.c | 1 + src/network/tc/qdisc.h | 2 + .../fuzz-network-parser/directives.network | 6 + 11 files changed, 285 insertions(+) create mode 100644 src/network/tc/gred.c create mode 100644 src/network/tc/gred.h diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 9ab541223a..edf409a3bb 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2600,6 +2600,53 @@ + + [GenericRandomEarlyDetection] Section Options + The [GenericRandomEarlyDetection] section manages the queueing discipline + (qdisc) of Generic Random Early Detection (GRED). + + + + Parent= + + Specifies the parent Queueing Discipline (qdisc). Takes one of root, + clsact or ingress. Defaults to root. + + + + + Handle= + + Specifies the major number of unique identifier of the qdisc, known as the handle. + Takes a number in hexadecimal ranges 1 to ffff. Defaults to unset. + + + + + VirtualQueues= + + Specifies the number of virtual queues. Takes a integer in the range 1-16. Defaults to unset and kernel's default is used. + + + + + DefaultVirtualQueue= + + Specifies the number of default virtual queue. This must be less than VirtualQueue=. + Defaults to unset and kernel's default is used. + + + + + GenericRIO= + + Takes a boolean. It turns on the RIO-like buffering scheme. Defaults to + unset and kernel's default is used. + + + + + [FairQueueingControlledDelay] Section Options The [FairQueueingControlledDelay] section manages the queueing discipline diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c index ebfb47fc5d..0bada96761 100644 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -780,6 +780,10 @@ static const NLType rtnl_tca_option_data_fq_codel_types[] = { [TCA_FQ_CODEL_MEMORY_LIMIT] = { .type = NETLINK_TYPE_U32 }, }; +static const NLType rtnl_tca_option_data_gred_types[] = { + [TCA_GRED_DPS] = { .size = sizeof(struct tc_gred_sopt) }, +}; + static const NLType rtnl_tca_option_data_htb_types[] = { [TCA_HTB_PARMS] = { .size = sizeof(struct tc_htb_opt) }, [TCA_HTB_INIT] = { .size = sizeof(struct tc_htb_glob) }, @@ -803,6 +807,7 @@ static const char* const nl_union_tca_option_data_table[] = { [NL_UNION_TCA_OPTION_DATA_CODEL] = "codel", [NL_UNION_TCA_OPTION_DATA_FQ] = "fq", [NL_UNION_TCA_OPTION_DATA_FQ_CODEL] = "fq_codel", + [NL_UNION_TCA_OPTION_DATA_GRED] = "gred", [NL_UNION_TCA_OPTION_DATA_HTB] = "htb", [NL_UNION_TCA_OPTION_DATA_TBF] = "tbf", }; @@ -816,6 +821,8 @@ static const NLTypeSystem rtnl_tca_option_data_type_systems[] = { .types = rtnl_tca_option_data_fq_types }, [NL_UNION_TCA_OPTION_DATA_FQ_CODEL] = { .count = ELEMENTSOF(rtnl_tca_option_data_fq_codel_types), .types = rtnl_tca_option_data_fq_codel_types }, + [NL_UNION_TCA_OPTION_DATA_GRED] = { .count = ELEMENTSOF(rtnl_tca_option_data_gred_types), + .types = rtnl_tca_option_data_gred_types }, [NL_UNION_TCA_OPTION_DATA_HTB] = { .count = ELEMENTSOF(rtnl_tca_option_data_htb_types), .types = rtnl_tca_option_data_htb_types }, [NL_UNION_TCA_OPTION_DATA_TBF] = { .count = ELEMENTSOF(rtnl_tca_option_data_tbf_types), diff --git a/src/libsystemd/sd-netlink/netlink-types.h b/src/libsystemd/sd-netlink/netlink-types.h index a3dd4aa447..3b18ebb5c5 100644 --- a/src/libsystemd/sd-netlink/netlink-types.h +++ b/src/libsystemd/sd-netlink/netlink-types.h @@ -95,6 +95,7 @@ typedef enum NLUnionTCAOptionData { NL_UNION_TCA_OPTION_DATA_CODEL, NL_UNION_TCA_OPTION_DATA_FQ, NL_UNION_TCA_OPTION_DATA_FQ_CODEL, + NL_UNION_TCA_OPTION_DATA_GRED, NL_UNION_TCA_OPTION_DATA_HTB, NL_UNION_TCA_OPTION_DATA_TBF, _NL_UNION_TCA_OPTION_DATA_MAX, diff --git a/src/network/meson.build b/src/network/meson.build index fe10826f4a..375bc7b197 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -115,6 +115,8 @@ sources = files(''' tc/fq.h tc/fq-codel.c tc/fq-codel.h + tc/gred.c + tc/gred.h tc/htb.c tc/htb.h tc/netem.c diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 9744d11992..a12e883b59 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -285,6 +285,11 @@ FairQueueingControlledDelay.TargetSec, config_parse_fair_queueing_controll FairQueueingControlledDelay.IntervalSec, config_parse_fair_queueing_controlled_delay_usec, QDISC_KIND_FQ_CODEL, 0 FairQueueingControlledDelay.CEThresholdSec, config_parse_fair_queueing_controlled_delay_usec, QDISC_KIND_FQ_CODEL, 0 FairQueueingControlledDelay.ECN, config_parse_fair_queueing_controlled_delay_bool, QDISC_KIND_FQ_CODEL, 0 +GenericRandomEarlyDetection.Parent, config_parse_qdisc_parent, QDISC_KIND_GRED, 0 +GenericRandomEarlyDetection.Handle, config_parse_qdisc_handle, QDISC_KIND_GRED, 0 +GenericRandomEarlyDetection.VirtualQueues, config_parse_generic_random_early_detection_u32, QDISC_KIND_GRED, 0 +GenericRandomEarlyDetection.DefaultVirtualQueue, config_parse_generic_random_early_detection_u32, QDISC_KIND_GRED, 0 +GenericRandomEarlyDetection.GenericRIO, config_parse_generic_random_early_detection_bool, QDISC_KIND_GRED, 0 HierarchyTokenBucket.Parent, config_parse_qdisc_parent, QDISC_KIND_HTB, 0 HierarchyTokenBucket.Handle, config_parse_qdisc_handle, QDISC_KIND_HTB, 0 HierarchyTokenBucket.DefaultClass, config_parse_hierarchy_token_bucket_default_class, QDISC_KIND_HTB, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index ad734e1f9b..8a1581f30c 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -488,6 +488,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi "PFIFO\0" "FairQueueing\0" "FairQueueingControlledDelay\0" + "GenericRandomEarlyDetection\0" "HierarchyTokenBucket\0" "HierarchyTokenBucketClass\0" "NetworkEmulator\0" diff --git a/src/network/tc/gred.c b/src/network/tc/gred.c new file mode 100644 index 0000000000..cca32c30a7 --- /dev/null +++ b/src/network/tc/gred.c @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: LGPL-2.1+ + * Copyright © 2020 VMware, Inc. */ + +#include + +#include "alloc-util.h" +#include "conf-parser.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "qdisc.h" +#include "string-util.h" + +static int generic_random_early_detection_init(QDisc *qdisc) { + GenericRandomEarlyDetection *gred; + + assert(qdisc); + + gred = GRED(qdisc); + + gred->grio = -1; + + return 0; +} + +static int generic_random_early_detection_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + GenericRandomEarlyDetection *gred; + struct tc_gred_sopt opt = {}; + int r; + + assert(link); + assert(qdisc); + assert(req); + + gred = GRED(qdisc); + + opt.DPs = gred->virtual_queues; + opt.def_DP = gred->default_virtual_queue; + + if (gred->grio >= 0) + opt.grio = gred->grio; + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "gred"); + 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_GRED_DPS, &opt, sizeof(struct tc_gred_sopt)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_GRED_DPS 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; +} + +static int generic_random_early_detection_verify(QDisc *qdisc) { + GenericRandomEarlyDetection *gred = GRED(qdisc); + + if (gred->default_virtual_queue >= gred->virtual_queues) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: DefaultVirtualQueue= must be less than VirtualQueues=. " + "Ignoring [GenericRandomEarlyDetection] section from line %u.", + qdisc->section->filename, qdisc->section->line); + + return 0; +} + +int config_parse_generic_random_early_detection_u32( + 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; + GenericRandomEarlyDetection *gred; + Network *network = data; + uint32_t *p; + uint32_t v; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_GRED, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_syntax(unit, LOG_ERR, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + + gred = GRED(qdisc); + + if (streq(lvalue, "VirtualQueues")) + p = &gred->virtual_queues; + else if (streq(lvalue, "DefaultVirtualQueue")) + p = &gred->default_virtual_queue; + else + assert_not_reached("Invalid lvalue."); + + if (isempty(rvalue)) { + *p = 0; + + qdisc = NULL; + return 0; + } + + r = safe_atou32(rvalue, &v); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + if (v > MAX_DPs) { + log_syntax(unit, LOG_ERR, filename, line, 0, + "Invalid '%s=', ignoring assignment: %s", + lvalue, rvalue); + } + + *p = v; + qdisc = NULL; + + return 0; +} +int config_parse_generic_random_early_detection_bool( + 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; + GenericRandomEarlyDetection *gred; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_GRED, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_syntax(unit, LOG_ERR, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + + gred = GRED(qdisc); + + if (isempty(rvalue)) { + gred->grio = -1; + + qdisc = NULL; + return 0; + } + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + gred->grio = r; + qdisc = NULL; + + return 0; +} + +const QDiscVTable gred_vtable = { + .object_size = sizeof(GenericRandomEarlyDetection), + .tca_kind = "gred", + .init = generic_random_early_detection_init, + .fill_message = generic_random_early_detection_fill_message, + .verify = generic_random_early_detection_verify, +}; diff --git a/src/network/tc/gred.h b/src/network/tc/gred.h new file mode 100644 index 0000000000..4fb2b37c11 --- /dev/null +++ b/src/network/tc/gred.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1+ + * Copyright © 2020 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" + +typedef struct GenericRandomEarlyDetection { + QDisc meta; + + uint32_t virtual_queues; + uint32_t default_virtual_queue; + int grio; +} GenericRandomEarlyDetection; + +DEFINE_QDISC_CAST(GRED, GenericRandomEarlyDetection); +extern const QDiscVTable gred_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_generic_random_early_detection_u32); +CONFIG_PARSER_PROTOTYPE(config_parse_generic_random_early_detection_bool); diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c index 01b89ca5ad..674c0a90df 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -19,6 +19,7 @@ const QDiscVTable * const qdisc_vtable[_QDISC_KIND_MAX] = { [QDISC_KIND_CODEL] = &codel_vtable, [QDISC_KIND_FQ] = &fq_vtable, [QDISC_KIND_FQ_CODEL] = &fq_codel_vtable, + [QDISC_KIND_GRED] = &gred_vtable, [QDISC_KIND_HTB] = &htb_vtable, [QDISC_KIND_NETEM] = &netem_vtable, [QDISC_KIND_PFIFO] = &pfifo_vtable, diff --git a/src/network/tc/qdisc.h b/src/network/tc/qdisc.h index 0de13bd51a..7fd3a260e6 100644 --- a/src/network/tc/qdisc.h +++ b/src/network/tc/qdisc.h @@ -12,6 +12,7 @@ typedef enum QDiscKind { QDISC_KIND_CODEL, QDISC_KIND_FQ, QDISC_KIND_FQ_CODEL, + QDISC_KIND_GRED, QDISC_KIND_HTB, QDISC_KIND_NETEM, QDISC_KIND_PFIFO, @@ -79,6 +80,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_qdisc_handle); #include "fifo.h" #include "fq-codel.h" #include "fq.h" +#include "gred.h" #include "htb.h" #include "netem.h" #include "sfq.h" diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 675849cf15..01de489c32 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -351,3 +351,9 @@ CeilRate= Parent= Handle= PacketLimit= +[GenericRandomEarlyDetection] +Parent= +Handle= +VirtualQueues= +DefaultVirtualQueue= +GenericRIO= From 95edcf3fac97145aab29b5529446d74127331d9e Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 17 Feb 2020 01:09:05 +0900 Subject: [PATCH 12/17] test-network: add a test case for GRED --- .../conf/25-qdisc-clsact-and-htb.network | 14 ++++++++++++++ test/test-network/systemd-networkd-tests.py | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/test/test-network/conf/25-qdisc-clsact-and-htb.network b/test/test-network/conf/25-qdisc-clsact-and-htb.network index 8d145028ff..a6e7ed91d4 100644 --- a/test/test-network/conf/25-qdisc-clsact-and-htb.network +++ b/test/test-network/conf/25-qdisc-clsact-and-htb.network @@ -134,3 +134,17 @@ CeilRate=0.5M Parent=2:37 Handle=0037 PacketLimit=100000 + +[HierarchyTokenBucketClass] +Parent=root +ClassId=0002:0038 +Priority=1 +Rate=1M +CeilRate=0.5M + +[GenericRandomEarlyDetection] +Parent=2:38 +Handle=0038 +VirtualQueues=12 +DefaultVirtualQueue=10 +GenericRIO=yes diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index db606e96ab..a6b1f02467 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -2296,6 +2296,9 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): self.assertRegex(output, 'qdisc pfifo 37: parent 2:37') self.assertRegex(output, 'limit 100000p') + self.assertRegex(output, 'qdisc gred 38: parent 2:38') + self.assertRegex(output, 'vqs 12 default 10 grio') + output = check_output('tc class show dev dummy98') print(output) self.assertRegex(output, 'class htb 2:30 root leaf 30:') @@ -2306,6 +2309,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): self.assertRegex(output, 'class htb 2:35 root leaf 35:') self.assertRegex(output, 'class htb 2:36 root leaf 36:') self.assertRegex(output, 'class htb 2:37 root leaf 37:') + self.assertRegex(output, 'class htb 2:38 root leaf 38:') self.assertRegex(output, 'prio 1 rate 1Mbit ceil 500Kbit') class NetworkdStateFileTests(unittest.TestCase, Utilities): From 982998b087e76f96b46a78124641691daa47009f Mon Sep 17 00:00:00 2001 From: Susant Sahani Date: Tue, 11 Feb 2020 08:24:27 +0100 Subject: [PATCH 13/17] network: TC introduce sfb - Stochastic Fair Blue Please see https://www.systutorials.com/docs/linux/man/8-tc-sfb/ --- man/systemd.network.xml | 32 ++++++ src/libsystemd/sd-netlink/netlink-types.c | 7 ++ src/libsystemd/sd-netlink/netlink-types.h | 1 + src/network/meson.build | 2 + src/network/networkd-network-gperf.gperf | 3 + src/network/networkd-network.c | 1 + src/network/tc/qdisc.c | 1 + src/network/tc/qdisc.h | 2 + src/network/tc/sfb.c | 106 ++++++++++++++++++ src/network/tc/sfb.h | 17 +++ .../fuzz-network-parser/directives.network | 4 + 11 files changed, 176 insertions(+) create mode 100644 src/network/tc/sfb.c create mode 100644 src/network/tc/sfb.h diff --git a/man/systemd.network.xml b/man/systemd.network.xml index edf409a3bb..299f241ae1 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2467,6 +2467,38 @@ + + [StochasticFairBlue] Section Options + The [StochasticFairBlue] section manages the queueing discipline + (qdisc) of stochastic fair blue (sfb). + + + + Parent= + + Specifies the parent Queueing Discipline (qdisc). Takes one of root, + clsact or ingress. Defaults to root. + + + + + Handle= + + Specifies the major number of unique identifier of the qdisc, known as the handle. + Takes a number in hexadecimal ranges 1 to ffff. Defaults to unset. + + + + + PacketLimit= + + Specifies the hard limit on the queue size in number of packets. When this limit is reached, incoming packets are + dropped. An unsigned integer ranges 0 to 4294967294. Defaults to unset and kernel's default is used. + + + + + [StochasticFairnessQueueing] Section Options The [StochasticFairnessQueueing] section manages the queueing discipline diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c index 0bada96761..4a24405487 100644 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -793,6 +793,10 @@ static const NLType rtnl_tca_option_data_htb_types[] = { [TCA_HTB_CEIL64] = { .type = NETLINK_TYPE_U64 }, }; +static const NLType rtnl_tca_option_data_sfb_types[] = { + [TCA_SFB_PARMS] = { .size = sizeof(struct tc_sfb_qopt) }, +}; + static const NLType rtnl_tca_option_data_tbf_types[] = { [TCA_TBF_PARMS] = { .size = sizeof(struct tc_tbf_qopt) }, [TCA_TBF_RTAB] = { .size = TC_RTAB_SIZE }, @@ -809,6 +813,7 @@ static const char* const nl_union_tca_option_data_table[] = { [NL_UNION_TCA_OPTION_DATA_FQ_CODEL] = "fq_codel", [NL_UNION_TCA_OPTION_DATA_GRED] = "gred", [NL_UNION_TCA_OPTION_DATA_HTB] = "htb", + [NL_UNION_TCA_OPTION_DATA_SFB] = "sfb", [NL_UNION_TCA_OPTION_DATA_TBF] = "tbf", }; @@ -825,6 +830,8 @@ static const NLTypeSystem rtnl_tca_option_data_type_systems[] = { .types = rtnl_tca_option_data_gred_types }, [NL_UNION_TCA_OPTION_DATA_HTB] = { .count = ELEMENTSOF(rtnl_tca_option_data_htb_types), .types = rtnl_tca_option_data_htb_types }, + [NL_UNION_TCA_OPTION_DATA_SFB] = { .count = ELEMENTSOF(rtnl_tca_option_data_sfb_types), + .types = rtnl_tca_option_data_sfb_types }, [NL_UNION_TCA_OPTION_DATA_TBF] = { .count = ELEMENTSOF(rtnl_tca_option_data_tbf_types), .types = rtnl_tca_option_data_tbf_types }, }; diff --git a/src/libsystemd/sd-netlink/netlink-types.h b/src/libsystemd/sd-netlink/netlink-types.h index 3b18ebb5c5..97bcf49aaa 100644 --- a/src/libsystemd/sd-netlink/netlink-types.h +++ b/src/libsystemd/sd-netlink/netlink-types.h @@ -97,6 +97,7 @@ typedef enum NLUnionTCAOptionData { NL_UNION_TCA_OPTION_DATA_FQ_CODEL, NL_UNION_TCA_OPTION_DATA_GRED, NL_UNION_TCA_OPTION_DATA_HTB, + NL_UNION_TCA_OPTION_DATA_SFB, NL_UNION_TCA_OPTION_DATA_TBF, _NL_UNION_TCA_OPTION_DATA_MAX, _NL_UNION_TCA_OPTION_DATA_INVALID = -1, diff --git a/src/network/meson.build b/src/network/meson.build index 375bc7b197..8733812279 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -123,6 +123,8 @@ sources = files(''' tc/netem.h tc/qdisc.c tc/qdisc.h + tc/sfb.c + tc/sfb.h tc/sfq.c tc/sfq.h tc/tbf.c diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index a12e883b59..881138df1b 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -305,6 +305,9 @@ NetworkEmulator.DelayJitterSec, config_parse_network_emulator_delay NetworkEmulator.LossRate, config_parse_network_emulator_rate, QDISC_KIND_NETEM, 0 NetworkEmulator.DuplicateRate, config_parse_network_emulator_rate, QDISC_KIND_NETEM, 0 NetworkEmulator.PacketLimit, config_parse_network_emulator_packet_limit, QDISC_KIND_NETEM, 0 +StochasticFairBlue.Parent, config_parse_qdisc_parent, QDISC_KIND_SFB, 0 +StochasticFairBlue.Handle, config_parse_qdisc_handle, QDISC_KIND_SFB, 0 +StochasticFairBlue.PacketLimit, config_parse_stochastic_fair_blue_u32, QDISC_KIND_SFB, 0 StochasticFairnessQueueing.Parent, config_parse_qdisc_parent, QDISC_KIND_SFQ, 0 StochasticFairnessQueueing.Handle, config_parse_qdisc_handle, QDISC_KIND_SFQ, 0 StochasticFairnessQueueing.PerturbPeriodSec, config_parse_stochastic_fairness_queueing_perturb_period, QDISC_KIND_SFQ, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 8a1581f30c..6762ba0569 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -492,6 +492,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi "HierarchyTokenBucket\0" "HierarchyTokenBucketClass\0" "NetworkEmulator\0" + "StochasticFairBlue\0" "StochasticFairnessQueueing\0" "TokenBucketFilter\0" "TrivialLinkEqualizer\0", diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c index 674c0a90df..405ff688b0 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -23,6 +23,7 @@ const QDiscVTable * const qdisc_vtable[_QDISC_KIND_MAX] = { [QDISC_KIND_HTB] = &htb_vtable, [QDISC_KIND_NETEM] = &netem_vtable, [QDISC_KIND_PFIFO] = &pfifo_vtable, + [QDISC_KIND_SFB] = &sfb_vtable, [QDISC_KIND_SFQ] = &sfq_vtable, [QDISC_KIND_TBF] = &tbf_vtable, [QDISC_KIND_TEQL] = &teql_vtable, diff --git a/src/network/tc/qdisc.h b/src/network/tc/qdisc.h index 7fd3a260e6..79323972e3 100644 --- a/src/network/tc/qdisc.h +++ b/src/network/tc/qdisc.h @@ -16,6 +16,7 @@ typedef enum QDiscKind { QDISC_KIND_HTB, QDISC_KIND_NETEM, QDISC_KIND_PFIFO, + QDISC_KIND_SFB, QDISC_KIND_SFQ, QDISC_KIND_TBF, QDISC_KIND_TEQL, @@ -83,6 +84,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_qdisc_handle); #include "gred.h" #include "htb.h" #include "netem.h" +#include "sfb.h" #include "sfq.h" #include "tbf.h" #include "teql.h" diff --git a/src/network/tc/sfb.c b/src/network/tc/sfb.c new file mode 100644 index 0000000000..9e954ff935 --- /dev/null +++ b/src/network/tc/sfb.c @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: LGPL-2.1+ + * Copyright © 2020 VMware, Inc. */ + +#include + +#include "alloc-util.h" +#include "conf-parser.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "qdisc.h" +#include "sfb.h" +#include "string-util.h" + +static int stochastic_fair_blue_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + StochasticFairBlue *sfb; + struct tc_sfb_qopt opt = { + .rehash_interval = 600*1000, + .warmup_time = 60*1000, + .penalty_rate = 10, + .penalty_burst = 20, + .increment = (SFB_MAX_PROB + 1000) / 2000, + .decrement = (SFB_MAX_PROB + 10000) / 20000, + .max = 25, + .bin_size = 20, + }; + int r; + + assert(link); + assert(qdisc); + assert(req); + + sfb = SFB(qdisc); + + opt.limit = sfb->packet_limit; + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "sfb"); + 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_SFB_PARMS, &opt, sizeof(struct tc_sfb_qopt)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_SFB_PARMS 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_stochastic_fair_blue_u32( + 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; + StochasticFairBlue *sfb; + Network *network = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_SFB, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_syntax(unit, LOG_ERR, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + + sfb = SFB(qdisc); + + if (isempty(rvalue)) { + sfb->packet_limit = 0; + + qdisc = NULL; + return 0; + } + + r = safe_atou32(rvalue, &sfb->packet_limit); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to parse '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + qdisc = NULL; + + return 0; +} + +const QDiscVTable sfb_vtable = { + .object_size = sizeof(StochasticFairBlue), + .tca_kind = "sfb", + .fill_message = stochastic_fair_blue_fill_message, +}; diff --git a/src/network/tc/sfb.h b/src/network/tc/sfb.h new file mode 100644 index 0000000000..3cc87d7373 --- /dev/null +++ b/src/network/tc/sfb.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1+ + * Copyright © 2020 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" + +typedef struct StochasticFairBlue { + QDisc meta; + + uint32_t packet_limit; +} StochasticFairBlue; + +DEFINE_QDISC_CAST(SFB, StochasticFairBlue); +extern const QDiscVTable sfb_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_stochastic_fair_blue_u32); diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 01de489c32..f20dabf4ef 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -357,3 +357,7 @@ Handle= VirtualQueues= DefaultVirtualQueue= GenericRIO= +[StochasticFairBlue] +Parent= +Handle= +PacketLimit= From f2c5c1296acfaba9de1cec0a32532e425e2dfaf4 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 17 Feb 2020 01:27:59 +0900 Subject: [PATCH 14/17] test-network: add a test case for sfb --- .../conf/25-qdisc-clsact-and-htb.network | 12 ++++++++++++ test/test-network/systemd-networkd-tests.py | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/test/test-network/conf/25-qdisc-clsact-and-htb.network b/test/test-network/conf/25-qdisc-clsact-and-htb.network index a6e7ed91d4..039a2ffa65 100644 --- a/test/test-network/conf/25-qdisc-clsact-and-htb.network +++ b/test/test-network/conf/25-qdisc-clsact-and-htb.network @@ -148,3 +148,15 @@ Handle=0038 VirtualQueues=12 DefaultVirtualQueue=10 GenericRIO=yes + +[HierarchyTokenBucketClass] +Parent=root +ClassId=0002:0039 +Priority=1 +Rate=1M +CeilRate=0.5M + +[StochasticFairBlue] +Parent=2:39 +Handle=0039 +PacketLimit=200000 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index a6b1f02467..3d26a87bae 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -2299,6 +2299,9 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): self.assertRegex(output, 'qdisc gred 38: parent 2:38') self.assertRegex(output, 'vqs 12 default 10 grio') + self.assertRegex(output, 'qdisc sfb 39: parent 2:39') + self.assertRegex(output, 'limit 200000') + output = check_output('tc class show dev dummy98') print(output) self.assertRegex(output, 'class htb 2:30 root leaf 30:') @@ -2310,6 +2313,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): self.assertRegex(output, 'class htb 2:36 root leaf 36:') self.assertRegex(output, 'class htb 2:37 root leaf 37:') self.assertRegex(output, 'class htb 2:38 root leaf 38:') + self.assertRegex(output, 'class htb 2:39 root leaf 39:') self.assertRegex(output, 'prio 1 rate 1Mbit ceil 500Kbit') class NetworkdStateFileTests(unittest.TestCase, Utilities): From aa550d2a51b025681ff8399e597338d35f540195 Mon Sep 17 00:00:00 2001 From: Susant Sahani Date: Mon, 2 Mar 2020 15:51:16 +0900 Subject: [PATCH 15/17] sd-netlink: introduce sd_netlink_message_append_s8() and friends --- src/libsystemd/sd-netlink/netlink-message.c | 68 +++++++++++++++++++++ src/libsystemd/sd-netlink/netlink-types.h | 4 ++ src/systemd/sd-netlink.h | 4 ++ 3 files changed, 76 insertions(+) diff --git a/src/libsystemd/sd-netlink/netlink-message.c b/src/libsystemd/sd-netlink/netlink-message.c index 39e3c36ad2..a5871c7319 100644 --- a/src/libsystemd/sd-netlink/netlink-message.c +++ b/src/libsystemd/sd-netlink/netlink-message.c @@ -343,6 +343,74 @@ int sd_netlink_message_append_u64(sd_netlink_message *m, unsigned short type, ui return 0; } +int sd_netlink_message_append_s8(sd_netlink_message *m, unsigned short type, int8_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_S8); + if (r < 0) + return r; + + r = add_rtattr(m, type, &data, sizeof(int8_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_s16(sd_netlink_message *m, unsigned short type, int16_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_S16); + if (r < 0) + return r; + + r = add_rtattr(m, type, &data, sizeof(int16_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_s32(sd_netlink_message *m, unsigned short type, int32_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_S32); + if (r < 0) + return r; + + r = add_rtattr(m, type, &data, sizeof(int32_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_s64(sd_netlink_message *m, unsigned short type, int64_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_S64); + if (r < 0) + return r; + + r = add_rtattr(m, type, &data, sizeof(int64_t)); + if (r < 0) + return r; + + return 0; +} + int sd_netlink_message_append_data(sd_netlink_message *m, unsigned short type, const void *data, size_t len) { int r; diff --git a/src/libsystemd/sd-netlink/netlink-types.h b/src/libsystemd/sd-netlink/netlink-types.h index 97bcf49aaa..cda99c1be0 100644 --- a/src/libsystemd/sd-netlink/netlink-types.h +++ b/src/libsystemd/sd-netlink/netlink-types.h @@ -9,6 +9,10 @@ enum { NETLINK_TYPE_U16, /* NLA_U16 */ NETLINK_TYPE_U32, /* NLA_U32 */ NETLINK_TYPE_U64, /* NLA_U64 */ + NETLINK_TYPE_S8, /* NLA_S8 */ + NETLINK_TYPE_S16, /* NLA_S16 */ + NETLINK_TYPE_S32, /* NLA_S32 */ + NETLINK_TYPE_S64, /* NLA_S64 */ NETLINK_TYPE_STRING, /* NLA_STRING */ NETLINK_TYPE_FLAG, /* NLA_FLAG */ NETLINK_TYPE_IN_ADDR, diff --git a/src/systemd/sd-netlink.h b/src/systemd/sd-netlink.h index ff06dc0cac..f9196491d6 100644 --- a/src/systemd/sd-netlink.h +++ b/src/systemd/sd-netlink.h @@ -86,6 +86,10 @@ int sd_netlink_message_append_u8(sd_netlink_message *m, unsigned short type, uin int sd_netlink_message_append_u16(sd_netlink_message *m, unsigned short type, uint16_t data); int sd_netlink_message_append_u32(sd_netlink_message *m, unsigned short type, uint32_t data); int sd_netlink_message_append_u64(sd_netlink_message *m, unsigned short type, uint64_t data); +int sd_netlink_message_append_s8(sd_netlink_message *m, unsigned short type, int8_t data); +int sd_netlink_message_append_s16(sd_netlink_message *m, unsigned short type, int16_t data); +int sd_netlink_message_append_s32(sd_netlink_message *m, unsigned short type, int32_t data); +int sd_netlink_message_append_s64(sd_netlink_message *m, unsigned short type, int64_t data); int sd_netlink_message_append_data(sd_netlink_message *m, unsigned short type, const void *data, size_t len); int sd_netlink_message_append_in_addr(sd_netlink_message *m, unsigned short type, const struct in_addr *data); int sd_netlink_message_append_in6_addr(sd_netlink_message *m, unsigned short type, const struct in6_addr *data); From ad8352f4ffa97488854bafadad36397b056c68b6 Mon Sep 17 00:00:00 2001 From: Susant Sahani Date: Mon, 2 Mar 2020 15:54:17 +0900 Subject: [PATCH 16/17] network: tc: introduce cake CAKE (Common Applications Kept Enhanced) Please see http://man7.org/linux/man-pages/man8/tc-cake.8.html --- man/systemd.network.xml | 42 +++++ src/libsystemd/sd-netlink/netlink-types.c | 9 + src/libsystemd/sd-netlink/netlink-types.h | 1 + src/network/meson.build | 2 + src/network/networkd-network-gperf.gperf | 4 + src/network/networkd-network.c | 1 + src/network/tc/cake.c | 159 ++++++++++++++++++ src/network/tc/cake.h | 20 +++ src/network/tc/qdisc.c | 1 + src/network/tc/qdisc.h | 2 + .../fuzz-network-parser/directives.network | 5 + 11 files changed, 246 insertions(+) create mode 100644 src/network/tc/cake.c create mode 100644 src/network/tc/cake.h diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 299f241ae1..77a3b240d9 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2562,7 +2562,49 @@ incoming packets are dropped. An unsigned integer ranges 0 to 4294967294. Defaults to unset and kernel's default is used. + + + + [CAKE] Section Options + The [CAKE] section manages the queueing discipline (qdisc) of + Common Applications Kept Enhanced (CAKE). + + + + Parent= + + Specifies the parent Queueing Discipline (qdisc). Takes one of root, + clsact, ingress or a class id. The class id takes the + major and minor number in hexadecimal ranges 1 to ffff separated with a colon + (major:minor). Defaults to root. + + + + + Handle= + + Specifies the major number of unique identifier of the qdisc, known as the handle. + Takes a number in hexadecimal ranges 1 to ffff. Defaults to unset. + + + + + Overhead= + + Specifies that bytes to be addeded to the size of each packet. Bytes may be negative. + Takes an integer ranges -64 to 256. Defaults to unset and kernel's default is used. + + + + + Bandwidth= + + Specifies the shaper bandwidth. When suffixed with K, M, or G, the specified size is + parsed as Kilobits, Megabits, or Gigabits, respectively, to the base of 1000. Defaults to + unset and kernel's default is used. + + diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c index 4a24405487..4846b6f74e 100644 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -745,6 +745,12 @@ static const NLTypeSystem rtnl_nexthop_type_system = { .types = rtnl_nexthop_types, }; +static const NLType rtnl_tca_option_data_cake_types[] = { + [TCA_CAKE_BASE_RATE64] = { .type = NETLINK_TYPE_U64 }, + [TCA_CAKE_OVERHEAD] = { .type = NETLINK_TYPE_S32 }, + [TCA_CAKE_MPU] = { .type = NETLINK_TYPE_U32 }, +}; + static const NLType rtnl_tca_option_data_codel_types[] = { [TCA_CODEL_TARGET] = { .type = NETLINK_TYPE_U32 }, [TCA_CODEL_LIMIT] = { .type = NETLINK_TYPE_U32 }, @@ -808,6 +814,7 @@ static const NLType rtnl_tca_option_data_tbf_types[] = { }; static const char* const nl_union_tca_option_data_table[] = { + [NL_UNION_TCA_OPTION_DATA_CAKE] = "cake", [NL_UNION_TCA_OPTION_DATA_CODEL] = "codel", [NL_UNION_TCA_OPTION_DATA_FQ] = "fq", [NL_UNION_TCA_OPTION_DATA_FQ_CODEL] = "fq_codel", @@ -820,6 +827,8 @@ static const char* const nl_union_tca_option_data_table[] = { DEFINE_STRING_TABLE_LOOKUP(nl_union_tca_option_data, NLUnionTCAOptionData); static const NLTypeSystem rtnl_tca_option_data_type_systems[] = { + [NL_UNION_TCA_OPTION_DATA_CAKE] = { .count = ELEMENTSOF(rtnl_tca_option_data_cake_types), + .types = rtnl_tca_option_data_cake_types }, [NL_UNION_TCA_OPTION_DATA_CODEL] = { .count = ELEMENTSOF(rtnl_tca_option_data_codel_types), .types = rtnl_tca_option_data_codel_types }, [NL_UNION_TCA_OPTION_DATA_FQ] = { .count = ELEMENTSOF(rtnl_tca_option_data_fq_types), diff --git a/src/libsystemd/sd-netlink/netlink-types.h b/src/libsystemd/sd-netlink/netlink-types.h index cda99c1be0..edc4a580b8 100644 --- a/src/libsystemd/sd-netlink/netlink-types.h +++ b/src/libsystemd/sd-netlink/netlink-types.h @@ -96,6 +96,7 @@ const char *nl_union_link_info_data_to_string(NLUnionLinkInfoData p) _const_; NLUnionLinkInfoData nl_union_link_info_data_from_string(const char *p) _pure_; typedef enum NLUnionTCAOptionData { + NL_UNION_TCA_OPTION_DATA_CAKE, NL_UNION_TCA_OPTION_DATA_CODEL, NL_UNION_TCA_OPTION_DATA_FQ, NL_UNION_TCA_OPTION_DATA_FQ_CODEL, diff --git a/src/network/meson.build b/src/network/meson.build index 8733812279..57d3772c3e 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -107,6 +107,8 @@ sources = files(''' networkd-util.h networkd-wifi.c networkd-wifi.h + tc/cake.c + tc/cake.h tc/codel.c tc/codel.h tc/fifo.c diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 881138df1b..12a430762f 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -254,6 +254,10 @@ CAN.RestartSec, config_parse_sec, CAN.TripleSampling, config_parse_tristate, 0, offsetof(Network, can_triple_sampling) QDisc.Parent, config_parse_qdisc_parent, _QDISC_KIND_INVALID, 0 QDisc.Handle, config_parse_qdisc_handle, _QDISC_KIND_INVALID, 0 +CAKE.Parent, config_parse_qdisc_parent, QDISC_KIND_CAKE, 0 +CAKE.Handle, config_parse_qdisc_handle, QDISC_KIND_CAKE, 0 +CAKE.Bandwidth, config_parse_cake_bandwidth, QDISC_KIND_CAKE, 0 +CAKE.Overhead, config_parse_cake_overhead, QDISC_KIND_CAKE, 0 ControlledDelay.Parent, config_parse_qdisc_parent, QDISC_KIND_CODEL, 0 ControlledDelay.Handle, config_parse_qdisc_handle, QDISC_KIND_CODEL, 0 ControlledDelay.PacketLimit, config_parse_controlled_delay_u32, QDISC_KIND_CODEL, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 6762ba0569..d8049fda7b 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -484,6 +484,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi "TrafficControlQueueingDiscipline\0" "CAN\0" "QDisc\0" + "CAKE\0" "ControlledDelay\0" "PFIFO\0" "FairQueueing\0" diff --git a/src/network/tc/cake.c b/src/network/tc/cake.c new file mode 100644 index 0000000000..b499661bd3 --- /dev/null +++ b/src/network/tc/cake.c @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: LGPL-2.1+ + * Copyright © 2020 VMware, Inc. */ + +#include + +#include "alloc-util.h" +#include "cake.h" +#include "conf-parser.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "qdisc.h" +#include "string-util.h" + +static int cake_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { + CommonApplicationsKeptEnhanced *c; + int r; + + assert(link); + assert(qdisc); + assert(req); + + c = CAKE(qdisc); + + r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "cake"); + if (r < 0) + return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m"); + + if (c->bandwidth > 0) { + r = sd_netlink_message_append_u64(req, TCA_CAKE_BASE_RATE64, c->bandwidth); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_CAKE_BASE_RATE64 attribute: %m"); + } + + r = sd_netlink_message_append_s32(req, TCA_CAKE_OVERHEAD, c->overhead); + if (r < 0) + return log_link_error_errno(link, r, "Could not append TCA_CAKE_OVERHEAD 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_cake_bandwidth( + 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; + CommonApplicationsKeptEnhanced *c; + Network *network = data; + uint64_t k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_CAKE, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_syntax(unit, LOG_ERR, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + + c = CAKE(qdisc); + + if (isempty(rvalue)) { + c->bandwidth = 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; + } + + c->bandwidth = k/8; + qdisc = NULL; + + return 0; +} + +int config_parse_cake_overhead( + 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; + CommonApplicationsKeptEnhanced *c; + Network *network = data; + int32_t v; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = qdisc_new_static(QDISC_KIND_CAKE, network, filename, section_line, &qdisc); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_syntax(unit, LOG_ERR, filename, line, r, + "More than one kind of queueing discipline, ignoring assignment: %m"); + + c = CAKE(qdisc); + + if (isempty(rvalue)) { + c->overhead = 0; + qdisc = NULL; + return 0; + } + + r = safe_atoi32(rvalue, &v); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to parse 'Overhead=', ignoring assignment: %s", + rvalue); + return 0; + } + if (v < -64 || v > 256) { + log_syntax(unit, LOG_ERR, filename, line, 0, + "Invalid 'Overhead=', ignoring assignment: %s", + rvalue); + return 0; + } + + c->overhead = v; + qdisc = NULL; + return 0; +} + +const QDiscVTable cake_vtable = { + .object_size = sizeof(CommonApplicationsKeptEnhanced), + .tca_kind = "cake", + .fill_message = cake_fill_message, +}; diff --git a/src/network/tc/cake.h b/src/network/tc/cake.h new file mode 100644 index 0000000000..36de5110dd --- /dev/null +++ b/src/network/tc/cake.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1+ + * Copyright © 2020 VMware, Inc. */ +#pragma once + +#include "conf-parser.h" +#include "qdisc.h" + +typedef struct CommonApplicationsKeptEnhanced { + QDisc meta; + + int overhead; + uint64_t bandwidth; + +} CommonApplicationsKeptEnhanced; + +DEFINE_QDISC_CAST(CAKE, CommonApplicationsKeptEnhanced); +extern const QDiscVTable cake_vtable; + +CONFIG_PARSER_PROTOTYPE(config_parse_cake_bandwidth); +CONFIG_PARSER_PROTOTYPE(config_parse_cake_overhead); diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c index 405ff688b0..31b6a716c4 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -16,6 +16,7 @@ #include "tc-util.h" const QDiscVTable * const qdisc_vtable[_QDISC_KIND_MAX] = { + [QDISC_KIND_CAKE] = &cake_vtable, [QDISC_KIND_CODEL] = &codel_vtable, [QDISC_KIND_FQ] = &fq_vtable, [QDISC_KIND_FQ_CODEL] = &fq_codel_vtable, diff --git a/src/network/tc/qdisc.h b/src/network/tc/qdisc.h index 79323972e3..403d6a5b53 100644 --- a/src/network/tc/qdisc.h +++ b/src/network/tc/qdisc.h @@ -9,6 +9,7 @@ #include "tc.h" typedef enum QDiscKind { + QDISC_KIND_CAKE, QDISC_KIND_CODEL, QDISC_KIND_FQ, QDISC_KIND_FQ_CODEL, @@ -77,6 +78,7 @@ DEFINE_TC_CAST(QDISC, QDisc); CONFIG_PARSER_PROTOTYPE(config_parse_qdisc_parent); CONFIG_PARSER_PROTOTYPE(config_parse_qdisc_handle); +#include "cake.h" #include "codel.h" #include "fifo.h" #include "fq-codel.h" diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index f20dabf4ef..66417ef2b0 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -326,6 +326,11 @@ TargetSec= IntervalSec= CEThresholdSec= ECN= +[CAKE] +Parent= +Handle= +Bandwidth= +Overhead= [TrafficControlQueueingDiscipline] Parent= NetworkEmulatorDelaySec= From f6c6af3811689c40073e3baf4eb6cfbb67afc45f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 17 Feb 2020 21:03:12 +0900 Subject: [PATCH 17/17] test-network: add a test case for CAKE --- test/test-network/conf/25-qdisc-cake.network | 12 ++++++++++ test/test-network/systemd-networkd-tests.py | 25 ++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 test/test-network/conf/25-qdisc-cake.network diff --git a/test/test-network/conf/25-qdisc-cake.network b/test/test-network/conf/25-qdisc-cake.network new file mode 100644 index 0000000000..b713245dbc --- /dev/null +++ b/test/test-network/conf/25-qdisc-cake.network @@ -0,0 +1,12 @@ +[Match] +Name=dummy98 + +[Network] +IPv6AcceptRA=no +Address=10.1.2.3/16 + +[CAKE] +Parent=root +Handle=3a +Overhead=128 +Bandwidth=500M diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 3d26a87bae..9d39bfb920 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -156,6 +156,18 @@ def expectedFailureIfAlternativeNameIsNotAvailable(): return f +def expectedFailureIfCAKEIsNotAvailable(): + def f(func): + call('ip link add dummy98 type dummy', stderr=subprocess.DEVNULL) + rc = call('tc qdisc add dev dummy98 parent root cake', stderr=subprocess.DEVNULL) + call('ip link del dummy98', stderr=subprocess.DEVNULL) + if rc == 0: + return func + else: + return unittest.expectedFailure(func) + + return f + def setUpModule(): global running_units @@ -1620,6 +1632,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): '25-neighbor-ip-dummy.network', '25-neighbor-ip.network', '25-nexthop.network', + '25-qdisc-cake.network', '25-qdisc-clsact-and-htb.network', '25-qdisc-ingress-netem-compat.network', '25-route-ipv6-src.network', @@ -2316,6 +2329,18 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): self.assertRegex(output, 'class htb 2:39 root leaf 39:') self.assertRegex(output, 'prio 1 rate 1Mbit ceil 500Kbit') + @expectedFailureIfCAKEIsNotAvailable() + def test_qdisc_cake(self): + copy_unit_to_networkd_unit_path('25-qdisc-cake.network', '12-dummy.netdev') + start_networkd() + self.wait_online(['dummy98:routable']) + + output = check_output('tc qdisc show dev dummy98') + print(output) + self.assertRegex(output, 'qdisc cake 3a: root') + self.assertRegex(output, 'bandwidth 500Mbit') + self.assertRegex(output, 'overhead 128') + class NetworkdStateFileTests(unittest.TestCase, Utilities): links = [ 'dummy98',