]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/tc/qdisc.c
Merge pull request #22445 from lnussel/logind
[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 "networkd-queue.h"
12 #include "parse-util.h"
13 #include "qdisc.h"
14 #include "set.h"
15 #include "string-util.h"
16 #include "strv.h"
17 #include "tc-util.h"
18
19 const QDiscVTable * const qdisc_vtable[_QDISC_KIND_MAX] = {
20 [QDISC_KIND_BFIFO] = &bfifo_vtable,
21 [QDISC_KIND_CAKE] = &cake_vtable,
22 [QDISC_KIND_CODEL] = &codel_vtable,
23 [QDISC_KIND_DRR] = &drr_vtable,
24 [QDISC_KIND_ETS] = &ets_vtable,
25 [QDISC_KIND_FQ] = &fq_vtable,
26 [QDISC_KIND_FQ_CODEL] = &fq_codel_vtable,
27 [QDISC_KIND_FQ_PIE] = &fq_pie_vtable,
28 [QDISC_KIND_GRED] = &gred_vtable,
29 [QDISC_KIND_HHF] = &hhf_vtable,
30 [QDISC_KIND_HTB] = &htb_vtable,
31 [QDISC_KIND_NETEM] = &netem_vtable,
32 [QDISC_KIND_PIE] = &pie_vtable,
33 [QDISC_KIND_QFQ] = &qfq_vtable,
34 [QDISC_KIND_PFIFO] = &pfifo_vtable,
35 [QDISC_KIND_PFIFO_FAST] = &pfifo_fast_vtable,
36 [QDISC_KIND_PFIFO_HEAD_DROP] = &pfifo_head_drop_vtable,
37 [QDISC_KIND_SFB] = &sfb_vtable,
38 [QDISC_KIND_SFQ] = &sfq_vtable,
39 [QDISC_KIND_TBF] = &tbf_vtable,
40 [QDISC_KIND_TEQL] = &teql_vtable,
41 };
42
43 static int qdisc_new(QDiscKind kind, QDisc **ret) {
44 _cleanup_(qdisc_freep) QDisc *qdisc = NULL;
45 int r;
46
47 if (kind == _QDISC_KIND_INVALID) {
48 qdisc = new(QDisc, 1);
49 if (!qdisc)
50 return -ENOMEM;
51
52 *qdisc = (QDisc) {
53 .meta.kind = TC_KIND_QDISC,
54 .parent = TC_H_ROOT,
55 .kind = kind,
56 };
57 } else {
58 assert(kind >= 0 && kind < _QDISC_KIND_MAX);
59 qdisc = malloc0(qdisc_vtable[kind]->object_size);
60 if (!qdisc)
61 return -ENOMEM;
62
63 qdisc->meta.kind = TC_KIND_QDISC,
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 = 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->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 qdisc->source = NETWORK_CONFIG_SOURCE_STATIC;
128
129 r = 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 hashmap_remove(qdisc->network->tc_by_section, qdisc->section);
143
144 config_section_free(qdisc->section);
145
146 if (qdisc->link)
147 set_remove(qdisc->link->traffic_control, TC(qdisc));
148
149 free(qdisc->tca_kind);
150 return mfree(qdisc);
151 }
152
153 static const char *qdisc_get_tca_kind(const QDisc *qdisc) {
154 assert(qdisc);
155
156 return (QDISC_VTABLE(qdisc) && QDISC_VTABLE(qdisc)->tca_kind) ?
157 QDISC_VTABLE(qdisc)->tca_kind : qdisc->tca_kind;
158 }
159
160 void qdisc_hash_func(const QDisc *qdisc, struct siphash *state) {
161 assert(qdisc);
162 assert(state);
163
164 siphash24_compress(&qdisc->handle, sizeof(qdisc->handle), state);
165 siphash24_compress(&qdisc->parent, sizeof(qdisc->parent), state);
166 siphash24_compress_string(qdisc_get_tca_kind(qdisc), state);
167 }
168
169 int qdisc_compare_func(const QDisc *a, const QDisc *b) {
170 int r;
171
172 assert(a);
173 assert(b);
174
175 r = CMP(a->handle, b->handle);
176 if (r != 0)
177 return r;
178
179 r = CMP(a->parent, b->parent);
180 if (r != 0)
181 return r;
182
183 return strcmp_ptr(qdisc_get_tca_kind(a), qdisc_get_tca_kind(b));
184 }
185
186 static int qdisc_get(Link *link, const QDisc *in, QDisc **ret) {
187 TrafficControl *existing;
188 int r;
189
190 assert(link);
191 assert(in);
192
193 r = traffic_control_get(link, TC(in), &existing);
194 if (r < 0)
195 return r;
196
197 if (ret)
198 *ret = TC_TO_QDISC(existing);
199 return 0;
200 }
201
202 static int qdisc_add(Link *link, QDisc *qdisc) {
203 int r;
204
205 assert(link);
206 assert(qdisc);
207
208 r = traffic_control_add(link, TC(qdisc));
209 if (r < 0)
210 return r;
211
212 qdisc->link = link;
213 return 0;
214 }
215
216 static int qdisc_dup(const QDisc *src, QDisc **ret) {
217 _cleanup_(qdisc_freep) QDisc *dst = NULL;
218
219 assert(src);
220 assert(ret);
221
222 if (QDISC_VTABLE(src))
223 dst = memdup(src, QDISC_VTABLE(src)->object_size);
224 else
225 dst = newdup(QDisc, src, 1);
226 if (!dst)
227 return -ENOMEM;
228
229 /* clear all pointers */
230 dst->network = NULL;
231 dst->section = NULL;
232 dst->link = NULL;
233 dst->tca_kind = NULL;
234
235 if (src->tca_kind) {
236 dst->tca_kind = strdup(src->tca_kind);
237 if (!dst->tca_kind)
238 return -ENOMEM;
239 }
240
241 *ret = TAKE_PTR(dst);
242 return 0;
243 }
244
245 static void log_qdisc_debug(QDisc *qdisc, Link *link, const char *str) {
246 _cleanup_free_ char *state = NULL;
247
248 assert(qdisc);
249 assert(str);
250
251 if (!DEBUG_LOGGING)
252 return;
253
254 (void) network_config_state_to_string_alloc(qdisc->state, &state);
255
256 log_link_debug(link, "%s %s QDisc (%s): handle=%"PRIx32":%"PRIx32", parent=%"PRIx32":%"PRIx32", kind=%s",
257 str, strna(network_config_source_to_string(qdisc->source)), strna(state),
258 TC_H_MAJ(qdisc->handle) >> 16, TC_H_MIN(qdisc->handle),
259 TC_H_MAJ(qdisc->parent) >> 16, TC_H_MIN(qdisc->parent),
260 strna(qdisc_get_tca_kind(qdisc)));
261 }
262
263 int link_find_qdisc(Link *link, uint32_t handle, uint32_t parent, const char *kind, QDisc **ret) {
264 TrafficControl *tc;
265
266 assert(link);
267
268 handle = TC_H_MAJ(handle);
269
270 SET_FOREACH(tc, link->traffic_control) {
271 QDisc *qdisc;
272
273 if (tc->kind != TC_KIND_QDISC)
274 continue;
275
276 qdisc = TC_TO_QDISC(tc);
277
278 if (qdisc->handle != handle)
279 continue;
280
281 if (qdisc->parent != parent)
282 continue;
283
284 if (qdisc->source == NETWORK_CONFIG_SOURCE_FOREIGN)
285 continue;
286
287 if (!qdisc_exists(qdisc))
288 continue;
289
290 if (kind && !streq_ptr(kind, qdisc_get_tca_kind(qdisc)))
291 continue;
292
293 if (ret)
294 *ret = qdisc;
295 return 0;
296 }
297
298 return -ENOENT;
299 }
300
301 static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
302 int r;
303
304 assert(link);
305 assert(link->tc_messages > 0);
306 link->tc_messages--;
307
308 if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
309 return 1;
310
311 r = sd_netlink_message_get_errno(m);
312 if (r < 0 && r != -EEXIST) {
313 log_link_message_error_errno(link, m, r, "Could not set QDisc");
314 link_enter_failed(link);
315 return 1;
316 }
317
318 if (link->tc_messages == 0) {
319 log_link_debug(link, "Traffic control configured");
320 link->tc_configured = true;
321 link_check_ready(link);
322 }
323
324 return 1;
325 }
326
327 int qdisc_configure(Link *link, QDisc *qdisc) {
328 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
329 int r;
330
331 assert(link);
332 assert(link->manager);
333 assert(link->manager->rtnl);
334 assert(link->ifindex > 0);
335
336 log_qdisc_debug(qdisc, link, "Configuring");
337
338 r = sd_rtnl_message_new_traffic_control(link->manager->rtnl, &req, RTM_NEWQDISC,
339 link->ifindex, qdisc->handle, qdisc->parent);
340 if (r < 0)
341 return log_link_debug_errno(link, r, "Could not create RTM_NEWQDISC message: %m");
342
343 r = sd_netlink_message_append_string(req, TCA_KIND, qdisc_get_tca_kind(qdisc));
344 if (r < 0)
345 return r;
346
347 if (QDISC_VTABLE(qdisc) && QDISC_VTABLE(qdisc)->fill_message) {
348 r = QDISC_VTABLE(qdisc)->fill_message(link, qdisc, req);
349 if (r < 0)
350 return r;
351 }
352
353 r = netlink_call_async(link->manager->rtnl, NULL, req, qdisc_handler, link_netlink_destroy_callback, link);
354 if (r < 0)
355 return log_link_debug_errno(link, r, "Could not send netlink message: %m");
356
357 link_ref(link);
358
359 qdisc_enter_configuring(qdisc);
360 return 0;
361 }
362
363 int qdisc_is_ready_to_configure(Link *link, QDisc *qdisc) {
364 assert(link);
365 assert(qdisc);
366
367 if (IN_SET(qdisc->parent, TC_H_ROOT, TC_H_CLSACT)) /* TC_H_CLSACT == TC_H_INGRESS */
368 return true;
369
370 return link_find_tclass(link, qdisc->parent, NULL) >= 0;
371 }
372
373 int link_request_qdisc(Link *link, QDisc *qdisc) {
374 QDisc *existing;
375 int r;
376
377 assert(link);
378 assert(qdisc);
379
380 if (qdisc_get(link, qdisc, &existing) < 0) {
381 _cleanup_(qdisc_freep) QDisc *tmp = NULL;
382
383 r = qdisc_dup(qdisc, &tmp);
384 if (r < 0)
385 return log_oom();
386
387 r = qdisc_add(link, tmp);
388 if (r < 0)
389 return log_link_warning_errno(link, r, "Failed to store QDisc: %m");
390
391 existing = TAKE_PTR(tmp);
392 } else
393 existing->source = qdisc->source;
394
395 log_qdisc_debug(existing, link, "Requesting");
396 r = link_queue_request(link, REQUEST_TYPE_TRAFFIC_CONTROL, TC(existing), false,
397 &link->tc_messages, NULL, NULL);
398 if (r < 0)
399 return log_link_warning_errno(link, r, "Failed to request QDisc: %m");
400 if (r == 0)
401 return 0;
402
403 qdisc_enter_requesting(existing);
404 return 1;
405 }
406
407 int manager_rtnl_process_qdisc(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) {
408 _cleanup_(qdisc_freep) QDisc *tmp = NULL;
409 QDisc *qdisc = NULL;
410 Link *link;
411 uint16_t type;
412 int ifindex, r;
413
414 assert(rtnl);
415 assert(message);
416 assert(m);
417
418 if (sd_netlink_message_is_error(message)) {
419 r = sd_netlink_message_get_errno(message);
420 if (r < 0)
421 log_message_warning_errno(message, r, "rtnl: failed to receive QDisc message, ignoring");
422
423 return 0;
424 }
425
426 r = sd_netlink_message_get_type(message, &type);
427 if (r < 0) {
428 log_warning_errno(r, "rtnl: could not get message type, ignoring: %m");
429 return 0;
430 } else if (!IN_SET(type, RTM_NEWQDISC, RTM_DELQDISC)) {
431 log_warning("rtnl: received unexpected message type %u when processing QDisc, ignoring.", type);
432 return 0;
433 }
434
435 r = sd_rtnl_message_traffic_control_get_ifindex(message, &ifindex);
436 if (r < 0) {
437 log_warning_errno(r, "rtnl: could not get ifindex from message, ignoring: %m");
438 return 0;
439 } else if (ifindex <= 0) {
440 log_warning("rtnl: received QDisc message with invalid ifindex %d, ignoring.", ifindex);
441 return 0;
442 }
443
444 if (link_get_by_index(m, ifindex, &link) < 0) {
445 if (!m->enumerating)
446 log_warning("rtnl: received QDisc for link '%d' we don't know about, ignoring.", ifindex);
447 return 0;
448 }
449
450 r = qdisc_new(_QDISC_KIND_INVALID, &tmp);
451 if (r < 0)
452 return log_oom();
453
454 r = sd_rtnl_message_traffic_control_get_handle(message, &tmp->handle);
455 if (r < 0) {
456 log_link_warning_errno(link, r, "rtnl: received QDisc message without handle, ignoring: %m");
457 return 0;
458 }
459
460 r = sd_rtnl_message_traffic_control_get_parent(message, &tmp->parent);
461 if (r < 0) {
462 log_link_warning_errno(link, r, "rtnl: received QDisc message without parent, ignoring: %m");
463 return 0;
464 }
465
466 r = sd_netlink_message_read_string_strdup(message, TCA_KIND, &tmp->tca_kind);
467 if (r < 0) {
468 log_link_warning_errno(link, r, "rtnl: received QDisc message without kind, ignoring: %m");
469 return 0;
470 }
471
472 (void) qdisc_get(link, tmp, &qdisc);
473
474 switch (type) {
475 case RTM_NEWQDISC:
476 if (qdisc) {
477 qdisc_enter_configured(qdisc);
478 log_qdisc_debug(qdisc, link, "Received remembered");
479 } else {
480 qdisc_enter_configured(tmp);
481 log_qdisc_debug(tmp, link, "Received new");
482
483 r = qdisc_add(link, tmp);
484 if (r < 0) {
485 log_link_warning_errno(link, r, "Failed to remember QDisc, ignoring: %m");
486 return 0;
487 }
488
489 qdisc = TAKE_PTR(tmp);
490 }
491
492 break;
493
494 case RTM_DELQDISC:
495 if (qdisc) {
496 qdisc_enter_removed(qdisc);
497 if (qdisc->state == 0) {
498 log_qdisc_debug(qdisc, link, "Forgetting");
499 qdisc_free(qdisc);
500 } else
501 log_qdisc_debug(qdisc, link, "Removed");
502 } else
503 log_qdisc_debug(tmp, link, "Kernel removed unknown");
504
505 break;
506
507 default:
508 assert_not_reached();
509 }
510
511 return 1;
512 }
513
514 int qdisc_section_verify(QDisc *qdisc, bool *has_root, bool *has_clsact) {
515 int r;
516
517 assert(qdisc);
518 assert(has_root);
519 assert(has_clsact);
520
521 if (section_is_invalid(qdisc->section))
522 return -EINVAL;
523
524 if (QDISC_VTABLE(qdisc) && QDISC_VTABLE(qdisc)->verify) {
525 r = QDISC_VTABLE(qdisc)->verify(qdisc);
526 if (r < 0)
527 return r;
528 }
529
530 if (qdisc->parent == TC_H_ROOT) {
531 if (*has_root)
532 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
533 "%s: More than one root qdisc section is defined. "
534 "Ignoring the qdisc section from line %u.",
535 qdisc->section->filename, qdisc->section->line);
536 *has_root = true;
537 } else if (qdisc->parent == TC_H_CLSACT) { /* TC_H_CLSACT == TC_H_INGRESS */
538 if (*has_clsact)
539 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
540 "%s: More than one clsact or ingress qdisc section is defined. "
541 "Ignoring the qdisc section from line %u.",
542 qdisc->section->filename, qdisc->section->line);
543 *has_clsact = true;
544 }
545
546 return 0;
547 }
548
549 int config_parse_qdisc_parent(
550 const char *unit,
551 const char *filename,
552 unsigned line,
553 const char *section,
554 unsigned section_line,
555 const char *lvalue,
556 int ltype,
557 const char *rvalue,
558 void *data,
559 void *userdata) {
560
561 _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
562 Network *network = data;
563 int r;
564
565 assert(filename);
566 assert(lvalue);
567 assert(rvalue);
568 assert(data);
569
570 r = qdisc_new_static(ltype, network, filename, section_line, &qdisc);
571 if (r == -ENOMEM)
572 return log_oom();
573 if (r < 0) {
574 log_syntax(unit, LOG_WARNING, filename, line, r,
575 "More than one kind of queueing discipline, ignoring assignment: %m");
576 return 0;
577 }
578
579 if (streq(rvalue, "root"))
580 qdisc->parent = TC_H_ROOT;
581 else if (streq(rvalue, "clsact")) {
582 qdisc->parent = TC_H_CLSACT;
583 qdisc->handle = TC_H_MAKE(TC_H_CLSACT, 0);
584 } else if (streq(rvalue, "ingress")) {
585 qdisc->parent = TC_H_INGRESS;
586 qdisc->handle = TC_H_MAKE(TC_H_INGRESS, 0);
587 } else {
588 r = parse_handle(rvalue, &qdisc->parent);
589 if (r < 0) {
590 log_syntax(unit, LOG_WARNING, filename, line, r,
591 "Failed to parse 'Parent=', ignoring assignment: %s",
592 rvalue);
593 return 0;
594 }
595 }
596
597 if (STR_IN_SET(rvalue, "clsact", "ingress")) {
598 r = free_and_strdup(&qdisc->tca_kind, rvalue);
599 if (r < 0)
600 return log_oom();
601 } else
602 qdisc->tca_kind = mfree(qdisc->tca_kind);
603
604 TAKE_PTR(qdisc);
605
606 return 0;
607 }
608
609 int config_parse_qdisc_handle(
610 const char *unit,
611 const char *filename,
612 unsigned line,
613 const char *section,
614 unsigned section_line,
615 const char *lvalue,
616 int ltype,
617 const char *rvalue,
618 void *data,
619 void *userdata) {
620
621 _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
622 Network *network = data;
623 uint16_t n;
624 int r;
625
626 assert(filename);
627 assert(lvalue);
628 assert(rvalue);
629 assert(data);
630
631 r = qdisc_new_static(ltype, network, filename, section_line, &qdisc);
632 if (r == -ENOMEM)
633 return log_oom();
634 if (r < 0) {
635 log_syntax(unit, LOG_WARNING, filename, line, r,
636 "More than one kind of queueing discipline, ignoring assignment: %m");
637 return 0;
638 }
639
640 if (isempty(rvalue)) {
641 qdisc->handle = TC_H_UNSPEC;
642 TAKE_PTR(qdisc);
643 return 0;
644 }
645
646 r = safe_atou16_full(rvalue, 16, &n);
647 if (r < 0) {
648 log_syntax(unit, LOG_WARNING, filename, line, r,
649 "Failed to parse 'Handle=', ignoring assignment: %s",
650 rvalue);
651 return 0;
652 }
653
654 qdisc->handle = (uint32_t) n << 16;
655 TAKE_PTR(qdisc);
656
657 return 0;
658 }