diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index c6fefd8da8..b43c460267 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -2348,7 +2348,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.
@@ -2412,7 +2414,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.
@@ -2489,6 +2493,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
@@ -2499,7 +2535,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.
@@ -2520,6 +2558,82 @@
+
+ [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.
+
+
+
+
+
+
+ [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.
+
+
+
+
+
[ControlledDelay] Section Options
The [ControlledDelay] section manages the queueing discipline (qdisc) of
@@ -2530,7 +2644,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.
@@ -2584,6 +2700,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
@@ -2594,7 +2757,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.
@@ -2684,7 +2849,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.
@@ -2786,7 +2953,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.
@@ -2810,6 +2979,94 @@
+
+ [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.
+
+
+
+
+
+
+ [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/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-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.c b/src/libsystemd/sd-netlink/netlink-types.c
index e35127a4cd..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 },
@@ -780,6 +786,23 @@ 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) },
+ [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_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 },
@@ -791,21 +814,33 @@ 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",
+ [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",
};
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),
.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_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 },
};
@@ -818,16 +853,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[] = {
@@ -868,9 +903,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-types.h b/src/libsystemd/sd-netlink/netlink-types.h
index b2fa8c96e6..edc4a580b8 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,
@@ -92,9 +96,13 @@ 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,
+ 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/libsystemd/sd-netlink/netlink-util.h b/src/libsystemd/sd-netlink/netlink-util.h
index d2d8334b21..8ed94063e3 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/network/meson.build b/src/network/meson.build
index c1c02cfda1..57d3772c3e 100644
--- a/src/network/meson.build
+++ b/src/network/meson.build
@@ -107,22 +107,36 @@ 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
+ tc/fifo.h
tc/fq.c
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
tc/netem.h
tc/qdisc.c
tc/qdisc.h
+ tc/sfb.c
+ tc/sfb.h
tc/sfq.c
tc/sfq.h
tc/tbf.c
tc/tbf.h
tc/tc-util.c
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-link.c b/src/network/networkd-link.c
index bac80ade84..d7845760c5 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) {
@@ -2743,24 +2743,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;
}
@@ -2772,7 +2772,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-gperf.gperf b/src/network/networkd-network-gperf.gperf
index c0e2479460..9345e0fd1e 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;
@@ -256,6 +257,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
@@ -263,6 +268,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
@@ -284,6 +292,19 @@ 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
+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
@@ -291,6 +312,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 066263c280..3047674212 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;
}
@@ -484,10 +485,16 @@ 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"
"FairQueueingControlledDelay\0"
+ "GenericRandomEarlyDetection\0"
+ "HierarchyTokenBucket\0"
+ "HierarchyTokenBucketClass\0"
"NetworkEmulator\0"
+ "StochasticFairBlue\0"
"StochasticFairnessQueueing\0"
"TokenBucketFilter\0"
"TrivialLinkEqualizer\0",
@@ -691,7 +698,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 1e43635c54..0cb8303c9e 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 {
@@ -276,7 +275,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/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/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/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/htb.c b/src/network/tc/htb.c
new file mode 100644
index 0000000000..f2b9c4507e
--- /dev/null
+++ b/src/network/tc/htb.c
@@ -0,0 +1,279 @@
+/* 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"
+#include "tc-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,
+};
+
+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
new file mode 100644
index 0000000000..c8dce2c1e3
--- /dev/null
+++ b/src/network/tc/htb.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "conf-parser.h"
+#include "qdisc.h"
+#include "tclass.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);
+
+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/qdisc.c b/src/network/tc/qdisc.c
index 0619e894cc..31b6a716c4 100644
--- a/src/network/tc/qdisc.c
+++ b/src/network/tc/qdisc.c
@@ -12,12 +12,19 @@
#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_CAKE] = &cake_vtable,
[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,
+ [QDISC_KIND_SFB] = &sfb_vtable,
[QDISC_KIND_SFQ] = &sfq_vtable,
[QDISC_KIND_TBF] = &tbf_vtable,
[QDISC_KIND_TEQL] = &teql_vtable,
@@ -33,6 +40,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,
@@ -42,6 +50,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;
@@ -61,7 +70,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);
@@ -73,15 +83,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;
}
}
@@ -90,23 +105,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;
@@ -119,7 +134,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);
@@ -131,8 +146,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;
@@ -144,9 +159,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);
}
@@ -203,7 +218,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;
}
@@ -279,19 +294,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/qdisc.h b/src/network/tc/qdisc.h
index 8e4a70de53..403d6a5b53 100644
--- a/src/network/tc/qdisc.h
+++ b/src/network/tc/qdisc.h
@@ -6,12 +6,18 @@
#include "networkd-link.h"
#include "networkd-network.h"
#include "networkd-util.h"
+#include "tc.h"
typedef enum QDiscKind {
+ QDISC_KIND_CAKE,
QDISC_KIND_CODEL,
QDISC_KIND_FQ,
QDISC_KIND_FQ_CODEL,
+ QDISC_KIND_GRED,
+ QDISC_KIND_HTB,
QDISC_KIND_NETEM,
+ QDISC_KIND_PFIFO,
+ QDISC_KIND_SFB,
QDISC_KIND_SFQ,
QDISC_KIND_TBF,
QDISC_KIND_TEQL,
@@ -20,6 +26,8 @@ typedef enum QDiscKind {
} QDiscKind;
typedef struct QDisc {
+ TrafficControl meta;
+
NetworkConfigSection *section;
Network *network;
@@ -65,13 +73,20 @@ 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);
+#include "cake.h"
#include "codel.h"
+#include "fifo.h"
#include "fq-codel.h"
#include "fq.h"
+#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/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);
diff --git a/src/network/tc/tc.c b/src/network/tc/tc.c
new file mode 100644
index 0000000000..30a00133d6
--- /dev/null
+++ b/src/network/tc/tc.c
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "macro.h"
+#include "qdisc.h"
+#include "tc.h"
+#include "tclass.h"
+
+void traffic_control_free(TrafficControl *tc) {
+ if (!tc)
+ return;
+
+ switch (tc->kind) {
+ 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");
+ }
+}
+
+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));
+ case TC_KIND_TCLASS:
+ return tclass_configure(link, TC_TO_TCLASS(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);
+ 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/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);
diff --git a/src/network/tc/tclass.c b/src/network/tc/tclass.c
new file mode 100644
index 0000000000..87f5bcf704
--- /dev/null
+++ b/src/network/tc/tclass.c
@@ -0,0 +1,277 @@
+/* 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] = {
+ [TCLASS_KIND_HTB] = &htb_tclass_vtable,
+};
+
+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"
diff --git a/src/systemd/sd-netlink.h b/src/systemd/sd-netlink.h
index 644d462b65..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);
@@ -208,6 +212,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);
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index 82d9787958..5754e44357 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -329,6 +329,11 @@ TargetSec=
IntervalSec=
CEThresholdSec=
ECN=
+[CAKE]
+Parent=
+Handle=
+Bandwidth=
+Overhead=
[TrafficControlQueueingDiscipline]
Parent=
NetworkEmulatorDelaySec=
@@ -340,3 +345,27 @@ NetworkEmulatorPacketLimit=
Parent=
Handle=
Id=
+[HierarchyTokenBucket]
+Parent=
+Handle=
+DefaultClass=
+[HierarchyTokenBucketClass]
+Parent=
+ClassId=
+Priority=
+Rate=
+CeilRate=
+[PFIFO]
+Parent=
+Handle=
+PacketLimit=
+[GenericRandomEarlyDetection]
+Parent=
+Handle=
+VirtualQueues=
+DefaultVirtualQueue=
+GenericRIO=
+[StochasticFairBlue]
+Parent=
+Handle=
+PacketLimit=
diff --git a/test/test-network/conf/25-qdisc-teql.network b/test/test-network/conf/25-qdisc-cake.network
similarity index 64%
rename from test/test-network/conf/25-qdisc-teql.network
rename to test/test-network/conf/25-qdisc-cake.network
index c8bb9034a2..b713245dbc 100644
--- a/test/test-network/conf/25-qdisc-teql.network
+++ b/test/test-network/conf/25-qdisc-cake.network
@@ -5,7 +5,8 @@ Name=dummy98
IPv6AcceptRA=no
Address=10.1.2.3/16
-[TrivialLinkEqualizer]
+[CAKE]
Parent=root
-Handle=0002
-Id=1
+Handle=3a
+Overhead=128
+Bandwidth=500M
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..039a2ffa65
--- /dev/null
+++ b/test/test-network/conf/25-qdisc-clsact-and-htb.network
@@ -0,0 +1,162 @@
+[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
+
+[HierarchyTokenBucketClass]
+Parent=root
+ClassId=0002:0037
+Priority=1
+Rate=1M
+CeilRate=0.5M
+
+[PFIFO]
+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
+
+[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/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/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index 0824108e7e..fb0048b3e6 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -157,6 +157,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
@@ -1621,13 +1633,9 @@ 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-cake.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.network',
'25-route-ipv6-src.network',
'25-route-static.network',
'25-route-vrf.network',
@@ -2254,71 +2262,85 @@ 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.network', '12-dummy.netdev')
- check_output('modprobe sch_teql max_equalizers=2')
- start_networkd()
+ output = check_output('tc qdisc show dev dummy98')
+ print(output)
+ 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')
+
+ 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')
+
+ 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:')
+ 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, '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')
+
+ @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 teql1 2:')
+ self.assertRegex(output, 'qdisc cake 3a: root')
+ self.assertRegex(output, 'bandwidth 500Mbit')
+ self.assertRegex(output, 'overhead 128')
class NetworkdStateFileTests(unittest.TestCase, Utilities):
links = [