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 "networkd-queue.h"
12 #include "parse-util.h"
15 #include "string-util.h"
19 const QDiscVTable
* const qdisc_vtable
[_QDISC_KIND_MAX
] = {
20 [QDISC_KIND_BFIFO
] = &bfifo_vtable
,
21 [QDISC_KIND_CAKE
] = &cake_vtable
,
22 [QDISC_KIND_CODEL
] = &codel_vtable
,
23 [QDISC_KIND_DRR
] = &drr_vtable
,
24 [QDISC_KIND_ETS
] = &ets_vtable
,
25 [QDISC_KIND_FQ
] = &fq_vtable
,
26 [QDISC_KIND_FQ_CODEL
] = &fq_codel_vtable
,
27 [QDISC_KIND_FQ_PIE
] = &fq_pie_vtable
,
28 [QDISC_KIND_GRED
] = &gred_vtable
,
29 [QDISC_KIND_HHF
] = &hhf_vtable
,
30 [QDISC_KIND_HTB
] = &htb_vtable
,
31 [QDISC_KIND_NETEM
] = &netem_vtable
,
32 [QDISC_KIND_PIE
] = &pie_vtable
,
33 [QDISC_KIND_QFQ
] = &qfq_vtable
,
34 [QDISC_KIND_PFIFO
] = &pfifo_vtable
,
35 [QDISC_KIND_PFIFO_FAST
] = &pfifo_fast_vtable
,
36 [QDISC_KIND_PFIFO_HEAD_DROP
] = &pfifo_head_drop_vtable
,
37 [QDISC_KIND_SFB
] = &sfb_vtable
,
38 [QDISC_KIND_SFQ
] = &sfq_vtable
,
39 [QDISC_KIND_TBF
] = &tbf_vtable
,
40 [QDISC_KIND_TEQL
] = &teql_vtable
,
43 static int qdisc_new(QDiscKind kind
, QDisc
**ret
) {
44 _cleanup_(qdisc_freep
) QDisc
*qdisc
= NULL
;
47 if (kind
== _QDISC_KIND_INVALID
) {
48 qdisc
= new(QDisc
, 1);
53 .meta
.kind
= TC_KIND_QDISC
,
58 assert(kind
>= 0 && kind
< _QDISC_KIND_MAX
);
59 qdisc
= malloc0(qdisc_vtable
[kind
]->object_size
);
63 qdisc
->meta
.kind
= TC_KIND_QDISC
,
64 qdisc
->parent
= TC_H_ROOT
;
67 if (QDISC_VTABLE(qdisc
)->init
) {
68 r
= QDISC_VTABLE(qdisc
)->init(qdisc
);
74 *ret
= TAKE_PTR(qdisc
);
79 int qdisc_new_static(QDiscKind kind
, Network
*network
, const char *filename
, unsigned section_line
, QDisc
**ret
) {
80 _cleanup_(config_section_freep
) ConfigSection
*n
= NULL
;
81 _cleanup_(qdisc_freep
) QDisc
*qdisc
= NULL
;
82 TrafficControl
*existing
;
89 assert(section_line
> 0);
91 r
= config_section_new(filename
, section_line
, &n
);
95 existing
= hashmap_get(network
->tc_by_section
, n
);
97 if (existing
->kind
!= TC_KIND_QDISC
)
100 q
= TC_TO_QDISC(existing
);
102 if (q
->kind
!= _QDISC_KIND_INVALID
&&
103 kind
!= _QDISC_KIND_INVALID
&&
107 if (q
->kind
== kind
|| kind
== _QDISC_KIND_INVALID
) {
113 r
= qdisc_new(kind
, &qdisc
);
118 qdisc
->handle
= q
->handle
;
119 qdisc
->parent
= q
->parent
;
120 qdisc
->tca_kind
= TAKE_PTR(q
->tca_kind
);
125 qdisc
->network
= network
;
126 qdisc
->section
= TAKE_PTR(n
);
127 qdisc
->source
= NETWORK_CONFIG_SOURCE_STATIC
;
129 r
= hashmap_ensure_put(&network
->tc_by_section
, &config_section_hash_ops
, qdisc
->section
, TC(qdisc
));
133 *ret
= TAKE_PTR(qdisc
);
137 QDisc
* qdisc_free(QDisc
*qdisc
) {
141 if (qdisc
->network
&& qdisc
->section
)
142 hashmap_remove(qdisc
->network
->tc_by_section
, qdisc
->section
);
144 config_section_free(qdisc
->section
);
147 set_remove(qdisc
->link
->traffic_control
, TC(qdisc
));
149 free(qdisc
->tca_kind
);
153 static const char *qdisc_get_tca_kind(const QDisc
*qdisc
) {
156 return (QDISC_VTABLE(qdisc
) && QDISC_VTABLE(qdisc
)->tca_kind
) ?
157 QDISC_VTABLE(qdisc
)->tca_kind
: qdisc
->tca_kind
;
160 void qdisc_hash_func(const QDisc
*qdisc
, struct siphash
*state
) {
164 siphash24_compress(&qdisc
->handle
, sizeof(qdisc
->handle
), state
);
165 siphash24_compress(&qdisc
->parent
, sizeof(qdisc
->parent
), state
);
166 siphash24_compress_string(qdisc_get_tca_kind(qdisc
), state
);
169 int qdisc_compare_func(const QDisc
*a
, const QDisc
*b
) {
175 r
= CMP(a
->handle
, b
->handle
);
179 r
= CMP(a
->parent
, b
->parent
);
183 return strcmp_ptr(qdisc_get_tca_kind(a
), qdisc_get_tca_kind(b
));
186 static int qdisc_get(Link
*link
, const QDisc
*in
, QDisc
**ret
) {
187 TrafficControl
*existing
;
193 r
= traffic_control_get(link
, TC(in
), &existing
);
198 *ret
= TC_TO_QDISC(existing
);
202 static int qdisc_add(Link
*link
, QDisc
*qdisc
) {
208 r
= traffic_control_add(link
, TC(qdisc
));
216 static int qdisc_dup(const QDisc
*src
, QDisc
**ret
) {
217 _cleanup_(qdisc_freep
) QDisc
*dst
= NULL
;
222 if (QDISC_VTABLE(src
))
223 dst
= memdup(src
, QDISC_VTABLE(src
)->object_size
);
225 dst
= newdup(QDisc
, src
, 1);
229 /* clear all pointers */
233 dst
->tca_kind
= NULL
;
236 dst
->tca_kind
= strdup(src
->tca_kind
);
241 *ret
= TAKE_PTR(dst
);
245 static void log_qdisc_debug(QDisc
*qdisc
, Link
*link
, const char *str
) {
246 _cleanup_free_
char *state
= NULL
;
254 (void) network_config_state_to_string_alloc(qdisc
->state
, &state
);
256 log_link_debug(link
, "%s %s QDisc (%s): handle=%"PRIx32
":%"PRIx32
", parent=%"PRIx32
":%"PRIx32
", kind=%s",
257 str
, strna(network_config_source_to_string(qdisc
->source
)), strna(state
),
258 TC_H_MAJ(qdisc
->handle
) >> 16, TC_H_MIN(qdisc
->handle
),
259 TC_H_MAJ(qdisc
->parent
) >> 16, TC_H_MIN(qdisc
->parent
),
260 strna(qdisc_get_tca_kind(qdisc
)));
263 int link_find_qdisc(Link
*link
, uint32_t handle
, uint32_t parent
, const char *kind
, QDisc
**ret
) {
268 handle
= TC_H_MAJ(handle
);
270 SET_FOREACH(tc
, link
->traffic_control
) {
273 if (tc
->kind
!= TC_KIND_QDISC
)
276 qdisc
= TC_TO_QDISC(tc
);
278 if (qdisc
->handle
!= handle
)
281 if (qdisc
->parent
!= parent
)
284 if (qdisc
->source
== NETWORK_CONFIG_SOURCE_FOREIGN
)
287 if (!qdisc_exists(qdisc
))
290 if (kind
&& !streq_ptr(kind
, qdisc_get_tca_kind(qdisc
)))
301 static int qdisc_handler(sd_netlink
*rtnl
, sd_netlink_message
*m
, Link
*link
) {
305 assert(link
->tc_messages
> 0);
308 if (IN_SET(link
->state
, LINK_STATE_FAILED
, LINK_STATE_LINGER
))
311 r
= sd_netlink_message_get_errno(m
);
312 if (r
< 0 && r
!= -EEXIST
) {
313 log_link_message_error_errno(link
, m
, r
, "Could not set QDisc");
314 link_enter_failed(link
);
318 if (link
->tc_messages
== 0) {
319 log_link_debug(link
, "Traffic control configured");
320 link
->tc_configured
= true;
321 link_check_ready(link
);
327 int qdisc_configure(Link
*link
, QDisc
*qdisc
) {
328 _cleanup_(sd_netlink_message_unrefp
) sd_netlink_message
*req
= NULL
;
332 assert(link
->manager
);
333 assert(link
->manager
->rtnl
);
334 assert(link
->ifindex
> 0);
336 log_qdisc_debug(qdisc
, link
, "Configuring");
338 r
= sd_rtnl_message_new_traffic_control(link
->manager
->rtnl
, &req
, RTM_NEWQDISC
,
339 link
->ifindex
, qdisc
->handle
, qdisc
->parent
);
341 return log_link_debug_errno(link
, r
, "Could not create RTM_NEWQDISC message: %m");
343 r
= sd_netlink_message_append_string(req
, TCA_KIND
, qdisc_get_tca_kind(qdisc
));
347 if (QDISC_VTABLE(qdisc
) && QDISC_VTABLE(qdisc
)->fill_message
) {
348 r
= QDISC_VTABLE(qdisc
)->fill_message(link
, qdisc
, req
);
353 r
= netlink_call_async(link
->manager
->rtnl
, NULL
, req
, qdisc_handler
, link_netlink_destroy_callback
, link
);
355 return log_link_debug_errno(link
, r
, "Could not send netlink message: %m");
359 qdisc_enter_configuring(qdisc
);
363 int qdisc_is_ready_to_configure(Link
*link
, QDisc
*qdisc
) {
367 if (IN_SET(qdisc
->parent
, TC_H_ROOT
, TC_H_CLSACT
)) /* TC_H_CLSACT == TC_H_INGRESS */
370 return link_find_tclass(link
, qdisc
->parent
, NULL
) >= 0;
373 int link_request_qdisc(Link
*link
, QDisc
*qdisc
) {
380 if (qdisc_get(link
, qdisc
, &existing
) < 0) {
381 _cleanup_(qdisc_freep
) QDisc
*tmp
= NULL
;
383 r
= qdisc_dup(qdisc
, &tmp
);
387 r
= qdisc_add(link
, tmp
);
389 return log_link_warning_errno(link
, r
, "Failed to store QDisc: %m");
391 existing
= TAKE_PTR(tmp
);
393 existing
->source
= qdisc
->source
;
395 log_qdisc_debug(existing
, link
, "Requesting");
396 r
= link_queue_request(link
, REQUEST_TYPE_TRAFFIC_CONTROL
, TC(existing
), false,
397 &link
->tc_messages
, NULL
, NULL
);
399 return log_link_warning_errno(link
, r
, "Failed to request QDisc: %m");
403 qdisc_enter_requesting(existing
);
407 int manager_rtnl_process_qdisc(sd_netlink
*rtnl
, sd_netlink_message
*message
, Manager
*m
) {
408 _cleanup_(qdisc_freep
) QDisc
*tmp
= NULL
;
418 if (sd_netlink_message_is_error(message
)) {
419 r
= sd_netlink_message_get_errno(message
);
421 log_message_warning_errno(message
, r
, "rtnl: failed to receive QDisc message, ignoring");
426 r
= sd_netlink_message_get_type(message
, &type
);
428 log_warning_errno(r
, "rtnl: could not get message type, ignoring: %m");
430 } else if (!IN_SET(type
, RTM_NEWQDISC
, RTM_DELQDISC
)) {
431 log_warning("rtnl: received unexpected message type %u when processing QDisc, ignoring.", type
);
435 r
= sd_rtnl_message_traffic_control_get_ifindex(message
, &ifindex
);
437 log_warning_errno(r
, "rtnl: could not get ifindex from message, ignoring: %m");
439 } else if (ifindex
<= 0) {
440 log_warning("rtnl: received QDisc message with invalid ifindex %d, ignoring.", ifindex
);
444 if (link_get_by_index(m
, ifindex
, &link
) < 0) {
446 log_warning("rtnl: received QDisc for link '%d' we don't know about, ignoring.", ifindex
);
450 r
= qdisc_new(_QDISC_KIND_INVALID
, &tmp
);
454 r
= sd_rtnl_message_traffic_control_get_handle(message
, &tmp
->handle
);
456 log_link_warning_errno(link
, r
, "rtnl: received QDisc message without handle, ignoring: %m");
460 r
= sd_rtnl_message_traffic_control_get_parent(message
, &tmp
->parent
);
462 log_link_warning_errno(link
, r
, "rtnl: received QDisc message without parent, ignoring: %m");
466 r
= sd_netlink_message_read_string_strdup(message
, TCA_KIND
, &tmp
->tca_kind
);
468 log_link_warning_errno(link
, r
, "rtnl: received QDisc message without kind, ignoring: %m");
472 (void) qdisc_get(link
, tmp
, &qdisc
);
477 qdisc_enter_configured(qdisc
);
478 log_qdisc_debug(qdisc
, link
, "Received remembered");
480 qdisc_enter_configured(tmp
);
481 log_qdisc_debug(tmp
, link
, "Received new");
483 r
= qdisc_add(link
, tmp
);
485 log_link_warning_errno(link
, r
, "Failed to remember QDisc, ignoring: %m");
489 qdisc
= TAKE_PTR(tmp
);
496 qdisc_enter_removed(qdisc
);
497 if (qdisc
->state
== 0) {
498 log_qdisc_debug(qdisc
, link
, "Forgetting");
501 log_qdisc_debug(qdisc
, link
, "Removed");
503 log_qdisc_debug(tmp
, link
, "Kernel removed unknown");
508 assert_not_reached();
514 int qdisc_section_verify(QDisc
*qdisc
, bool *has_root
, bool *has_clsact
) {
521 if (section_is_invalid(qdisc
->section
))
524 if (QDISC_VTABLE(qdisc
) && QDISC_VTABLE(qdisc
)->verify
) {
525 r
= QDISC_VTABLE(qdisc
)->verify(qdisc
);
530 if (qdisc
->parent
== TC_H_ROOT
) {
532 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL
),
533 "%s: More than one root qdisc section is defined. "
534 "Ignoring the qdisc section from line %u.",
535 qdisc
->section
->filename
, qdisc
->section
->line
);
537 } else if (qdisc
->parent
== TC_H_CLSACT
) { /* TC_H_CLSACT == TC_H_INGRESS */
539 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL
),
540 "%s: More than one clsact or ingress qdisc section is defined. "
541 "Ignoring the qdisc section from line %u.",
542 qdisc
->section
->filename
, qdisc
->section
->line
);
549 int config_parse_qdisc_parent(
551 const char *filename
,
554 unsigned section_line
,
561 _cleanup_(qdisc_free_or_set_invalidp
) QDisc
*qdisc
= NULL
;
562 Network
*network
= data
;
570 r
= qdisc_new_static(ltype
, network
, filename
, section_line
, &qdisc
);
574 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
575 "More than one kind of queueing discipline, ignoring assignment: %m");
579 if (streq(rvalue
, "root"))
580 qdisc
->parent
= TC_H_ROOT
;
581 else if (streq(rvalue
, "clsact")) {
582 qdisc
->parent
= TC_H_CLSACT
;
583 qdisc
->handle
= TC_H_MAKE(TC_H_CLSACT
, 0);
584 } else if (streq(rvalue
, "ingress")) {
585 qdisc
->parent
= TC_H_INGRESS
;
586 qdisc
->handle
= TC_H_MAKE(TC_H_INGRESS
, 0);
588 r
= parse_handle(rvalue
, &qdisc
->parent
);
590 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
591 "Failed to parse 'Parent=', ignoring assignment: %s",
597 if (STR_IN_SET(rvalue
, "clsact", "ingress")) {
598 r
= free_and_strdup(&qdisc
->tca_kind
, rvalue
);
602 qdisc
->tca_kind
= mfree(qdisc
->tca_kind
);
609 int config_parse_qdisc_handle(
611 const char *filename
,
614 unsigned section_line
,
621 _cleanup_(qdisc_free_or_set_invalidp
) QDisc
*qdisc
= NULL
;
622 Network
*network
= data
;
631 r
= qdisc_new_static(ltype
, network
, filename
, section_line
, &qdisc
);
635 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
636 "More than one kind of queueing discipline, ignoring assignment: %m");
640 if (isempty(rvalue
)) {
641 qdisc
->handle
= TC_H_UNSPEC
;
646 r
= safe_atou16_full(rvalue
, 16, &n
);
648 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
649 "Failed to parse 'Handle=', ignoring assignment: %s",
654 qdisc
->handle
= (uint32_t) n
<< 16;