]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/tc/qdisc.c
Merge pull request #22175 from keszybz/kernel-install-mkosi-initrd
[thirdparty/systemd.git] / src / network / tc / qdisc.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later
2 * Copyright © 2019 VMware, Inc. */
3
4 #include <linux/pkt_sched.h>
5
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"
12 #include "qdisc.h"
13 #include "set.h"
14 #include "string-util.h"
15 #include "strv.h"
16 #include "tc-util.h"
17
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,
40 };
41
42 static int qdisc_new(QDiscKind kind, QDisc **ret) {
43 _cleanup_(qdisc_freep) QDisc *qdisc = NULL;
44 int r;
45
46 if (kind == _QDISC_KIND_INVALID) {
47 qdisc = new(QDisc, 1);
48 if (!qdisc)
49 return -ENOMEM;
50
51 *qdisc = (QDisc) {
52 .meta.kind = TC_KIND_QDISC,
53 .family = AF_UNSPEC,
54 .parent = TC_H_ROOT,
55 .kind = kind,
56 };
57 } else {
58 qdisc = malloc0(qdisc_vtable[kind]->object_size);
59 if (!qdisc)
60 return -ENOMEM;
61
62 qdisc->meta.kind = TC_KIND_QDISC,
63 qdisc->family = AF_UNSPEC;
64 qdisc->parent = TC_H_ROOT;
65 qdisc->kind = kind;
66
67 if (QDISC_VTABLE(qdisc)->init) {
68 r = QDISC_VTABLE(qdisc)->init(qdisc);
69 if (r < 0)
70 return r;
71 }
72 }
73
74 *ret = TAKE_PTR(qdisc);
75
76 return 0;
77 }
78
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;
83 QDisc *q = NULL;
84 int r;
85
86 assert(network);
87 assert(ret);
88 assert(filename);
89 assert(section_line > 0);
90
91 r = config_section_new(filename, section_line, &n);
92 if (r < 0)
93 return r;
94
95 existing = ordered_hashmap_get(network->tc_by_section, n);
96 if (existing) {
97 if (existing->kind != TC_KIND_QDISC)
98 return -EINVAL;
99
100 q = TC_TO_QDISC(existing);
101
102 if (q->kind != _QDISC_KIND_INVALID &&
103 kind != _QDISC_KIND_INVALID &&
104 q->kind != kind)
105 return -EINVAL;
106
107 if (q->kind == kind || kind == _QDISC_KIND_INVALID) {
108 *ret = q;
109 return 0;
110 }
111 }
112
113 r = qdisc_new(kind, &qdisc);
114 if (r < 0)
115 return r;
116
117 if (q) {
118 qdisc->family = q->family;
119 qdisc->handle = q->handle;
120 qdisc->parent = q->parent;
121 qdisc->tca_kind = TAKE_PTR(q->tca_kind);
122
123 qdisc_free(q);
124 }
125
126 qdisc->network = network;
127 qdisc->section = TAKE_PTR(n);
128
129 r = ordered_hashmap_ensure_put(&network->tc_by_section, &config_section_hash_ops, qdisc->section, TC(qdisc));
130 if (r < 0)
131 return r;
132
133 *ret = TAKE_PTR(qdisc);
134 return 0;
135 }
136
137 QDisc* qdisc_free(QDisc *qdisc) {
138 if (!qdisc)
139 return NULL;
140
141 if (qdisc->network && qdisc->section)
142 ordered_hashmap_remove(qdisc->network->tc_by_section, qdisc->section);
143
144 config_section_free(qdisc->section);
145
146 free(qdisc->tca_kind);
147 return mfree(qdisc);
148 }
149
150 static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
151 int r;
152
153 assert(link);
154 assert(link->tc_messages > 0);
155 link->tc_messages--;
156
157 if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
158 return 1;
159
160 r = sd_netlink_message_get_errno(m);
161 if (r < 0 && r != -EEXIST) {
162 log_link_message_error_errno(link, m, r, "Could not set QDisc");
163 link_enter_failed(link);
164 return 1;
165 }
166
167 if (link->tc_messages == 0) {
168 log_link_debug(link, "Traffic control configured");
169 link->tc_configured = true;
170 link_check_ready(link);
171 }
172
173 return 1;
174 }
175
176 int qdisc_configure(Link *link, QDisc *qdisc) {
177 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
178 int r;
179
180 assert(link);
181 assert(link->manager);
182 assert(link->manager->rtnl);
183 assert(link->ifindex > 0);
184
185 r = sd_rtnl_message_new_qdisc(link->manager->rtnl, &req, RTM_NEWQDISC, qdisc->family, link->ifindex);
186 if (r < 0)
187 return log_link_debug_errno(link, r, "Could not create RTM_NEWQDISC message: %m");
188
189 r = sd_rtnl_message_set_qdisc_parent(req, qdisc->parent);
190 if (r < 0)
191 return r;
192
193 if (qdisc->handle != TC_H_UNSPEC) {
194 r = sd_rtnl_message_set_qdisc_handle(req, qdisc->handle);
195 if (r < 0)
196 return r;
197 }
198
199 if (QDISC_VTABLE(qdisc)) {
200 if (QDISC_VTABLE(qdisc)->fill_tca_kind) {
201 r = QDISC_VTABLE(qdisc)->fill_tca_kind(link, qdisc, req);
202 if (r < 0)
203 return r;
204 } else {
205 r = sd_netlink_message_append_string(req, TCA_KIND, QDISC_VTABLE(qdisc)->tca_kind);
206 if (r < 0)
207 return r;
208 }
209
210 if (QDISC_VTABLE(qdisc)->fill_message) {
211 r = QDISC_VTABLE(qdisc)->fill_message(link, qdisc, req);
212 if (r < 0)
213 return r;
214 }
215 } else {
216 r = sd_netlink_message_append_string(req, TCA_KIND, qdisc->tca_kind);
217 if (r < 0)
218 return r;
219 }
220
221 r = netlink_call_async(link->manager->rtnl, NULL, req, qdisc_handler, link_netlink_destroy_callback, link);
222 if (r < 0)
223 return log_link_debug_errno(link, r, "Could not send netlink message: %m");
224
225 link_ref(link);
226 link->tc_messages++;
227
228 return 0;
229 }
230
231 int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact) {
232 int r;
233
234 assert(qdisc);
235 assert(has_root);
236 assert(has_clsact);
237
238 if (section_is_invalid(qdisc->section))
239 return -EINVAL;
240
241 if (QDISC_VTABLE(qdisc) && QDISC_VTABLE(qdisc)->verify) {
242 r = QDISC_VTABLE(qdisc)->verify(qdisc);
243 if (r < 0)
244 return r;
245 }
246
247 if (qdisc->parent == TC_H_ROOT) {
248 if (*has_root)
249 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
250 "%s: More than one root qdisc section is defined. "
251 "Ignoring the qdisc section from line %u.",
252 qdisc->section->filename, qdisc->section->line);
253 *has_root = true;
254 } else if (qdisc->parent == TC_H_CLSACT) { /* TC_H_CLSACT == TC_H_INGRESS */
255 if (*has_clsact)
256 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
257 "%s: More than one clsact or ingress qdisc section is defined. "
258 "Ignoring the qdisc section from line %u.",
259 qdisc->section->filename, qdisc->section->line);
260 *has_clsact = true;
261 }
262
263 return 0;
264 }
265
266 int config_parse_qdisc_parent(
267 const char *unit,
268 const char *filename,
269 unsigned line,
270 const char *section,
271 unsigned section_line,
272 const char *lvalue,
273 int ltype,
274 const char *rvalue,
275 void *data,
276 void *userdata) {
277
278 _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
279 Network *network = data;
280 int r;
281
282 assert(filename);
283 assert(lvalue);
284 assert(rvalue);
285 assert(data);
286
287 r = qdisc_new_static(ltype, network, filename, section_line, &qdisc);
288 if (r == -ENOMEM)
289 return log_oom();
290 if (r < 0) {
291 log_syntax(unit, LOG_WARNING, filename, line, r,
292 "More than one kind of queueing discipline, ignoring assignment: %m");
293 return 0;
294 }
295
296 if (streq(rvalue, "root")) {
297 qdisc->parent = TC_H_ROOT;
298 if (qdisc->handle == 0)
299 qdisc->handle = TC_H_UNSPEC;
300 } else if (streq(rvalue, "clsact")) {
301 qdisc->parent = TC_H_CLSACT;
302 qdisc->handle = TC_H_MAKE(TC_H_CLSACT, 0);
303 } else if (streq(rvalue, "ingress")) {
304 qdisc->parent = TC_H_INGRESS;
305 qdisc->handle = TC_H_MAKE(TC_H_INGRESS, 0);
306 } else {
307 r = parse_handle(rvalue, &qdisc->parent);
308 if (r < 0) {
309 log_syntax(unit, LOG_WARNING, filename, line, r,
310 "Failed to parse 'Parent=', ignoring assignment: %s",
311 rvalue);
312 return 0;
313 }
314 }
315
316 if (STR_IN_SET(rvalue, "clsact", "ingress")) {
317 r = free_and_strdup(&qdisc->tca_kind, rvalue);
318 if (r < 0)
319 return log_oom();
320 } else
321 qdisc->tca_kind = mfree(qdisc->tca_kind);
322
323 TAKE_PTR(qdisc);
324
325 return 0;
326 }
327
328 int config_parse_qdisc_handle(
329 const char *unit,
330 const char *filename,
331 unsigned line,
332 const char *section,
333 unsigned section_line,
334 const char *lvalue,
335 int ltype,
336 const char *rvalue,
337 void *data,
338 void *userdata) {
339
340 _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
341 Network *network = data;
342 uint16_t n;
343 int r;
344
345 assert(filename);
346 assert(lvalue);
347 assert(rvalue);
348 assert(data);
349
350 r = qdisc_new_static(ltype, network, filename, section_line, &qdisc);
351 if (r == -ENOMEM)
352 return log_oom();
353 if (r < 0) {
354 log_syntax(unit, LOG_WARNING, filename, line, r,
355 "More than one kind of queueing discipline, ignoring assignment: %m");
356 return 0;
357 }
358
359 if (isempty(rvalue)) {
360 qdisc->handle = TC_H_UNSPEC;
361 TAKE_PTR(qdisc);
362 return 0;
363 }
364
365 r = safe_atou16_full(rvalue, 16, &n);
366 if (r < 0) {
367 log_syntax(unit, LOG_WARNING, filename, line, r,
368 "Failed to parse 'Handle=', ignoring assignment: %s",
369 rvalue);
370 return 0;
371 }
372
373 qdisc->handle = (uint32_t) n << 16;
374 TAKE_PTR(qdisc);
375
376 return 0;
377 }