1 /* SPDX-License-Identifier: LGPL-2.1-or-later
2 * Copyright © 2019 VMware, Inc. */
4 #include <linux/pkt_sched.h>
6 #include "alloc-util.h"
7 #include "conf-parser.h"
8 #include "in-addr-util.h"
9 #include "netlink-util.h"
10 #include "networkd-manager.h"
11 #include "parse-util.h"
14 #include "string-util.h"
18 const QDiscVTable
* const qdisc_vtable
[_QDISC_KIND_MAX
] = {
19 [QDISC_KIND_BFIFO
] = &bfifo_vtable
,
20 [QDISC_KIND_CAKE
] = &cake_vtable
,
21 [QDISC_KIND_CODEL
] = &codel_vtable
,
22 [QDISC_KIND_DRR
] = &drr_vtable
,
23 [QDISC_KIND_ETS
] = &ets_vtable
,
24 [QDISC_KIND_FQ
] = &fq_vtable
,
25 [QDISC_KIND_FQ_CODEL
] = &fq_codel_vtable
,
26 [QDISC_KIND_FQ_PIE
] = &fq_pie_vtable
,
27 [QDISC_KIND_GRED
] = &gred_vtable
,
28 [QDISC_KIND_HHF
] = &hhf_vtable
,
29 [QDISC_KIND_HTB
] = &htb_vtable
,
30 [QDISC_KIND_NETEM
] = &netem_vtable
,
31 [QDISC_KIND_PIE
] = &pie_vtable
,
32 [QDISC_KIND_QFQ
] = &qfq_vtable
,
33 [QDISC_KIND_PFIFO
] = &pfifo_vtable
,
34 [QDISC_KIND_PFIFO_FAST
] = &pfifo_fast_vtable
,
35 [QDISC_KIND_PFIFO_HEAD_DROP
] = &pfifo_head_drop_vtable
,
36 [QDISC_KIND_SFB
] = &sfb_vtable
,
37 [QDISC_KIND_SFQ
] = &sfq_vtable
,
38 [QDISC_KIND_TBF
] = &tbf_vtable
,
39 [QDISC_KIND_TEQL
] = &teql_vtable
,
42 static int qdisc_new(QDiscKind kind
, QDisc
**ret
) {
43 _cleanup_(qdisc_freep
) QDisc
*qdisc
= NULL
;
46 if (kind
== _QDISC_KIND_INVALID
) {
47 qdisc
= new(QDisc
, 1);
52 .meta
.kind
= TC_KIND_QDISC
,
57 assert(kind
>= 0 && kind
< _QDISC_KIND_MAX
);
58 qdisc
= malloc0(qdisc_vtable
[kind
]->object_size
);
62 qdisc
->meta
.kind
= TC_KIND_QDISC
,
63 qdisc
->parent
= TC_H_ROOT
;
66 if (QDISC_VTABLE(qdisc
)->init
) {
67 r
= QDISC_VTABLE(qdisc
)->init(qdisc
);
73 *ret
= TAKE_PTR(qdisc
);
78 int qdisc_new_static(QDiscKind kind
, Network
*network
, const char *filename
, unsigned section_line
, QDisc
**ret
) {
79 _cleanup_(config_section_freep
) ConfigSection
*n
= NULL
;
80 _cleanup_(qdisc_freep
) QDisc
*qdisc
= NULL
;
81 TrafficControl
*existing
;
88 assert(section_line
> 0);
90 r
= config_section_new(filename
, section_line
, &n
);
94 existing
= ordered_hashmap_get(network
->tc_by_section
, n
);
96 if (existing
->kind
!= TC_KIND_QDISC
)
99 q
= TC_TO_QDISC(existing
);
101 if (q
->kind
!= _QDISC_KIND_INVALID
&&
102 kind
!= _QDISC_KIND_INVALID
&&
106 if (q
->kind
== kind
|| kind
== _QDISC_KIND_INVALID
) {
112 r
= qdisc_new(kind
, &qdisc
);
117 qdisc
->handle
= q
->handle
;
118 qdisc
->parent
= q
->parent
;
119 qdisc
->tca_kind
= TAKE_PTR(q
->tca_kind
);
124 qdisc
->network
= network
;
125 qdisc
->section
= TAKE_PTR(n
);
127 r
= ordered_hashmap_ensure_put(&network
->tc_by_section
, &config_section_hash_ops
, qdisc
->section
, TC(qdisc
));
131 *ret
= TAKE_PTR(qdisc
);
135 QDisc
* qdisc_free(QDisc
*qdisc
) {
139 if (qdisc
->network
&& qdisc
->section
)
140 ordered_hashmap_remove(qdisc
->network
->tc_by_section
, qdisc
->section
);
142 config_section_free(qdisc
->section
);
145 set_remove(qdisc
->link
->traffic_control
, TC(qdisc
));
147 free(qdisc
->tca_kind
);
151 static const char *qdisc_get_tca_kind(const QDisc
*qdisc
) {
154 return (QDISC_VTABLE(qdisc
) && QDISC_VTABLE(qdisc
)->tca_kind
) ?
155 QDISC_VTABLE(qdisc
)->tca_kind
: qdisc
->tca_kind
;
158 void qdisc_hash_func(const QDisc
*qdisc
, struct siphash
*state
) {
162 siphash24_compress(&qdisc
->handle
, sizeof(qdisc
->handle
), state
);
163 siphash24_compress(&qdisc
->parent
, sizeof(qdisc
->parent
), state
);
164 siphash24_compress_string(qdisc_get_tca_kind(qdisc
), state
);
167 int qdisc_compare_func(const QDisc
*a
, const QDisc
*b
) {
173 r
= CMP(a
->handle
, b
->handle
);
177 r
= CMP(a
->parent
, b
->parent
);
181 return strcmp_ptr(qdisc_get_tca_kind(a
), qdisc_get_tca_kind(b
));
184 static int qdisc_get(Link
*link
, const QDisc
*in
, QDisc
**ret
) {
185 TrafficControl
*existing
;
191 r
= traffic_control_get(link
, TC(in
), &existing
);
196 *ret
= TC_TO_QDISC(existing
);
200 static int qdisc_add(Link
*link
, QDisc
*qdisc
) {
206 r
= traffic_control_add(link
, TC(qdisc
));
214 static void log_qdisc_debug(QDisc
*qdisc
, Link
*link
, const char *str
) {
215 _cleanup_free_
char *state
= NULL
;
223 (void) network_config_state_to_string_alloc(qdisc
->state
, &state
);
225 log_link_debug(link
, "%s %s QDisc (%s): handle=%"PRIx32
":%"PRIx32
", parent=%"PRIx32
":%"PRIx32
", kind=%s",
226 str
, strna(network_config_source_to_string(qdisc
->source
)), strna(state
),
227 TC_H_MAJ(qdisc
->handle
) >> 16, TC_H_MIN(qdisc
->handle
),
228 TC_H_MAJ(qdisc
->parent
) >> 16, TC_H_MIN(qdisc
->parent
),
229 strna(qdisc_get_tca_kind(qdisc
)));
232 static int qdisc_handler(sd_netlink
*rtnl
, sd_netlink_message
*m
, Link
*link
) {
236 assert(link
->tc_messages
> 0);
239 if (IN_SET(link
->state
, LINK_STATE_FAILED
, LINK_STATE_LINGER
))
242 r
= sd_netlink_message_get_errno(m
);
243 if (r
< 0 && r
!= -EEXIST
) {
244 log_link_message_error_errno(link
, m
, r
, "Could not set QDisc");
245 link_enter_failed(link
);
249 if (link
->tc_messages
== 0) {
250 log_link_debug(link
, "Traffic control configured");
251 link
->tc_configured
= true;
252 link_check_ready(link
);
258 int qdisc_configure(Link
*link
, QDisc
*qdisc
) {
259 _cleanup_(sd_netlink_message_unrefp
) sd_netlink_message
*req
= NULL
;
263 assert(link
->manager
);
264 assert(link
->manager
->rtnl
);
265 assert(link
->ifindex
> 0);
267 r
= sd_rtnl_message_new_traffic_control(link
->manager
->rtnl
, &req
, RTM_NEWQDISC
,
268 link
->ifindex
, qdisc
->handle
, qdisc
->parent
);
270 return log_link_debug_errno(link
, r
, "Could not create RTM_NEWQDISC message: %m");
272 if (QDISC_VTABLE(qdisc
)) {
273 if (QDISC_VTABLE(qdisc
)->fill_tca_kind
) {
274 r
= QDISC_VTABLE(qdisc
)->fill_tca_kind(link
, qdisc
, req
);
278 r
= sd_netlink_message_append_string(req
, TCA_KIND
, QDISC_VTABLE(qdisc
)->tca_kind
);
283 if (QDISC_VTABLE(qdisc
)->fill_message
) {
284 r
= QDISC_VTABLE(qdisc
)->fill_message(link
, qdisc
, req
);
289 r
= sd_netlink_message_append_string(req
, TCA_KIND
, qdisc
->tca_kind
);
294 r
= netlink_call_async(link
->manager
->rtnl
, NULL
, req
, qdisc_handler
, link_netlink_destroy_callback
, link
);
296 return log_link_debug_errno(link
, r
, "Could not send netlink message: %m");
304 int manager_rtnl_process_qdisc(sd_netlink
*rtnl
, sd_netlink_message
*message
, Manager
*m
) {
305 _cleanup_(qdisc_freep
) QDisc
*tmp
= NULL
;
315 if (sd_netlink_message_is_error(message
)) {
316 r
= sd_netlink_message_get_errno(message
);
318 log_message_warning_errno(message
, r
, "rtnl: failed to receive QDisc message, ignoring");
323 r
= sd_netlink_message_get_type(message
, &type
);
325 log_warning_errno(r
, "rtnl: could not get message type, ignoring: %m");
327 } else if (!IN_SET(type
, RTM_NEWQDISC
, RTM_DELQDISC
)) {
328 log_warning("rtnl: received unexpected message type %u when processing QDisc, ignoring.", type
);
332 r
= sd_rtnl_message_traffic_control_get_ifindex(message
, &ifindex
);
334 log_warning_errno(r
, "rtnl: could not get ifindex from message, ignoring: %m");
336 } else if (ifindex
<= 0) {
337 log_warning("rtnl: received QDisc message with invalid ifindex %d, ignoring.", ifindex
);
341 if (link_get_by_index(m
, ifindex
, &link
) < 0) {
343 log_warning("rtnl: received QDisc for link '%d' we don't know about, ignoring.", ifindex
);
347 r
= qdisc_new(_QDISC_KIND_INVALID
, &tmp
);
351 r
= sd_rtnl_message_traffic_control_get_handle(message
, &tmp
->handle
);
353 log_link_warning_errno(link
, r
, "rtnl: received QDisc message without handle, ignoring: %m");
357 r
= sd_rtnl_message_traffic_control_get_parent(message
, &tmp
->parent
);
359 log_link_warning_errno(link
, r
, "rtnl: received QDisc message without parent, ignoring: %m");
363 r
= sd_netlink_message_read_string_strdup(message
, TCA_KIND
, &tmp
->tca_kind
);
365 log_link_warning_errno(link
, r
, "rtnl: received QDisc message without kind, ignoring: %m");
369 (void) qdisc_get(link
, tmp
, &qdisc
);
374 qdisc_enter_configured(qdisc
);
375 log_qdisc_debug(qdisc
, link
, "Received remembered");
377 qdisc_enter_configured(tmp
);
378 log_qdisc_debug(tmp
, link
, "Received new");
380 r
= qdisc_add(link
, tmp
);
382 log_link_warning_errno(link
, r
, "Failed to remember QDisc, ignoring: %m");
386 qdisc
= TAKE_PTR(tmp
);
393 qdisc_enter_removed(qdisc
);
394 if (qdisc
->state
== 0) {
395 log_qdisc_debug(qdisc
, link
, "Forgetting");
398 log_qdisc_debug(qdisc
, link
, "Removed");
400 log_qdisc_debug(tmp
, link
, "Kernel removed unknown");
405 assert_not_reached();
411 int qdisc_section_verify(QDisc
*qdisc
, bool *has_root
, bool *has_clsact
) {
418 if (section_is_invalid(qdisc
->section
))
421 if (QDISC_VTABLE(qdisc
) && QDISC_VTABLE(qdisc
)->verify
) {
422 r
= QDISC_VTABLE(qdisc
)->verify(qdisc
);
427 if (qdisc
->parent
== TC_H_ROOT
) {
429 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL
),
430 "%s: More than one root qdisc section is defined. "
431 "Ignoring the qdisc section from line %u.",
432 qdisc
->section
->filename
, qdisc
->section
->line
);
434 } else if (qdisc
->parent
== TC_H_CLSACT
) { /* TC_H_CLSACT == TC_H_INGRESS */
436 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL
),
437 "%s: More than one clsact or ingress qdisc section is defined. "
438 "Ignoring the qdisc section from line %u.",
439 qdisc
->section
->filename
, qdisc
->section
->line
);
446 int config_parse_qdisc_parent(
448 const char *filename
,
451 unsigned section_line
,
458 _cleanup_(qdisc_free_or_set_invalidp
) QDisc
*qdisc
= NULL
;
459 Network
*network
= data
;
467 r
= qdisc_new_static(ltype
, network
, filename
, section_line
, &qdisc
);
471 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
472 "More than one kind of queueing discipline, ignoring assignment: %m");
476 if (streq(rvalue
, "root"))
477 qdisc
->parent
= TC_H_ROOT
;
478 else if (streq(rvalue
, "clsact")) {
479 qdisc
->parent
= TC_H_CLSACT
;
480 qdisc
->handle
= TC_H_MAKE(TC_H_CLSACT
, 0);
481 } else if (streq(rvalue
, "ingress")) {
482 qdisc
->parent
= TC_H_INGRESS
;
483 qdisc
->handle
= TC_H_MAKE(TC_H_INGRESS
, 0);
485 r
= parse_handle(rvalue
, &qdisc
->parent
);
487 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
488 "Failed to parse 'Parent=', ignoring assignment: %s",
494 if (STR_IN_SET(rvalue
, "clsact", "ingress")) {
495 r
= free_and_strdup(&qdisc
->tca_kind
, rvalue
);
499 qdisc
->tca_kind
= mfree(qdisc
->tca_kind
);
506 int config_parse_qdisc_handle(
508 const char *filename
,
511 unsigned section_line
,
518 _cleanup_(qdisc_free_or_set_invalidp
) QDisc
*qdisc
= NULL
;
519 Network
*network
= data
;
528 r
= qdisc_new_static(ltype
, network
, filename
, section_line
, &qdisc
);
532 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
533 "More than one kind of queueing discipline, ignoring assignment: %m");
537 if (isempty(rvalue
)) {
538 qdisc
->handle
= TC_H_UNSPEC
;
543 r
= safe_atou16_full(rvalue
, 16, &n
);
545 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
546 "Failed to parse 'Handle=', ignoring assignment: %s",
551 qdisc
->handle
= (uint32_t) n
<< 16;