]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/tc/qdisc.c
Merge pull request #17270 from keszybz/less-secure-mode
[thirdparty/systemd.git] / src / network / tc / qdisc.c
1 /* SPDX-License-Identifier: LGPL-2.1+
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_(network_config_section_freep) NetworkConfigSection *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 = network_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_allocated(&network->tc_by_section, &network_config_hash_ops);
130 if (r < 0)
131 return r;
132
133 r = ordered_hashmap_put(network->tc_by_section, qdisc->section, TC(qdisc));
134 if (r < 0)
135 return r;
136
137 *ret = TAKE_PTR(qdisc);
138 return 0;
139 }
140
141 void qdisc_free(QDisc *qdisc) {
142 if (!qdisc)
143 return;
144
145 if (qdisc->network && qdisc->section)
146 ordered_hashmap_remove(qdisc->network->tc_by_section, qdisc->section);
147
148 network_config_section_free(qdisc->section);
149
150 free(qdisc->tca_kind);
151 free(qdisc);
152 }
153
154 static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
155 int r;
156
157 assert(link);
158 assert(link->tc_messages > 0);
159 link->tc_messages--;
160
161 if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
162 return 1;
163
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);
168 return 1;
169 }
170
171 if (link->tc_messages == 0) {
172 log_link_debug(link, "Traffic control configured");
173 link->tc_configured = true;
174 link_check_ready(link);
175 }
176
177 return 1;
178 }
179
180 int qdisc_configure(Link *link, QDisc *qdisc) {
181 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
182 int r;
183
184 assert(link);
185 assert(link->manager);
186 assert(link->manager->rtnl);
187 assert(link->ifindex > 0);
188
189 r = sd_rtnl_message_new_qdisc(link->manager->rtnl, &req, RTM_NEWQDISC, qdisc->family, link->ifindex);
190 if (r < 0)
191 return log_link_error_errno(link, r, "Could not create RTM_NEWQDISC message: %m");
192
193 r = sd_rtnl_message_set_qdisc_parent(req, qdisc->parent);
194 if (r < 0)
195 return log_link_error_errno(link, r, "Could not create tcm_parent message: %m");
196
197 if (qdisc->handle != TC_H_UNSPEC) {
198 r = sd_rtnl_message_set_qdisc_handle(req, qdisc->handle);
199 if (r < 0)
200 return log_link_error_errno(link, r, "Could not set tcm_handle message: %m");
201 }
202
203 if (QDISC_VTABLE(qdisc)) {
204 if (QDISC_VTABLE(qdisc)->fill_tca_kind) {
205 r = QDISC_VTABLE(qdisc)->fill_tca_kind(link, qdisc, req);
206 if (r < 0)
207 return r;
208 } else {
209 r = sd_netlink_message_append_string(req, TCA_KIND, QDISC_VTABLE(qdisc)->tca_kind);
210 if (r < 0)
211 return log_link_error_errno(link, r, "Could not append TCA_KIND attribute: %m");
212 }
213
214 if (QDISC_VTABLE(qdisc)->fill_message) {
215 r = QDISC_VTABLE(qdisc)->fill_message(link, qdisc, req);
216 if (r < 0)
217 return r;
218 }
219 } else {
220 r = sd_netlink_message_append_string(req, TCA_KIND, qdisc->tca_kind);
221 if (r < 0)
222 return log_link_error_errno(link, r, "Could not append TCA_KIND attribute: %m");
223 }
224
225 r = netlink_call_async(link->manager->rtnl, NULL, req, qdisc_handler, link_netlink_destroy_callback, link);
226 if (r < 0)
227 return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
228
229 link_ref(link);
230 link->tc_messages++;
231
232 return 0;
233 }
234
235 int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact) {
236 int r;
237
238 assert(qdisc);
239 assert(has_root);
240 assert(has_clsact);
241
242 if (section_is_invalid(qdisc->section))
243 return -EINVAL;
244
245 if (QDISC_VTABLE(qdisc) && QDISC_VTABLE(qdisc)->verify) {
246 r = QDISC_VTABLE(qdisc)->verify(qdisc);
247 if (r < 0)
248 return r;
249 }
250
251 if (qdisc->parent == TC_H_ROOT) {
252 if (*has_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);
257 *has_root = true;
258 } else if (qdisc->parent == TC_H_CLSACT) { /* TC_H_CLSACT == TC_H_INGRESS */
259 if (*has_clsact)
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);
264 *has_clsact = true;
265 }
266
267 return 0;
268 }
269
270 int config_parse_qdisc_parent(
271 const char *unit,
272 const char *filename,
273 unsigned line,
274 const char *section,
275 unsigned section_line,
276 const char *lvalue,
277 int ltype,
278 const char *rvalue,
279 void *data,
280 void *userdata) {
281
282 _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
283 Network *network = data;
284 int r;
285
286 assert(filename);
287 assert(lvalue);
288 assert(rvalue);
289 assert(data);
290
291 r = qdisc_new_static(ltype, network, filename, section_line, &qdisc);
292 if (r == -ENOMEM)
293 return log_oom();
294 if (r < 0) {
295 log_syntax(unit, LOG_WARNING, filename, line, r,
296 "More than one kind of queueing discipline, ignoring assignment: %m");
297 return 0;
298 }
299
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);
310 } else {
311 r = parse_handle(rvalue, &qdisc->parent);
312 if (r < 0) {
313 log_syntax(unit, LOG_WARNING, filename, line, r,
314 "Failed to parse 'Parent=', ignoring assignment: %s",
315 rvalue);
316 return 0;
317 }
318 }
319
320 if (STR_IN_SET(rvalue, "clsact", "ingress")) {
321 r = free_and_strdup(&qdisc->tca_kind, rvalue);
322 if (r < 0)
323 return log_oom();
324 } else
325 qdisc->tca_kind = mfree(qdisc->tca_kind);
326
327 qdisc = NULL;
328
329 return 0;
330 }
331
332 int config_parse_qdisc_handle(
333 const char *unit,
334 const char *filename,
335 unsigned line,
336 const char *section,
337 unsigned section_line,
338 const char *lvalue,
339 int ltype,
340 const char *rvalue,
341 void *data,
342 void *userdata) {
343
344 _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
345 Network *network = data;
346 uint16_t n;
347 int r;
348
349 assert(filename);
350 assert(lvalue);
351 assert(rvalue);
352 assert(data);
353
354 r = qdisc_new_static(ltype, network, filename, section_line, &qdisc);
355 if (r == -ENOMEM)
356 return log_oom();
357 if (r < 0) {
358 log_syntax(unit, LOG_WARNING, filename, line, r,
359 "More than one kind of queueing discipline, ignoring assignment: %m");
360 return 0;
361 }
362
363 if (isempty(rvalue)) {
364 qdisc->handle = TC_H_UNSPEC;
365 qdisc = NULL;
366 return 0;
367 }
368
369 r = safe_atou16_full(rvalue, 16, &n);
370 if (r < 0) {
371 log_syntax(unit, LOG_WARNING, filename, line, r,
372 "Failed to parse 'Handle=', ignoring assignment: %s",
373 rvalue);
374 return 0;
375 }
376
377 qdisc->handle = (uint32_t) n << 16;
378 qdisc = NULL;
379
380 return 0;
381 }