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-link.h"
11 #include "networkd-manager.h"
12 #include "networkd-network.h"
13 #include "networkd-queue.h"
14 #include "parse-util.h"
17 #include "string-util.h"
21 const QDiscVTable
* const qdisc_vtable
[_QDISC_KIND_MAX
] = {
22 [QDISC_KIND_BFIFO
] = &bfifo_vtable
,
23 [QDISC_KIND_CAKE
] = &cake_vtable
,
24 [QDISC_KIND_CODEL
] = &codel_vtable
,
25 [QDISC_KIND_DRR
] = &drr_vtable
,
26 [QDISC_KIND_ETS
] = &ets_vtable
,
27 [QDISC_KIND_FQ
] = &fq_vtable
,
28 [QDISC_KIND_FQ_CODEL
] = &fq_codel_vtable
,
29 [QDISC_KIND_FQ_PIE
] = &fq_pie_vtable
,
30 [QDISC_KIND_GRED
] = &gred_vtable
,
31 [QDISC_KIND_HHF
] = &hhf_vtable
,
32 [QDISC_KIND_HTB
] = &htb_vtable
,
33 [QDISC_KIND_NETEM
] = &netem_vtable
,
34 [QDISC_KIND_PIE
] = &pie_vtable
,
35 [QDISC_KIND_QFQ
] = &qfq_vtable
,
36 [QDISC_KIND_PFIFO
] = &pfifo_vtable
,
37 [QDISC_KIND_PFIFO_FAST
] = &pfifo_fast_vtable
,
38 [QDISC_KIND_PFIFO_HEAD_DROP
] = &pfifo_head_drop_vtable
,
39 [QDISC_KIND_SFB
] = &sfb_vtable
,
40 [QDISC_KIND_SFQ
] = &sfq_vtable
,
41 [QDISC_KIND_TBF
] = &tbf_vtable
,
42 [QDISC_KIND_TEQL
] = &teql_vtable
,
45 static int qdisc_new(QDiscKind kind
, QDisc
**ret
) {
46 _cleanup_(qdisc_freep
) QDisc
*qdisc
= NULL
;
49 if (kind
== _QDISC_KIND_INVALID
) {
50 qdisc
= new(QDisc
, 1);
59 assert(kind
>= 0 && kind
< _QDISC_KIND_MAX
);
60 qdisc
= malloc0(qdisc_vtable
[kind
]->object_size
);
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
;
88 assert(section_line
> 0);
90 r
= config_section_new(filename
, section_line
, &n
);
94 existing
= hashmap_get(network
->qdiscs_by_section
, n
);
96 if (existing
->kind
!= _QDISC_KIND_INVALID
&&
97 kind
!= _QDISC_KIND_INVALID
&&
98 existing
->kind
!= kind
)
101 if (existing
->kind
== kind
|| kind
== _QDISC_KIND_INVALID
) {
107 r
= qdisc_new(kind
, &qdisc
);
112 qdisc
->handle
= existing
->handle
;
113 qdisc
->parent
= existing
->parent
;
114 qdisc
->tca_kind
= TAKE_PTR(existing
->tca_kind
);
116 qdisc_free(existing
);
119 qdisc
->network
= network
;
120 qdisc
->section
= TAKE_PTR(n
);
121 qdisc
->source
= NETWORK_CONFIG_SOURCE_STATIC
;
123 r
= hashmap_ensure_put(&network
->qdiscs_by_section
, &config_section_hash_ops
, qdisc
->section
, qdisc
);
127 *ret
= TAKE_PTR(qdisc
);
131 QDisc
* qdisc_free(QDisc
*qdisc
) {
135 if (qdisc
->network
&& qdisc
->section
)
136 hashmap_remove(qdisc
->network
->qdiscs_by_section
, qdisc
->section
);
138 config_section_free(qdisc
->section
);
141 set_remove(qdisc
->link
->qdiscs
, qdisc
);
143 free(qdisc
->tca_kind
);
147 static const char *qdisc_get_tca_kind(const QDisc
*qdisc
) {
150 return (QDISC_VTABLE(qdisc
) && QDISC_VTABLE(qdisc
)->tca_kind
) ?
151 QDISC_VTABLE(qdisc
)->tca_kind
: qdisc
->tca_kind
;
154 void qdisc_hash_func(const QDisc
*qdisc
, struct siphash
*state
) {
158 siphash24_compress(&qdisc
->handle
, sizeof(qdisc
->handle
), state
);
159 siphash24_compress(&qdisc
->parent
, sizeof(qdisc
->parent
), state
);
160 siphash24_compress_string(qdisc_get_tca_kind(qdisc
), state
);
163 int qdisc_compare_func(const QDisc
*a
, const QDisc
*b
) {
169 r
= CMP(a
->handle
, b
->handle
);
173 r
= CMP(a
->parent
, b
->parent
);
177 return strcmp_ptr(qdisc_get_tca_kind(a
), qdisc_get_tca_kind(b
));
180 DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
187 static int qdisc_get(Link
*link
, const QDisc
*in
, QDisc
**ret
) {
193 existing
= set_get(link
->qdiscs
, in
);
202 static int qdisc_add(Link
*link
, QDisc
*qdisc
) {
208 r
= set_ensure_put(&link
->qdiscs
, &qdisc_hash_ops
, qdisc
);
218 static int qdisc_dup(const QDisc
*src
, QDisc
**ret
) {
219 _cleanup_(qdisc_freep
) QDisc
*dst
= NULL
;
224 if (QDISC_VTABLE(src
))
225 dst
= memdup(src
, QDISC_VTABLE(src
)->object_size
);
227 dst
= newdup(QDisc
, src
, 1);
231 /* clear all pointers */
235 dst
->tca_kind
= NULL
;
238 dst
->tca_kind
= strdup(src
->tca_kind
);
243 *ret
= TAKE_PTR(dst
);
247 static void log_qdisc_debug(QDisc
*qdisc
, Link
*link
, const char *str
) {
248 _cleanup_free_
char *state
= NULL
;
256 (void) network_config_state_to_string_alloc(qdisc
->state
, &state
);
258 log_link_debug(link
, "%s %s QDisc (%s): handle=%"PRIx32
":%"PRIx32
", parent=%"PRIx32
":%"PRIx32
", kind=%s",
259 str
, strna(network_config_source_to_string(qdisc
->source
)), strna(state
),
260 TC_H_MAJ(qdisc
->handle
) >> 16, TC_H_MIN(qdisc
->handle
),
261 TC_H_MAJ(qdisc
->parent
) >> 16, TC_H_MIN(qdisc
->parent
),
262 strna(qdisc_get_tca_kind(qdisc
)));
265 int link_find_qdisc(Link
*link
, uint32_t handle
, uint32_t parent
, const char *kind
, QDisc
**ret
) {
270 handle
= TC_H_MAJ(handle
);
272 SET_FOREACH(qdisc
, link
->qdiscs
) {
273 if (qdisc
->handle
!= handle
)
276 if (qdisc
->parent
!= parent
)
279 if (qdisc
->source
== NETWORK_CONFIG_SOURCE_FOREIGN
)
282 if (!qdisc_exists(qdisc
))
285 if (kind
&& !streq_ptr(kind
, qdisc_get_tca_kind(qdisc
)))
296 static int qdisc_handler(sd_netlink
*rtnl
, sd_netlink_message
*m
, Link
*link
) {
300 assert(link
->tc_messages
> 0);
303 if (IN_SET(link
->state
, LINK_STATE_FAILED
, LINK_STATE_LINGER
))
306 r
= sd_netlink_message_get_errno(m
);
307 if (r
< 0 && r
!= -EEXIST
) {
308 log_link_message_error_errno(link
, m
, r
, "Could not set QDisc");
309 link_enter_failed(link
);
313 if (link
->tc_messages
== 0) {
314 log_link_debug(link
, "Traffic control configured");
315 link
->tc_configured
= true;
316 link_check_ready(link
);
322 static int qdisc_configure(QDisc
*qdisc
, Link
*link
) {
323 _cleanup_(sd_netlink_message_unrefp
) sd_netlink_message
*req
= NULL
;
328 assert(link
->manager
);
329 assert(link
->manager
->rtnl
);
330 assert(link
->ifindex
> 0);
332 log_qdisc_debug(qdisc
, link
, "Configuring");
334 r
= sd_rtnl_message_new_traffic_control(link
->manager
->rtnl
, &req
, RTM_NEWQDISC
,
335 link
->ifindex
, qdisc
->handle
, qdisc
->parent
);
337 return log_link_debug_errno(link
, r
, "Could not create RTM_NEWQDISC message: %m");
339 r
= sd_netlink_message_append_string(req
, TCA_KIND
, qdisc_get_tca_kind(qdisc
));
343 if (QDISC_VTABLE(qdisc
) && QDISC_VTABLE(qdisc
)->fill_message
) {
344 r
= QDISC_VTABLE(qdisc
)->fill_message(link
, qdisc
, req
);
349 r
= netlink_call_async(link
->manager
->rtnl
, NULL
, req
, qdisc_handler
, link_netlink_destroy_callback
, link
);
351 return log_link_debug_errno(link
, r
, "Could not send netlink message: %m");
357 static bool qdisc_is_ready_to_configure(QDisc
*qdisc
, Link
*link
) {
361 if (!IN_SET(link
->state
, LINK_STATE_CONFIGURING
, LINK_STATE_CONFIGURED
))
364 if (IN_SET(qdisc
->parent
, TC_H_ROOT
, TC_H_CLSACT
)) /* TC_H_CLSACT == TC_H_INGRESS */
367 return link_find_tclass(link
, qdisc
->parent
, NULL
) >= 0;
370 int request_process_qdisc(Request
*req
) {
376 assert_se(link
= req
->link
);
377 assert_se(qdisc
= req
->qdisc
);
379 if (!qdisc_is_ready_to_configure(qdisc
, link
))
382 r
= qdisc_configure(qdisc
, link
);
384 return log_link_warning_errno(link
, r
, "Failed to configure QDisc: %m");
386 qdisc_enter_configuring(qdisc
);
390 int link_request_qdisc(Link
*link
, QDisc
*qdisc
) {
397 if (qdisc_get(link
, qdisc
, &existing
) < 0) {
398 _cleanup_(qdisc_freep
) QDisc
*tmp
= NULL
;
400 r
= qdisc_dup(qdisc
, &tmp
);
404 r
= qdisc_add(link
, tmp
);
406 return log_link_warning_errno(link
, r
, "Failed to store QDisc: %m");
408 existing
= TAKE_PTR(tmp
);
410 existing
->source
= qdisc
->source
;
412 log_qdisc_debug(existing
, link
, "Requesting");
413 r
= link_queue_request(link
, REQUEST_TYPE_TC_QDISC
, existing
, false,
414 &link
->tc_messages
, NULL
, NULL
);
416 return log_link_warning_errno(link
, r
, "Failed to request QDisc: %m");
420 qdisc_enter_requesting(existing
);
424 int manager_rtnl_process_qdisc(sd_netlink
*rtnl
, sd_netlink_message
*message
, Manager
*m
) {
425 _cleanup_(qdisc_freep
) QDisc
*tmp
= NULL
;
435 if (sd_netlink_message_is_error(message
)) {
436 r
= sd_netlink_message_get_errno(message
);
438 log_message_warning_errno(message
, r
, "rtnl: failed to receive QDisc message, ignoring");
443 r
= sd_netlink_message_get_type(message
, &type
);
445 log_warning_errno(r
, "rtnl: could not get message type, ignoring: %m");
447 } else if (!IN_SET(type
, RTM_NEWQDISC
, RTM_DELQDISC
)) {
448 log_warning("rtnl: received unexpected message type %u when processing QDisc, ignoring.", type
);
452 r
= sd_rtnl_message_traffic_control_get_ifindex(message
, &ifindex
);
454 log_warning_errno(r
, "rtnl: could not get ifindex from message, ignoring: %m");
456 } else if (ifindex
<= 0) {
457 log_warning("rtnl: received QDisc message with invalid ifindex %d, ignoring.", ifindex
);
461 if (link_get_by_index(m
, ifindex
, &link
) < 0) {
463 log_warning("rtnl: received QDisc for link '%d' we don't know about, ignoring.", ifindex
);
467 r
= qdisc_new(_QDISC_KIND_INVALID
, &tmp
);
471 r
= sd_rtnl_message_traffic_control_get_handle(message
, &tmp
->handle
);
473 log_link_warning_errno(link
, r
, "rtnl: received QDisc message without handle, ignoring: %m");
477 r
= sd_rtnl_message_traffic_control_get_parent(message
, &tmp
->parent
);
479 log_link_warning_errno(link
, r
, "rtnl: received QDisc message without parent, ignoring: %m");
483 r
= sd_netlink_message_read_string_strdup(message
, TCA_KIND
, &tmp
->tca_kind
);
485 log_link_warning_errno(link
, r
, "rtnl: received QDisc message without kind, ignoring: %m");
489 (void) qdisc_get(link
, tmp
, &qdisc
);
494 qdisc_enter_configured(qdisc
);
495 log_qdisc_debug(qdisc
, link
, "Received remembered");
497 qdisc_enter_configured(tmp
);
498 log_qdisc_debug(tmp
, link
, "Received new");
500 r
= qdisc_add(link
, tmp
);
502 log_link_warning_errno(link
, r
, "Failed to remember QDisc, ignoring: %m");
506 qdisc
= TAKE_PTR(tmp
);
513 qdisc_enter_removed(qdisc
);
514 if (qdisc
->state
== 0) {
515 log_qdisc_debug(qdisc
, link
, "Forgetting");
518 log_qdisc_debug(qdisc
, link
, "Removed");
520 log_qdisc_debug(tmp
, link
, "Kernel removed unknown");
525 assert_not_reached();
531 static int qdisc_section_verify(QDisc
*qdisc
, bool *has_root
, bool *has_clsact
) {
538 if (section_is_invalid(qdisc
->section
))
541 if (QDISC_VTABLE(qdisc
) && QDISC_VTABLE(qdisc
)->verify
) {
542 r
= QDISC_VTABLE(qdisc
)->verify(qdisc
);
547 if (qdisc
->parent
== TC_H_ROOT
) {
549 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL
),
550 "%s: More than one root qdisc section is defined. "
551 "Ignoring the qdisc section from line %u.",
552 qdisc
->section
->filename
, qdisc
->section
->line
);
554 } else if (qdisc
->parent
== TC_H_CLSACT
) { /* TC_H_CLSACT == TC_H_INGRESS */
556 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL
),
557 "%s: More than one clsact or ingress qdisc section is defined. "
558 "Ignoring the qdisc section from line %u.",
559 qdisc
->section
->filename
, qdisc
->section
->line
);
566 void network_drop_invalid_qdisc(Network
*network
) {
567 bool has_root
= false, has_clsact
= false;
572 HASHMAP_FOREACH(qdisc
, network
->qdiscs_by_section
)
573 if (qdisc_section_verify(qdisc
, &has_root
, &has_clsact
) < 0)
577 int config_parse_qdisc_parent(
579 const char *filename
,
582 unsigned section_line
,
589 _cleanup_(qdisc_free_or_set_invalidp
) QDisc
*qdisc
= NULL
;
590 Network
*network
= data
;
598 r
= qdisc_new_static(ltype
, network
, filename
, section_line
, &qdisc
);
602 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
603 "More than one kind of queueing discipline, ignoring assignment: %m");
607 if (streq(rvalue
, "root"))
608 qdisc
->parent
= TC_H_ROOT
;
609 else if (streq(rvalue
, "clsact")) {
610 qdisc
->parent
= TC_H_CLSACT
;
611 qdisc
->handle
= TC_H_MAKE(TC_H_CLSACT
, 0);
612 } else if (streq(rvalue
, "ingress")) {
613 qdisc
->parent
= TC_H_INGRESS
;
614 qdisc
->handle
= TC_H_MAKE(TC_H_INGRESS
, 0);
616 r
= parse_handle(rvalue
, &qdisc
->parent
);
618 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
619 "Failed to parse 'Parent=', ignoring assignment: %s",
625 if (STR_IN_SET(rvalue
, "clsact", "ingress")) {
626 r
= free_and_strdup(&qdisc
->tca_kind
, rvalue
);
630 qdisc
->tca_kind
= mfree(qdisc
->tca_kind
);
637 int config_parse_qdisc_handle(
639 const char *filename
,
642 unsigned section_line
,
649 _cleanup_(qdisc_free_or_set_invalidp
) QDisc
*qdisc
= NULL
;
650 Network
*network
= data
;
659 r
= qdisc_new_static(ltype
, network
, filename
, section_line
, &qdisc
);
663 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
664 "More than one kind of queueing discipline, ignoring assignment: %m");
668 if (isempty(rvalue
)) {
669 qdisc
->handle
= TC_H_UNSPEC
;
674 r
= safe_atou16_full(rvalue
, 16, &n
);
676 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
677 "Failed to parse 'Handle=', ignoring assignment: %s",
682 qdisc
->handle
= (uint32_t) n
<< 16;