/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include "alloc-util.h" #include "conf-parser.h" #include "netlink-util.h" #include "networkd-link.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; int r; assert(link); assert(qdisc); assert(req); assert_se(htb = HTB(qdisc)); struct tc_htb_glob opt = { .version = 3, .rate2quantum = htb->rate_to_quantum, .defcls = htb->default_class, }; r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "htb"); if (r < 0) return r; r = sd_netlink_message_append_data(req, TCA_HTB_INIT, &opt, sizeof(opt)); if (r < 0) return r; r = sd_netlink_message_close_container(req); if (r < 0) return r; 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 = ASSERT_PTR(data); int r; assert(filename); assert(lvalue); assert(rvalue); 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; TAKE_PTR(qdisc); 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; } TAKE_PTR(qdisc); 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 = ASSERT_PTR(data); int r; assert(filename); assert(lvalue); assert(rvalue); 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; TAKE_PTR(qdisc); 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; } TAKE_PTR(qdisc); 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; uint32_t rtab[256], ctab[256]; int r; assert(link); assert(tclass); assert(req); assert_se(htb = TCLASS_TO_HTB(tclass)); struct tc_htb_opt opt = { .prio = htb->priority, .quantum = htb->quantum, .rate.rate = (htb->rate >= (1ULL << 32)) ? ~0U : htb->rate, .ceil.rate = (htb->ceil_rate >= (1ULL << 32)) ? ~0U : htb->ceil_rate, .rate.overhead = htb->overhead, .ceil.overhead = htb->overhead, }; r = tc_transmit_time(htb->rate, htb->buffer, &opt.buffer); if (r < 0) return log_link_debug_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_debug_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_debug_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_debug_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 r; r = sd_netlink_message_append_data(req, TCA_HTB_PARMS, &opt, sizeof(opt)); if (r < 0) return r; if (htb->rate >= (1ULL << 32)) { r = sd_netlink_message_append_u64(req, TCA_HTB_RATE64, htb->rate); if (r < 0) return r; } if (htb->ceil_rate >= (1ULL << 32)) { r = sd_netlink_message_append_u64(req, TCA_HTB_CEIL64, htb->ceil_rate); if (r < 0) return r; } r = sd_netlink_message_append_data(req, TCA_HTB_RTAB, rtab, sizeof(rtab)); if (r < 0) return r; r = sd_netlink_message_append_data(req, TCA_HTB_CTAB, ctab, sizeof(ctab)); if (r < 0) return r; r = sd_netlink_message_close_container(req); if (r < 0) return r; 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 = ASSERT_PTR(data); uint32_t v; int r; assert(filename); assert(lvalue); assert(rvalue); 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 = ASSERT_PTR(data); uint64_t v; int r; assert(filename); assert(lvalue); assert(rvalue); 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(); 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(); 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 = ASSERT_PTR(data); uint64_t *v; int r; assert(filename); assert(lvalue); assert(rvalue); 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(); 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, };