diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 146401f6c90..a4ca67a27f7 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -3252,6 +3252,45 @@
+
+ [QuickFairQueueing] Section Options
+ The [QuickFairQueueing] section manages the queueing discipline
+ (qdisc) of Quick Fair Queueing (QFQ).
+
+
+
+
+
+
+
+
+ [QuickFairQueueingClass] Section Options
+ The [QuickFairQueueingClass] section manages the traffic control class of
+ Quick Fair Queueing (qfq).
+
+
+
+
+
+
+ Weight=
+
+ Specifies the weight of the class. Takse an integer in the range 1..1023. Defaults to
+ unset in which case the kernel default is used.
+
+
+
+
+ MaxPacketSize=
+
+ Specifies the maximum packet size in bytes for the class. When suffixed with K, M, or G, the specified
+ size is parsed as Kilobytes, Megabytes, or Gigabytes, respectively, to the base of 1000. When unset,
+ the kernel default 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 8644cc7488c..d9810b2298c 100644
--- a/src/libsystemd/sd-netlink/netlink-types.c
+++ b/src/libsystemd/sd-netlink/netlink-types.c
@@ -810,6 +810,11 @@ static const NLType rtnl_tca_option_data_pie_types[] = {
[TCA_PIE_LIMIT] = { .type = NETLINK_TYPE_U32 },
};
+static const NLType rtnl_tca_option_data_qfq_types[] = {
+ [TCA_QFQ_WEIGHT] = { .type = NETLINK_TYPE_U32 },
+ [TCA_QFQ_LMAX] = { .type = NETLINK_TYPE_U32 },
+};
+
static const NLType rtnl_tca_option_data_sfb_types[] = {
[TCA_SFB_PARMS] = { .size = sizeof(struct tc_sfb_qopt) },
};
@@ -834,6 +839,7 @@ static const char* const nl_union_tca_option_data_table[] = {
[NL_UNION_TCA_OPTION_DATA_HHF] = "hhf",
[NL_UNION_TCA_OPTION_DATA_HTB] = "htb",
[NL_UNION_TCA_OPTION_DATA_PIE] = "pie",
+ [NL_UNION_TCA_OPTION_DATA_QFQ] = "qfq",
[NL_UNION_TCA_OPTION_DATA_SFB] = "sfb",
[NL_UNION_TCA_OPTION_DATA_TBF] = "tbf",
};
@@ -859,6 +865,8 @@ static const NLTypeSystem rtnl_tca_option_data_type_systems[] = {
.types = rtnl_tca_option_data_htb_types },
[NL_UNION_TCA_OPTION_DATA_PIE] = { .count = ELEMENTSOF(rtnl_tca_option_data_pie_types),
.types = rtnl_tca_option_data_pie_types },
+ [NL_UNION_TCA_OPTION_DATA_QFQ] = { .count = ELEMENTSOF(rtnl_tca_option_data_qfq_types),
+ .types = rtnl_tca_option_data_qfq_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),
diff --git a/src/libsystemd/sd-netlink/netlink-types.h b/src/libsystemd/sd-netlink/netlink-types.h
index 08b91c78c06..1a0e1997cc1 100644
--- a/src/libsystemd/sd-netlink/netlink-types.h
+++ b/src/libsystemd/sd-netlink/netlink-types.h
@@ -105,6 +105,7 @@ typedef enum NLUnionTCAOptionData {
NL_UNION_TCA_OPTION_DATA_HHF,
NL_UNION_TCA_OPTION_DATA_HTB,
NL_UNION_TCA_OPTION_DATA_PIE,
+ NL_UNION_TCA_OPTION_DATA_QFQ,
NL_UNION_TCA_OPTION_DATA_SFB,
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 35b35a76a6e..9cd5796f099 100644
--- a/src/network/meson.build
+++ b/src/network/meson.build
@@ -131,6 +131,8 @@ sources = files('''
tc/pie.h
tc/qdisc.c
tc/qdisc.h
+ tc/qfq.c
+ tc/qfq.h
tc/sfb.c
tc/sfb.h
tc/sfq.c
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index b3dde3bfac6..39188906648 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -312,6 +312,12 @@ PFIFOFast.Handle, config_parse_qdisc_handle,
PFIFOHeadDrop.Parent, config_parse_qdisc_parent, QDISC_KIND_PFIFO_HEAD_DROP, 0
PFIFOHeadDrop.Handle, config_parse_qdisc_handle, QDISC_KIND_PFIFO_HEAD_DROP, 0
PFIFOHeadDrop.PacketLimit, config_parse_pfifo_size, QDISC_KIND_PFIFO_HEAD_DROP, 0
+QuickFairQueueing.Parent, config_parse_qdisc_parent, QDISC_KIND_QFQ, 0
+QuickFairQueueing.Handle, config_parse_qdisc_handle, QDISC_KIND_QFQ, 0
+QuickFairQueueingClass.Parent, config_parse_tclass_parent, TCLASS_KIND_QFQ, 0
+QuickFairQueueingClass.ClassId, config_parse_tclass_classid, TCLASS_KIND_QFQ, 0
+QuickFairQueueingClass.Weight, config_parse_quick_fair_queueing_weight, TCLASS_KIND_QFQ, 0
+QuickFairQueueingClass.MaxPacketSize, config_parse_quick_fair_queueing_max_packet, TCLASS_KIND_QFQ, 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 2fdaafd040d..124c570b0e4 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -508,9 +508,6 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
"ControlledDelay\0"
"DeficitRoundRobinScheduler\0"
"DeficitRoundRobinSchedulerClass\0"
- "PFIFO\0"
- "PFIFOFast\0"
- "PFIFOHeadDrop\0"
"FairQueueing\0"
"FairQueueingControlledDelay\0"
"GenericRandomEarlyDetection\0"
@@ -518,7 +515,12 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
"HierarchyTokenBucket\0"
"HierarchyTokenBucketClass\0"
"NetworkEmulator\0"
+ "PFIFO\0"
+ "PFIFOFast\0"
+ "PFIFOHeadDrop\0"
"PIE\0"
+ "QuickFairQueueing\0"
+ "QuickFairQueueingClass\0"
"StochasticFairBlue\0"
"StochasticFairnessQueueing\0"
"TokenBucketFilter\0"
diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c
index 543f71330f7..6ba4325c9c5 100644
--- a/src/network/tc/qdisc.c
+++ b/src/network/tc/qdisc.c
@@ -27,6 +27,7 @@ const QDiscVTable * const qdisc_vtable[_QDISC_KIND_MAX] = {
[QDISC_KIND_HTB] = &htb_vtable,
[QDISC_KIND_NETEM] = &netem_vtable,
[QDISC_KIND_PIE] = &pie_vtable,
+ [QDISC_KIND_QFQ] = &qfq_vtable,
[QDISC_KIND_PFIFO] = &pfifo_vtable,
[QDISC_KIND_PFIFO_FAST] = &pfifo_fast_vtable,
[QDISC_KIND_PFIFO_HEAD_DROP] = &pfifo_head_drop_vtable,
diff --git a/src/network/tc/qdisc.h b/src/network/tc/qdisc.h
index 5c43d7a8381..802653efb01 100644
--- a/src/network/tc/qdisc.h
+++ b/src/network/tc/qdisc.h
@@ -23,6 +23,7 @@ typedef enum QDiscKind {
QDISC_KIND_PFIFO_FAST,
QDISC_KIND_PFIFO_HEAD_DROP,
QDISC_KIND_PIE,
+ QDISC_KIND_QFQ,
QDISC_KIND_SFB,
QDISC_KIND_SFQ,
QDISC_KIND_TBF,
@@ -93,6 +94,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_qdisc_handle);
#include "hhf.h"
#include "htb.h"
#include "pie.h"
+#include "qfq.h"
#include "netem.h"
#include "drr.h"
#include "sfb.h"
diff --git a/src/network/tc/qfq.c b/src/network/tc/qfq.c
new file mode 100644
index 00000000000..71d5b15e81c
--- /dev/null
+++ b/src/network/tc/qfq.c
@@ -0,0 +1,170 @@
+/* SPDX-License-Identifier: LGPL-2.1+
+ * Copyright © 2020 VMware, Inc. */
+
+#include
+
+#include "parse-util.h"
+#include "qdisc.h"
+#include "qfq.h"
+#include "string-util.h"
+
+#define QFQ_MAX_WEIGHT (1 << 10)
+#define QFQ_MIN_MAX_PACKET 512
+#define QFQ_MAX_MAX_PACKET (1 << 16)
+
+const QDiscVTable qfq_vtable = {
+ .object_size = sizeof(QuickFairQueueing),
+ .tca_kind = "qfq",
+};
+
+static int quick_fair_queueing_class_fill_message(Link *link, TClass *tclass, sd_netlink_message *req) {
+ QuickFairQueueingClass *qfq;
+ int r;
+
+ assert(link);
+ assert(tclass);
+ assert(req);
+
+ qfq = TCLASS_TO_QFQ(tclass);
+
+ r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "qfq");
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not open container TCA_OPTIONS: %m");
+
+ if (qfq->weight > 0) {
+ r = sd_netlink_message_append_u32(req, TCA_QFQ_WEIGHT, qfq->weight);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append TCA_QFQ_WEIGHT attribute: %m");
+ }
+
+ if (qfq->max_packet > 0) {
+ r = sd_netlink_message_append_u32(req, TCA_QFQ_LMAX, qfq->max_packet);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append TCA_QFQ_LMAX 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_quick_fair_queueing_weight(
+ 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;
+ QuickFairQueueingClass *qfq;
+ Network *network = data;
+ uint32_t v;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = tclass_new_static(TCLASS_KIND_QFQ, 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");
+
+ qfq = TCLASS_TO_QFQ(tclass);
+
+ if (isempty(rvalue)) {
+ qfq->weight = 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;
+ }
+
+ if (v == 0 || v > QFQ_MAX_WEIGHT) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "Invalid '%s=', ignoring assignment: %s",
+ lvalue, rvalue);
+ return 0;
+ }
+
+ qfq->weight = v;
+ tclass = NULL;
+
+ return 0;
+}
+
+int config_parse_quick_fair_queueing_max_packet(
+ 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;
+ QuickFairQueueingClass *qfq;
+ Network *network = data;
+ uint64_t v;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = tclass_new_static(TCLASS_KIND_QFQ, 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");
+
+ qfq = TCLASS_TO_QFQ(tclass);
+
+ if (isempty(rvalue)) {
+ qfq->max_packet = 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;
+ }
+
+ if (v < QFQ_MIN_MAX_PACKET || v > QFQ_MAX_MAX_PACKET) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "Invalid '%s=', ignoring assignment: %s",
+ lvalue, rvalue);
+ return 0;
+ }
+
+ qfq->max_packet = (uint32_t) v;
+ tclass = NULL;
+
+ return 0;
+}
+
+const TClassVTable qfq_tclass_vtable = {
+ .object_size = sizeof(QuickFairQueueingClass),
+ .tca_kind = "qfq",
+ .fill_message = quick_fair_queueing_class_fill_message,
+};
diff --git a/src/network/tc/qfq.h b/src/network/tc/qfq.h
new file mode 100644
index 00000000000..10bab3e642d
--- /dev/null
+++ b/src/network/tc/qfq.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: LGPL-2.1+
+ * Copyright © 2020 VMware, Inc. */
+#pragma once
+
+#include "conf-parser.h"
+#include "qdisc.h"
+
+typedef struct QuickFairQueueing {
+ QDisc meta;
+} QuickFairQueueing;
+
+DEFINE_QDISC_CAST(QFQ, QuickFairQueueing);
+extern const QDiscVTable qfq_vtable;
+
+typedef struct QuickFairQueueingClass {
+ TClass meta;
+
+ uint32_t weight;
+ uint32_t max_packet;
+} QuickFairQueueingClass;
+
+DEFINE_TCLASS_CAST(QFQ, QuickFairQueueingClass);
+extern const TClassVTable qfq_tclass_vtable;
+
+CONFIG_PARSER_PROTOTYPE(config_parse_quick_fair_queueing_weight);
+CONFIG_PARSER_PROTOTYPE(config_parse_quick_fair_queueing_max_packet);
diff --git a/src/network/tc/tclass.c b/src/network/tc/tclass.c
index 219ffa2ea6f..4f75e1b42dc 100644
--- a/src/network/tc/tclass.c
+++ b/src/network/tc/tclass.c
@@ -18,6 +18,7 @@
const TClassVTable * const tclass_vtable[_TCLASS_KIND_MAX] = {
[TCLASS_KIND_DRR] = &drr_tclass_vtable,
[TCLASS_KIND_HTB] = &htb_tclass_vtable,
+ [TCLASS_KIND_QFQ] = &qfq_tclass_vtable,
};
static int tclass_new(TClassKind kind, TClass **ret) {
diff --git a/src/network/tc/tclass.h b/src/network/tc/tclass.h
index 217dbfe0017..dc6886ac3fe 100644
--- a/src/network/tc/tclass.h
+++ b/src/network/tc/tclass.h
@@ -11,6 +11,7 @@
typedef enum TClassKind {
TCLASS_KIND_DRR,
TCLASS_KIND_HTB,
+ TCLASS_KIND_QFQ,
_TCLASS_KIND_MAX,
_TCLASS_KIND_INVALID = -1,
} TClassKind;
@@ -67,3 +68,4 @@ CONFIG_PARSER_PROTOTYPE(config_parse_tclass_classid);
#include "drr.h"
#include "htb.h"
+#include "qfq.h"
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index 0b62420e0a9..7cade0e9edc 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -411,6 +411,14 @@ PacketLimit=
Parent=
Handle=
PacketLimit=
+[QuickFairQueueing]
+Parent=
+Handle=
+[QuickFairQueueingClass]
+Parent=
+ClassId=
+Weight=
+MaxPacketSize=
[DeficitRoundRobinScheduler]
Parent=
Handle=
diff --git a/test/test-network/conf/25-qdisc-qfq.network b/test/test-network/conf/25-qdisc-qfq.network
new file mode 100644
index 00000000000..c94fc9e4cba
--- /dev/null
+++ b/test/test-network/conf/25-qdisc-qfq.network
@@ -0,0 +1,22 @@
+[Match]
+Name=test1
+
+[Network]
+IPv6AcceptRA=no
+Address=10.1.2.4/16
+
+[QuickFairQueueing]
+Parent=root
+Handle=0002
+
+[QuickFairQueueingClass]
+Parent=root
+ClassId=0002:0030
+Weight=2
+MaxPacketSize=16000
+
+[QuickFairQueueingClass]
+Parent=root
+ClassId=0002:0031
+Weight=10
+MaxPacketSize=8000
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index 78309d26515..da4063d51c9 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -1676,6 +1676,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
'25-qdisc-hhf.network',
'25-qdisc-ingress-netem-compat.network',
'25-qdisc-pie.network',
+ '25-qdisc-qfq.network',
'25-route-ipv6-src.network',
'25-route-static.network',
'25-route-vrf.network',
@@ -2423,10 +2424,11 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
self.assertRegex(output, 'prio 1 rate 1Mbit ceil 500Kbit')
def test_qdisc2(self):
- copy_unit_to_networkd_unit_path('25-qdisc-drr.network', '12-dummy.netdev')
+ copy_unit_to_networkd_unit_path('25-qdisc-drr.network', '12-dummy.netdev',
+ '25-qdisc-qfq.network', '11-dummy.netdev')
start_networkd()
- self.wait_online(['dummy98:routable'])
+ self.wait_online(['dummy98:routable', 'test1:routable'])
output = check_output('tc qdisc show dev dummy98')
print(output)
@@ -2435,6 +2437,14 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
print(output)
self.assertRegex(output, 'class drr 2:30 root quantum 2000b')
+ output = check_output('tc qdisc show dev test1')
+ print(output)
+ self.assertRegex(output, 'qdisc qfq 2: root')
+ output = check_output('tc class show dev test1')
+ print(output)
+ self.assertRegex(output, 'class qfq 2:30 root weight 2 maxpkt 16000')
+ self.assertRegex(output, 'class qfq 2:31 root weight 10 maxpkt 8000')
+
@expectedFailureIfCAKEIsNotAvailable()
def test_qdisc_cake(self):
copy_unit_to_networkd_unit_path('25-qdisc-cake.network', '12-dummy.netdev')