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