From 19f86a63515715387e367e99f76c8ecce4405adb Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 12 Feb 2020 09:52:56 +0900 Subject: [PATCH] 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=