| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| |
| #include <linux/pkt_sched.h> |
| |
| #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" |
| |
| #define HTB_DEFAULT_RATE_TO_QUANTUM 10 |
| #define HTB_DEFAULT_MTU 1600 /* Ethernet packet length */ |
| |
| static int hierarchy_token_bucket_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { |
| HierarchyTokenBucket *htb; |
| struct tc_htb_glob opt = { |
| .version = 3, |
| }; |
| int r; |
| |
| assert(link); |
| assert(qdisc); |
| assert(req); |
| |
| htb = HTB(qdisc); |
| |
| opt.rate2quantum = htb->rate_to_quantum; |
| opt.defcls = htb->default_class; |
| |
| r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "htb"); |
| 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) { |
| log_syntax(unit, LOG_WARNING, filename, line, r, |
| "More than one kind of queueing discipline, ignoring assignment: %m"); |
| return 0; |
| } |
| |
| 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_WARNING, filename, line, r, |
| "Failed to parse '%s=', ignoring assignment: %s", |
| lvalue, rvalue); |
| return 0; |
| } |
| |
| qdisc = NULL; |
| |
| return 0; |
| } |
| |
| int config_parse_hierarchy_token_bucket_u32( |
| const char *unit, |
| const char *filename, |
| unsigned line, |
| const char *section, |
| unsigned section_line, |
| const char *lvalue, |
| int ltype, |
| const char *rvalue, |
| void *data, |
| void *userdata) { |
| |
| _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL; |
| HierarchyTokenBucket *htb; |
| Network *network = data; |
| int r; |
| |
| assert(filename); |
| assert(lvalue); |
| assert(rvalue); |
| assert(data); |
| |
| r = qdisc_new_static(QDISC_KIND_HTB, network, filename, section_line, &qdisc); |
| if (r == -ENOMEM) |
| return log_oom(); |
| if (r < 0) { |
| log_syntax(unit, LOG_WARNING, filename, line, r, |
| "More than one kind of queueing discipline, ignoring assignment: %m"); |
| return 0; |
| } |
| |
| htb = HTB(qdisc); |
| |
| if (isempty(rvalue)) { |
| htb->rate_to_quantum = HTB_DEFAULT_RATE_TO_QUANTUM; |
| |
| qdisc = NULL; |
| return 0; |
| } |
| |
| r = safe_atou32(rvalue, &htb->rate_to_quantum); |
| if (r < 0) { |
| log_syntax(unit, LOG_WARNING, filename, line, r, |
| "Failed to parse '%s=', ignoring assignment: %s", |
| lvalue, rvalue); |
| return 0; |
| } |
| |
| qdisc = NULL; |
| |
| return 0; |
| } |
| |
| static int hierarchy_token_bucket_init(QDisc *qdisc) { |
| HierarchyTokenBucket *htb; |
| |
| assert(qdisc); |
| |
| htb = HTB(qdisc); |
| |
| htb->rate_to_quantum = HTB_DEFAULT_RATE_TO_QUANTUM; |
| |
| return 0; |
| } |
| |
| const QDiscVTable htb_vtable = { |
| .object_size = sizeof(HierarchyTokenBucket), |
| .tca_kind = "htb", |
| .fill_message = hierarchy_token_bucket_fill_message, |
| .init = hierarchy_token_bucket_init, |
| }; |
| |
| static int hierarchy_token_bucket_class_fill_message(Link *link, TClass *tclass, sd_netlink_message *req) { |
| HierarchyTokenBucketClass *htb; |
| struct tc_htb_opt opt = {}; |
| uint32_t rtab[256], ctab[256]; |
| int r; |
| |
| assert(link); |
| assert(tclass); |
| assert(req); |
| |
| htb = TCLASS_TO_HTB(tclass); |
| |
| opt.prio = htb->priority; |
| opt.quantum = htb->quantum; |
| opt.rate.rate = (htb->rate >= (1ULL << 32)) ? ~0U : htb->rate; |
| opt.ceil.rate = (htb->ceil_rate >= (1ULL << 32)) ? ~0U : htb->ceil_rate; |
| opt.rate.overhead = htb->overhead; |
| opt.ceil.overhead = htb->overhead; |
| |
| r = tc_transmit_time(htb->rate, htb->buffer, &opt.buffer); |
| if (r < 0) |
| return log_link_error_errno(link, r, "Failed to calculate buffer size: %m"); |
| |
| r = tc_transmit_time(htb->ceil_rate, htb->ceil_buffer, &opt.cbuffer); |
| if (r < 0) |
| return log_link_error_errno(link, r, "Failed to calculate ceil buffer size: %m"); |
| |
| r = tc_fill_ratespec_and_table(&opt.rate, rtab, htb->mtu); |
| if (r < 0) |
| return log_link_error_errno(link, r, "Failed to calculate rate table: %m"); |
| |
| r = tc_fill_ratespec_and_table(&opt.ceil, ctab, htb->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_class_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; |
| uint32_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 == -ENOMEM) |
| return log_oom(); |
| if (r < 0) { |
| log_syntax(unit, LOG_WARNING, filename, line, r, |
| "Failed to create traffic control class, ignoring assignment: %m"); |
| return 0; |
| } |
| |
| htb = TCLASS_TO_HTB(tclass); |
| |
| if (isempty(rvalue)) { |
| htb->priority = 0; |
| tclass = NULL; |
| return 0; |
| } |
| |
| r = safe_atou32(rvalue, &v); |
| if (r < 0) { |
| log_syntax(unit, LOG_WARNING, filename, line, r, |
| "Failed to parse '%s=', ignoring assignment: %s", |
| lvalue, rvalue); |
| return 0; |
| } |
| |
| htb->priority = v; |
| tclass = NULL; |
| |
| return 0; |
| } |
| |
| int config_parse_hierarchy_token_bucket_class_size( |
| const char *unit, |
| const char *filename, |
| unsigned line, |
| const char *section, |
| unsigned section_line, |
| const char *lvalue, |
| int ltype, |
| const char *rvalue, |
| void *data, |
| void *userdata) { |
| |
| _cleanup_(tclass_free_or_set_invalidp) TClass *tclass = NULL; |
| HierarchyTokenBucketClass *htb; |
| Network *network = data; |
| uint64_t v; |
| int r; |
| |
| assert(filename); |
| assert(lvalue); |
| assert(rvalue); |
| assert(data); |
| |
| r = tclass_new_static(TCLASS_KIND_HTB, network, filename, section_line, &tclass); |
| if (r == -ENOMEM) |
| return log_oom(); |
| if (r < 0) { |
| log_syntax(unit, LOG_WARNING, filename, line, r, |
| "Failed to create traffic control class, ignoring assignment: %m"); |
| return 0; |
| } |
| |
| htb = TCLASS_TO_HTB(tclass); |
| |
| if (isempty(rvalue)) { |
| if (streq(lvalue, "QuantumBytes")) |
| htb->quantum = 0; |
| else if (streq(lvalue, "MTUBytes")) |
| htb->mtu = HTB_DEFAULT_MTU; |
| else if (streq(lvalue, "OverheadBytes")) |
| htb->overhead = 0; |
| else if (streq(lvalue, "BufferBytes")) |
| htb->buffer = 0; |
| else if (streq(lvalue, "CeilBufferBytes")) |
| htb->ceil_buffer = 0; |
| else |
| assert_not_reached("Invalid lvalue"); |
| |
| tclass = NULL; |
| return 0; |
| } |
| |
| r = parse_size(rvalue, 1024, &v); |
| if (r < 0) { |
| log_syntax(unit, LOG_WARNING, filename, line, r, |
| "Failed to parse '%s=', ignoring assignment: %s", |
| lvalue, rvalue); |
| return 0; |
| } |
| if ((streq(lvalue, "OverheadBytes") && v > UINT16_MAX) || v > UINT32_MAX) { |
| log_syntax(unit, LOG_WARNING, filename, line, 0, |
| "Invalid '%s=', ignoring assignment: %s", |
| lvalue, rvalue); |
| return 0; |
| } |
| |
| if (streq(lvalue, "QuantumBytes")) |
| htb->quantum = v; |
| else if (streq(lvalue, "OverheadBytes")) |
| htb->overhead = v; |
| else if (streq(lvalue, "MTUBytes")) |
| htb->mtu = v; |
| else if (streq(lvalue, "BufferBytes")) |
| htb->buffer = v; |
| else if (streq(lvalue, "CeilBufferBytes")) |
| htb->ceil_buffer = v; |
| else |
| assert_not_reached("Invalid lvalue"); |
| |
| tclass = NULL; |
| |
| return 0; |
| } |
| |
| int config_parse_hierarchy_token_bucket_class_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 == -ENOMEM) |
| return log_oom(); |
| if (r < 0) { |
| log_syntax(unit, LOG_WARNING, filename, line, r, |
| "Failed to create traffic control class, ignoring assignment: %m"); |
| return 0; |
| } |
| |
| 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_WARNING, filename, line, r, |
| "Failed to parse '%s=', ignoring assignment: %s", |
| lvalue, rvalue); |
| return 0; |
| } |
| |
| *v /= 8; |
| tclass = NULL; |
| |
| return 0; |
| } |
| |
| static int hierarchy_token_bucket_class_init(TClass *tclass) { |
| HierarchyTokenBucketClass *htb; |
| |
| assert(tclass); |
| |
| htb = TCLASS_TO_HTB(tclass); |
| |
| htb->mtu = HTB_DEFAULT_MTU; |
| |
| return 0; |
| } |
| |
| static int hierarchy_token_bucket_class_verify(TClass *tclass) { |
| HierarchyTokenBucketClass *htb; |
| uint32_t hz; |
| int r; |
| |
| assert(tclass); |
| |
| htb = TCLASS_TO_HTB(tclass); |
| |
| if (htb->rate == 0) |
| return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), |
| "%s: Rate= is mandatory. " |
| "Ignoring [HierarchyTokenBucketClass] section from line %u.", |
| tclass->section->filename, tclass->section->line); |
| |
| /* if CeilRate= setting is missing, use the same as Rate= */ |
| if (htb->ceil_rate == 0) |
| htb->ceil_rate = htb->rate; |
| |
| r = tc_init(NULL, &hz); |
| if (r < 0) |
| return log_error_errno(r, "Failed to read /proc/net/psched: %m"); |
| |
| if (htb->buffer == 0) |
| htb->buffer = htb->rate / hz + htb->mtu; |
| if (htb->ceil_buffer == 0) |
| htb->ceil_buffer = htb->ceil_rate / hz + htb->mtu; |
| |
| return 0; |
| } |
| |
| const TClassVTable htb_tclass_vtable = { |
| .object_size = sizeof(HierarchyTokenBucketClass), |
| .tca_kind = "htb", |
| .fill_message = hierarchy_token_bucket_class_fill_message, |
| .init = hierarchy_token_bucket_class_init, |
| .verify = hierarchy_token_bucket_class_verify, |
| }; |