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 static 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 static 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
, Request
*req
, Link
*link
, QDisc
*qdisc
) {
302 r
= sd_netlink_message_get_errno(m
);
303 if (r
< 0 && r
!= -EEXIST
) {
304 log_link_message_error_errno(link
, m
, r
, "Could not set QDisc");
305 link_enter_failed(link
);
309 if (link
->tc_messages
== 0) {
310 log_link_debug(link
, "Traffic control configured");
311 link
->tc_configured
= true;
312 link_check_ready(link
);
318 static int qdisc_configure(QDisc
*qdisc
, Link
*link
, Request
*req
) {
319 _cleanup_(sd_netlink_message_unrefp
) sd_netlink_message
*m
= NULL
;
324 assert(link
->manager
);
325 assert(link
->manager
->rtnl
);
326 assert(link
->ifindex
> 0);
329 log_qdisc_debug(qdisc
, link
, "Configuring");
331 r
= sd_rtnl_message_new_traffic_control(link
->manager
->rtnl
, &m
, RTM_NEWQDISC
,
332 link
->ifindex
, qdisc
->handle
, qdisc
->parent
);
336 r
= sd_netlink_message_append_string(m
, TCA_KIND
, qdisc_get_tca_kind(qdisc
));
340 if (QDISC_VTABLE(qdisc
) && QDISC_VTABLE(qdisc
)->fill_message
) {
341 r
= QDISC_VTABLE(qdisc
)->fill_message(link
, qdisc
, m
);
346 return request_call_netlink_async(link
->manager
->rtnl
, m
, req
);
349 static bool qdisc_is_ready_to_configure(QDisc
*qdisc
, Link
*link
) {
353 if (!IN_SET(link
->state
, LINK_STATE_CONFIGURING
, LINK_STATE_CONFIGURED
))
356 /* TC_H_CLSACT == TC_H_INGRESS */
357 if (!IN_SET(qdisc
->parent
, TC_H_ROOT
, TC_H_CLSACT
) &&
358 link_find_tclass(link
, qdisc
->parent
, NULL
) < 0)
361 if (QDISC_VTABLE(qdisc
) &&
362 QDISC_VTABLE(qdisc
)->is_ready
&&
363 QDISC_VTABLE(qdisc
)->is_ready(qdisc
, link
) <= 0)
369 static int qdisc_process_request(Request
*req
, Link
*link
, QDisc
*qdisc
) {
376 if (!qdisc_is_ready_to_configure(qdisc
, link
))
379 r
= qdisc_configure(qdisc
, link
, req
);
381 return log_link_warning_errno(link
, r
, "Failed to configure QDisc: %m");
383 qdisc_enter_configuring(qdisc
);
387 int link_request_qdisc(Link
*link
, QDisc
*qdisc
) {
394 if (qdisc_get(link
, qdisc
, &existing
) < 0) {
395 _cleanup_(qdisc_freep
) QDisc
*tmp
= NULL
;
397 r
= qdisc_dup(qdisc
, &tmp
);
401 r
= qdisc_add(link
, tmp
);
403 return log_link_warning_errno(link
, r
, "Failed to store QDisc: %m");
405 existing
= TAKE_PTR(tmp
);
407 existing
->source
= qdisc
->source
;
409 log_qdisc_debug(existing
, link
, "Requesting");
410 r
= link_queue_request_safe(link
, REQUEST_TYPE_TC_QDISC
,
414 qdisc_process_request
,
419 return log_link_warning_errno(link
, r
, "Failed to request QDisc: %m");
423 qdisc_enter_requesting(existing
);
427 int manager_rtnl_process_qdisc(sd_netlink
*rtnl
, sd_netlink_message
*message
, Manager
*m
) {
428 _cleanup_(qdisc_freep
) QDisc
*tmp
= NULL
;
438 if (sd_netlink_message_is_error(message
)) {
439 r
= sd_netlink_message_get_errno(message
);
441 log_message_warning_errno(message
, r
, "rtnl: failed to receive QDisc message, ignoring");
446 r
= sd_netlink_message_get_type(message
, &type
);
448 log_warning_errno(r
, "rtnl: could not get message type, ignoring: %m");
450 } else if (!IN_SET(type
, RTM_NEWQDISC
, RTM_DELQDISC
)) {
451 log_warning("rtnl: received unexpected message type %u when processing QDisc, ignoring.", type
);
455 r
= sd_rtnl_message_traffic_control_get_ifindex(message
, &ifindex
);
457 log_warning_errno(r
, "rtnl: could not get ifindex from message, ignoring: %m");
459 } else if (ifindex
<= 0) {
460 log_warning("rtnl: received QDisc message with invalid ifindex %d, ignoring.", ifindex
);
464 if (link_get_by_index(m
, ifindex
, &link
) < 0) {
466 log_warning("rtnl: received QDisc for link '%d' we don't know about, ignoring.", ifindex
);
470 r
= qdisc_new(_QDISC_KIND_INVALID
, &tmp
);
474 r
= sd_rtnl_message_traffic_control_get_handle(message
, &tmp
->handle
);
476 log_link_warning_errno(link
, r
, "rtnl: received QDisc message without handle, ignoring: %m");
480 r
= sd_rtnl_message_traffic_control_get_parent(message
, &tmp
->parent
);
482 log_link_warning_errno(link
, r
, "rtnl: received QDisc message without parent, ignoring: %m");
486 r
= sd_netlink_message_read_string_strdup(message
, TCA_KIND
, &tmp
->tca_kind
);
488 log_link_warning_errno(link
, r
, "rtnl: received QDisc message without kind, ignoring: %m");
492 (void) qdisc_get(link
, tmp
, &qdisc
);
497 qdisc_enter_configured(qdisc
);
498 log_qdisc_debug(qdisc
, link
, "Received remembered");
500 qdisc_enter_configured(tmp
);
501 log_qdisc_debug(tmp
, link
, "Received new");
503 r
= qdisc_add(link
, tmp
);
505 log_link_warning_errno(link
, r
, "Failed to remember QDisc, ignoring: %m");
509 qdisc
= TAKE_PTR(tmp
);
516 qdisc_enter_removed(qdisc
);
517 if (qdisc
->state
== 0) {
518 log_qdisc_debug(qdisc
, link
, "Forgetting");
521 log_qdisc_debug(qdisc
, link
, "Removed");
523 log_qdisc_debug(tmp
, link
, "Kernel removed unknown");
528 assert_not_reached();
534 static int qdisc_section_verify(QDisc
*qdisc
, bool *has_root
, bool *has_clsact
) {
541 if (section_is_invalid(qdisc
->section
))
544 if (QDISC_VTABLE(qdisc
) && QDISC_VTABLE(qdisc
)->verify
) {
545 r
= QDISC_VTABLE(qdisc
)->verify(qdisc
);
550 if (qdisc
->parent
== TC_H_ROOT
) {
552 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL
),
553 "%s: More than one root qdisc section is defined. "
554 "Ignoring the qdisc section from line %u.",
555 qdisc
->section
->filename
, qdisc
->section
->line
);
557 } else if (qdisc
->parent
== TC_H_CLSACT
) { /* TC_H_CLSACT == TC_H_INGRESS */
559 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL
),
560 "%s: More than one clsact or ingress qdisc section is defined. "
561 "Ignoring the qdisc section from line %u.",
562 qdisc
->section
->filename
, qdisc
->section
->line
);
569 void network_drop_invalid_qdisc(Network
*network
) {
570 bool has_root
= false, has_clsact
= false;
575 HASHMAP_FOREACH(qdisc
, network
->qdiscs_by_section
)
576 if (qdisc_section_verify(qdisc
, &has_root
, &has_clsact
) < 0)
580 int config_parse_qdisc_parent(
582 const char *filename
,
585 unsigned section_line
,
592 _cleanup_(qdisc_free_or_set_invalidp
) QDisc
*qdisc
= NULL
;
593 Network
*network
= data
;
601 r
= qdisc_new_static(ltype
, network
, filename
, section_line
, &qdisc
);
605 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
606 "More than one kind of queueing discipline, ignoring assignment: %m");
610 if (streq(rvalue
, "root"))
611 qdisc
->parent
= TC_H_ROOT
;
612 else if (streq(rvalue
, "clsact")) {
613 qdisc
->parent
= TC_H_CLSACT
;
614 qdisc
->handle
= TC_H_MAKE(TC_H_CLSACT
, 0);
615 } else if (streq(rvalue
, "ingress")) {
616 qdisc
->parent
= TC_H_INGRESS
;
617 qdisc
->handle
= TC_H_MAKE(TC_H_INGRESS
, 0);
619 r
= parse_handle(rvalue
, &qdisc
->parent
);
621 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
622 "Failed to parse 'Parent=', ignoring assignment: %s",
628 if (STR_IN_SET(rvalue
, "clsact", "ingress")) {
629 r
= free_and_strdup(&qdisc
->tca_kind
, rvalue
);
633 qdisc
->tca_kind
= mfree(qdisc
->tca_kind
);
640 int config_parse_qdisc_handle(
642 const char *filename
,
645 unsigned section_line
,
652 _cleanup_(qdisc_free_or_set_invalidp
) QDisc
*qdisc
= NULL
;
653 Network
*network
= data
;
662 r
= qdisc_new_static(ltype
, network
, filename
, section_line
, &qdisc
);
666 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
667 "More than one kind of queueing discipline, ignoring assignment: %m");
671 if (isempty(rvalue
)) {
672 qdisc
->handle
= TC_H_UNSPEC
;
677 r
= safe_atou16_full(rvalue
, 16, &n
);
679 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
680 "Failed to parse 'Handle=', ignoring assignment: %s",
685 qdisc
->handle
= (uint32_t) n
<< 16;