diff options
-rw-r--r-- | man/systemd.network.xml | 54 | ||||
-rw-r--r-- | src/network/networkd-network-gperf.gperf | 12 | ||||
-rw-r--r-- | src/network/tc/htb.c | 218 | ||||
-rw-r--r-- | src/network/tc/htb.h | 12 | ||||
-rw-r--r-- | src/network/tc/tc-util.c | 46 | ||||
-rw-r--r-- | src/network/tc/tc-util.h | 1 | ||||
-rw-r--r-- | test/fuzz/fuzz-network-parser/directives.network | 6 |
7 files changed, 312 insertions, 37 deletions
diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 851570e723..515cfe23cd 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -3209,6 +3209,14 @@ to the class. Defaults to unset.</para> </listitem> </varlistentry> + + <varlistentry> + <term><varname>RateToQuantum=</varname></term> + <listitem> + <para>Takes an unsigned integer. The DRR quantums are calculated by dividing the value + configured in <varname>Rate=</varname> by <varname>RateToQuantum=</varname>.</para> + </listitem> + </varlistentry> </variablelist> </refsect1> @@ -3225,7 +3233,33 @@ <term><varname>Priority=</varname></term> <listitem> <para>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.</para> + priority field are tried for packets first.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><varname>QuantumBytes=</varname></term> + <listitem> + <para>Specifies how many bytes to serve from leaf at once. When suffixed with K, M, or G, the + specified size is parsed as Kilobytes, Megabytes, or Gigabytes, respectively, to the base of + 1024.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><varname>MTUBytes=</varname></term> + <listitem> + <para>Specifies the maximum packet size we create. When suffixed with K, M, or G, the specified + size is parsed as Kilobytes, Megabytes, or Gigabytes, respectively, to the base of 1024.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><varname>OverheadBytes=</varname></term> + <listitem> + <para>Takes an unsigned integer which specifies per-packet size overhead used in rate + computations. When suffixed with K, M, or G, the specified size is parsed as Kilobytes, + Megabytes, or Gigabytes, respectively, to the base of 1024.</para> </listitem> </varlistentry> @@ -3247,6 +3281,24 @@ is used.</para> </listitem> </varlistentry> + + <varlistentry> + <term><varname>BufferBytes=</varname></term> + <listitem> + <para>Specifies the maximum bytes burst which can be accumulated during idle period. When suffixed + with K, M, or G, the specified size is parsed as Kilobytes, Megabytes, or Gigabytes, respectively, + to the base of 1024.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><varname>CeilBufferBytes=</varname></term> + <listitem> + <para>Specifies the maximum bytes burst for ceil which can be accumulated during idle period. + When suffixed with K, M, or G, the specified size is parsed as Kilobytes, Megabytes, or Gigabytes, + respectively, to the base of 1024.</para> + </listitem> + </varlistentry> </variablelist> </refsect1> diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 84ea5d552e..030ed6fba6 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -352,11 +352,17 @@ HeavyHitterFilter.PacketLimit, config_parse_heavy_hitter_filter_pa 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 +HierarchyTokenBucket.RateToQuantum, config_parse_hierarchy_token_bucket_u32, 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 +HierarchyTokenBucketClass.Priority, config_parse_hierarchy_token_bucket_class_u32, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.QuantumBytes, config_parse_hierarchy_token_bucket_class_size, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.MTUBytes, config_parse_hierarchy_token_bucket_class_size, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.OverheadBytes, config_parse_hierarchy_token_bucket_class_size, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.Rate, config_parse_hierarchy_token_bucket_class_rate, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.CeilRate, config_parse_hierarchy_token_bucket_class_rate, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.BufferBytes, config_parse_hierarchy_token_bucket_class_size, TCLASS_KIND_HTB, 0 +HierarchyTokenBucketClass.CeilBufferBytes, config_parse_hierarchy_token_bucket_class_size, 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/tc/htb.c b/src/network/tc/htb.c index f2b9c4507e..227d6233e7 100644 --- a/src/network/tc/htb.c +++ b/src/network/tc/htb.c @@ -11,10 +11,12 @@ #include "string-util.h" #include "tc-util.h" +#define HTB_DEFAULT_RATE_TO_QUANTUM 10 +#define HTB_DEFAULT_MTU 1600 /* Ethernet packet length */ + 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; @@ -25,6 +27,7 @@ static int hierarchy_token_bucket_fill_message(Link *link, QDisc *qdisc, sd_netl htb = HTB(qdisc); + opt.rate2quantum = htb->rate_to_quantum; opt.defcls = htb->default_class; r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "htb"); @@ -92,16 +95,80 @@ int config_parse_hierarchy_token_bucket_default_class( 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_(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->rate_to_quantum = HTB_DEFAULT_RATE_TO_QUANTUM; + + qdisc = NULL; + return 0; + } + + r = safe_atou32(rvalue, &htb->rate_to_quantum); + 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; +} + +static int hierarchy_token_bucket_init(QDisc *qdisc) { + HierarchyTokenBucket *htb; + + assert(qdisc); + + htb = HTB(qdisc); + + htb->rate_to_quantum = HTB_DEFAULT_RATE_TO_QUANTUM; + + return 0; +} + const QDiscVTable htb_vtable = { .object_size = sizeof(HierarchyTokenBucket), .tca_kind = "htb", .fill_message = hierarchy_token_bucket_fill_message, + .init = hierarchy_token_bucket_init, }; 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 */ + uint32_t rtab[256], ctab[256]; int r; assert(link); @@ -110,25 +177,26 @@ static int hierarchy_token_bucket_class_fill_message(Link *link, TClass *tclass, htb = TCLASS_TO_HTB(tclass); - if (htb->ceil_rate == 0) - htb->ceil_rate = htb->rate; - opt.prio = htb->priority; + opt.quantum = htb->quantum; 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); + opt.rate.overhead = htb->overhead; + opt.ceil.overhead = htb->overhead; + + r = tc_transmit_time(htb->rate, htb->buffer, &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); + r = tc_transmit_time(htb->ceil_rate, htb->ceil_buffer, &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); + r = tc_fill_ratespec_and_table(&opt.rate, rtab, htb->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); + r = tc_fill_ratespec_and_table(&opt.ceil, ctab, htb->mtu); if (r < 0) return log_link_error_errno(link, r, "Failed to calculate ceil rate table: %m"); @@ -166,7 +234,7 @@ static int hierarchy_token_bucket_class_fill_message(Link *link, TClass *tclass, return 0; } -int config_parse_hierarchy_token_bucket_u32( +int config_parse_hierarchy_token_bucket_class_u32( const char *unit, const char *filename, unsigned line, @@ -181,6 +249,7 @@ int config_parse_hierarchy_token_bucket_u32( _cleanup_(tclass_free_or_set_invalidp) TClass *tclass = NULL; HierarchyTokenBucketClass *htb; Network *network = data; + uint32_t v; int r; assert(filename); @@ -197,25 +266,105 @@ int config_parse_hierarchy_token_bucket_u32( if (isempty(rvalue)) { htb->priority = 0; + tclass = 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; + } + + htb->priority = v; + tclass = NULL; + + return 0; +} + +int config_parse_hierarchy_token_bucket_class_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_(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 (isempty(rvalue)) { + if (streq(lvalue, "QuantumBytes")) + htb->quantum = 0; + else if (streq(lvalue, "MTUBytes")) + htb->mtu = HTB_DEFAULT_MTU; + else if (streq(lvalue, "OverheadBytes")) + htb->overhead = 0; + else if (streq(lvalue, "BufferBytes")) + htb->buffer = 0; + else if (streq(lvalue, "CeilBufferBytes")) + htb->ceil_buffer = 0; + else + assert_not_reached("Invalid lvalue"); tclass = NULL; return 0; } - r = safe_atou32(rvalue, &htb->priority); + r = parse_size(rvalue, 1024, &v); if (r < 0) { log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse '%s=', ignoring assignment: %s", lvalue, rvalue); return 0; } + if ((streq(lvalue, "OverheadBytes") && v > UINT16_MAX) || v > UINT32_MAX) { + log_syntax(unit, LOG_ERR, filename, line, 0, + "Invalid '%s=', ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + if (streq(lvalue, "QuantumBytes")) + htb->quantum = v; + else if (streq(lvalue, "OverheadBytes")) + htb->overhead = v; + else if (streq(lvalue, "MTUBytes")) + htb->mtu = v; + else if (streq(lvalue, "BufferBytes")) + htb->buffer = v; + else if (streq(lvalue, "CeilBufferBytes")) + htb->ceil_buffer = v; + else + assert_not_reached("Invalid lvalue"); tclass = NULL; return 0; } -int config_parse_hierarchy_token_bucket_rate( +int config_parse_hierarchy_token_bucket_class_rate( const char *unit, const char *filename, unsigned line, @@ -272,8 +421,53 @@ int config_parse_hierarchy_token_bucket_rate( return 0; } +static int hierarchy_token_bucket_class_init(TClass *tclass) { + HierarchyTokenBucketClass *htb; + + assert(tclass); + + htb = TCLASS_TO_HTB(tclass); + + htb->mtu = HTB_DEFAULT_MTU; + + return 0; +} + +static int hierarchy_token_bucket_class_verify(TClass *tclass) { + HierarchyTokenBucketClass *htb; + uint32_t hz; + int r; + + assert(tclass); + + htb = TCLASS_TO_HTB(tclass); + + if (htb->rate == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Rate= is mandatory. " + "Ignoring [HierarchyTokenBucketClass] section from line %u.", + tclass->section->filename, tclass->section->line); + + /* if CeilRate= setting is missing, use the same as Rate= */ + if (htb->ceil_rate == 0) + htb->ceil_rate = htb->rate; + + r = tc_init(NULL, &hz); + if (r < 0) + return log_error_errno(r, "Failed to read /proc/net/psched: %m"); + + if (htb->buffer == 0) + htb->buffer = htb->rate / hz + htb->mtu; + if (htb->ceil_buffer == 0) + htb->ceil_buffer = htb->ceil_rate / hz + htb->mtu; + + return 0; +} + const TClassVTable htb_tclass_vtable = { .object_size = sizeof(HierarchyTokenBucketClass), .tca_kind = "htb", .fill_message = hierarchy_token_bucket_class_fill_message, + .init = hierarchy_token_bucket_class_init, + .verify = hierarchy_token_bucket_class_verify, }; diff --git a/src/network/tc/htb.h b/src/network/tc/htb.h index c8dce2c1e3..b385872e0a 100644 --- a/src/network/tc/htb.h +++ b/src/network/tc/htb.h @@ -9,23 +9,31 @@ typedef struct HierarchyTokenBucket { QDisc meta; uint32_t default_class; + uint32_t rate_to_quantum; } HierarchyTokenBucket; DEFINE_QDISC_CAST(HTB, HierarchyTokenBucket); extern const QDiscVTable htb_vtable; CONFIG_PARSER_PROTOTYPE(config_parse_hierarchy_token_bucket_default_class); +CONFIG_PARSER_PROTOTYPE(config_parse_hierarchy_token_bucket_u32); typedef struct HierarchyTokenBucketClass { TClass meta; uint32_t priority; + uint32_t quantum; + uint32_t mtu; + uint16_t overhead; uint64_t rate; + uint32_t buffer; uint64_t ceil_rate; + uint32_t ceil_buffer; } 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); +CONFIG_PARSER_PROTOTYPE(config_parse_hierarchy_token_bucket_class_u32); +CONFIG_PARSER_PROTOTYPE(config_parse_hierarchy_token_bucket_class_size); +CONFIG_PARSER_PROTOTYPE(config_parse_hierarchy_token_bucket_class_rate); diff --git a/src/network/tc/tc-util.c b/src/network/tc/tc-util.c index 47371a841b..5f25acbdd6 100644 --- a/src/network/tc/tc-util.c +++ b/src/network/tc/tc-util.c @@ -8,38 +8,46 @@ #include "tc-util.h" #include "time-util.h" -static int tc_init(double *ticks_in_usec) { - uint32_t clock_resolution, ticks_to_usec, usec_to_ticks; - _cleanup_free_ char *line = NULL; - double clock_factor; - int r; +int tc_init(double *ret_ticks_in_usec, uint32_t *ret_hz) { + static double ticks_in_usec = -1; + static uint32_t hz; - r = read_one_line_file("/proc/net/psched", &line); - if (r < 0) - return r; + if (ticks_in_usec < 0) { + uint32_t clock_resolution, ticks_to_usec, usec_to_ticks; + _cleanup_free_ char *line = NULL; + double clock_factor; + int r; - r = sscanf(line, "%08x%08x%08x", &ticks_to_usec, &usec_to_ticks, &clock_resolution); - if (r < 3) - return -EIO; + r = read_one_line_file("/proc/net/psched", &line); + if (r < 0) + return r; - clock_factor = (double) clock_resolution / USEC_PER_SEC; - *ticks_in_usec = (double) ticks_to_usec / usec_to_ticks * clock_factor; + r = sscanf(line, "%08x%08x%08x%08x", &ticks_to_usec, &usec_to_ticks, &clock_resolution, &hz); + if (r < 4) + return -EIO; + + clock_factor = (double) clock_resolution / USEC_PER_SEC; + ticks_in_usec = (double) ticks_to_usec / usec_to_ticks * clock_factor; + } + + if (ret_ticks_in_usec) + *ret_ticks_in_usec = ticks_in_usec; + if (ret_hz) + *ret_hz = hz; return 0; } int tc_time_to_tick(usec_t t, uint32_t *ret) { - static double ticks_in_usec = -1; + double ticks_in_usec; usec_t a; int r; assert(ret); - if (ticks_in_usec < 0) { - r = tc_init(&ticks_in_usec); - if (r < 0) - return r; - } + r = tc_init(&ticks_in_usec, NULL); + if (r < 0) + return r; a = t * ticks_in_usec; if (a > UINT32_MAX) diff --git a/src/network/tc/tc-util.h b/src/network/tc/tc-util.h index 38b9d0786d..6287b35a76 100644 --- a/src/network/tc/tc-util.h +++ b/src/network/tc/tc-util.h @@ -6,6 +6,7 @@ #include "time-util.h" +int tc_init(double *ret_ticks_in_usec, uint32_t *ret_hz); 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); diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index a6ef817360..40b936e9dc 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -378,12 +378,18 @@ Id= Parent= Handle= DefaultClass= +RateToQuantum= [HierarchyTokenBucketClass] Parent= ClassId= Priority= +QuantumBytes= +MTUBytes= +OverheadBytes= Rate= CeilRate= +BufferBytes= +CeilBufferBytes= [BFIFO] Parent= Handle= |