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