summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--man/systemd.network.xml54
-rw-r--r--src/network/networkd-network-gperf.gperf12
-rw-r--r--src/network/tc/htb.c218
-rw-r--r--src/network/tc/htb.h12
-rw-r--r--src/network/tc/tc-util.c46
-rw-r--r--src/network/tc/tc-util.h1
-rw-r--r--test/fuzz/fuzz-network-parser/directives.network6
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=