1 /* SPDX-License-Identifier: LGPL-2.1+
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
,
58 qdisc
= malloc0(qdisc_vtable
[kind
]->object_size
);
62 qdisc
->meta
.kind
= TC_KIND_QDISC
,
63 qdisc
->family
= AF_UNSPEC
;
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_(network_config_section_freep
) NetworkConfigSection
*n
= NULL
;
81 _cleanup_(qdisc_freep
) QDisc
*qdisc
= NULL
;
82 TrafficControl
*existing
;
89 assert(section_line
> 0);
91 r
= network_config_section_new(filename
, section_line
, &n
);
95 existing
= ordered_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
->family
= q
->family
;
119 qdisc
->handle
= q
->handle
;
120 qdisc
->parent
= q
->parent
;
121 qdisc
->tca_kind
= TAKE_PTR(q
->tca_kind
);
126 qdisc
->network
= network
;
127 qdisc
->section
= TAKE_PTR(n
);
129 r
= ordered_hashmap_ensure_allocated(&network
->tc_by_section
, &network_config_hash_ops
);
133 r
= ordered_hashmap_put(network
->tc_by_section
, qdisc
->section
, TC(qdisc
));
137 *ret
= TAKE_PTR(qdisc
);
141 void qdisc_free(QDisc
*qdisc
) {
145 if (qdisc
->network
&& qdisc
->section
)
146 ordered_hashmap_remove(qdisc
->network
->tc_by_section
, qdisc
->section
);
148 network_config_section_free(qdisc
->section
);
150 free(qdisc
->tca_kind
);
154 static int qdisc_handler(sd_netlink
*rtnl
, sd_netlink_message
*m
, Link
*link
) {
158 assert(link
->tc_messages
> 0);
161 if (IN_SET(link
->state
, LINK_STATE_FAILED
, LINK_STATE_LINGER
))
164 r
= sd_netlink_message_get_errno(m
);
165 if (r
< 0 && r
!= -EEXIST
) {
166 log_link_message_error_errno(link
, m
, r
, "Could not set QDisc");
167 link_enter_failed(link
);
171 if (link
->tc_messages
== 0) {
172 log_link_debug(link
, "Traffic control configured");
173 link
->tc_configured
= true;
174 link_check_ready(link
);
180 int qdisc_configure(Link
*link
, QDisc
*qdisc
) {
181 _cleanup_(sd_netlink_message_unrefp
) sd_netlink_message
*req
= NULL
;
185 assert(link
->manager
);
186 assert(link
->manager
->rtnl
);
187 assert(link
->ifindex
> 0);
189 r
= sd_rtnl_message_new_qdisc(link
->manager
->rtnl
, &req
, RTM_NEWQDISC
, qdisc
->family
, link
->ifindex
);
191 return log_link_error_errno(link
, r
, "Could not create RTM_NEWQDISC message: %m");
193 r
= sd_rtnl_message_set_qdisc_parent(req
, qdisc
->parent
);
195 return log_link_error_errno(link
, r
, "Could not create tcm_parent message: %m");
197 if (qdisc
->handle
!= TC_H_UNSPEC
) {
198 r
= sd_rtnl_message_set_qdisc_handle(req
, qdisc
->handle
);
200 return log_link_error_errno(link
, r
, "Could not set tcm_handle message: %m");
203 if (QDISC_VTABLE(qdisc
)) {
204 if (QDISC_VTABLE(qdisc
)->fill_tca_kind
) {
205 r
= QDISC_VTABLE(qdisc
)->fill_tca_kind(link
, qdisc
, req
);
209 r
= sd_netlink_message_append_string(req
, TCA_KIND
, QDISC_VTABLE(qdisc
)->tca_kind
);
211 return log_link_error_errno(link
, r
, "Could not append TCA_KIND attribute: %m");
214 if (QDISC_VTABLE(qdisc
)->fill_message
) {
215 r
= QDISC_VTABLE(qdisc
)->fill_message(link
, qdisc
, req
);
220 r
= sd_netlink_message_append_string(req
, TCA_KIND
, qdisc
->tca_kind
);
222 return log_link_error_errno(link
, r
, "Could not append TCA_KIND attribute: %m");
225 r
= netlink_call_async(link
->manager
->rtnl
, NULL
, req
, qdisc_handler
, link_netlink_destroy_callback
, link
);
227 return log_link_error_errno(link
, r
, "Could not send rtnetlink message: %m");
235 int qdisc_section_verify(QDisc
*qdisc
, bool *has_root
, bool *has_clsact
) {
242 if (section_is_invalid(qdisc
->section
))
245 if (QDISC_VTABLE(qdisc
) && QDISC_VTABLE(qdisc
)->verify
) {
246 r
= QDISC_VTABLE(qdisc
)->verify(qdisc
);
251 if (qdisc
->parent
== TC_H_ROOT
) {
253 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL
),
254 "%s: More than one root qdisc section is defined. "
255 "Ignoring the qdisc section from line %u.",
256 qdisc
->section
->filename
, qdisc
->section
->line
);
258 } else if (qdisc
->parent
== TC_H_CLSACT
) { /* TC_H_CLSACT == TC_H_INGRESS */
260 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL
),
261 "%s: More than one clsact or ingress qdisc section is defined. "
262 "Ignoring the qdisc section from line %u.",
263 qdisc
->section
->filename
, qdisc
->section
->line
);
270 int config_parse_qdisc_parent(
272 const char *filename
,
275 unsigned section_line
,
282 _cleanup_(qdisc_free_or_set_invalidp
) QDisc
*qdisc
= NULL
;
283 Network
*network
= data
;
291 r
= qdisc_new_static(ltype
, network
, filename
, section_line
, &qdisc
);
295 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
296 "More than one kind of queueing discipline, ignoring assignment: %m");
300 if (streq(rvalue
, "root")) {
301 qdisc
->parent
= TC_H_ROOT
;
302 if (qdisc
->handle
== 0)
303 qdisc
->handle
= TC_H_UNSPEC
;
304 } else if (streq(rvalue
, "clsact")) {
305 qdisc
->parent
= TC_H_CLSACT
;
306 qdisc
->handle
= TC_H_MAKE(TC_H_CLSACT
, 0);
307 } else if (streq(rvalue
, "ingress")) {
308 qdisc
->parent
= TC_H_INGRESS
;
309 qdisc
->handle
= TC_H_MAKE(TC_H_INGRESS
, 0);
311 r
= parse_handle(rvalue
, &qdisc
->parent
);
313 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
314 "Failed to parse 'Parent=', ignoring assignment: %s",
320 if (STR_IN_SET(rvalue
, "clsact", "ingress")) {
321 r
= free_and_strdup(&qdisc
->tca_kind
, rvalue
);
325 qdisc
->tca_kind
= mfree(qdisc
->tca_kind
);
332 int config_parse_qdisc_handle(
334 const char *filename
,
337 unsigned section_line
,
344 _cleanup_(qdisc_free_or_set_invalidp
) QDisc
*qdisc
= NULL
;
345 Network
*network
= data
;
354 r
= qdisc_new_static(ltype
, network
, filename
, section_line
, &qdisc
);
358 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
359 "More than one kind of queueing discipline, ignoring assignment: %m");
363 if (isempty(rvalue
)) {
364 qdisc
->handle
= TC_H_UNSPEC
;
369 r
= safe_atou16_full(rvalue
, 16, &n
);
371 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
372 "Failed to parse 'Handle=', ignoring assignment: %s",
377 qdisc
->handle
= (uint32_t) n
<< 16;