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 = [