]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/tc/tclass.c
abc9d7c14556c61d4bbd2c5cafd2aaa284055c8d
[thirdparty/systemd.git] / src / network / tc / tclass.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-link.h"
11 #include "networkd-manager.h"
12 #include "networkd-network.h"
13 #include "networkd-queue.h"
14 #include "parse-util.h"
15 #include "set.h"
16 #include "string-util.h"
17 #include "strv.h"
18 #include "tc-util.h"
19 #include "tclass.h"
20
21 const TClassVTable * const tclass_vtable[_TCLASS_KIND_MAX] = {
22 [TCLASS_KIND_DRR] = &drr_tclass_vtable,
23 [TCLASS_KIND_HTB] = &htb_tclass_vtable,
24 [TCLASS_KIND_QFQ] = &qfq_tclass_vtable,
25 };
26
27 static int tclass_new(TClassKind kind, TClass **ret) {
28 _cleanup_(tclass_freep) TClass *tclass = NULL;
29 int r;
30
31 if (kind == _TCLASS_KIND_INVALID) {
32 tclass = new(TClass, 1);
33 if (!tclass)
34 return -ENOMEM;
35
36 *tclass = (TClass) {
37 .parent = TC_H_ROOT,
38 .kind = kind,
39 };
40 } else {
41 assert(kind >= 0 && kind < _TCLASS_KIND_MAX);
42 tclass = malloc0(tclass_vtable[kind]->object_size);
43 if (!tclass)
44 return -ENOMEM;
45
46 tclass->parent = TC_H_ROOT;
47 tclass->kind = kind;
48
49 if (TCLASS_VTABLE(tclass)->init) {
50 r = TCLASS_VTABLE(tclass)->init(tclass);
51 if (r < 0)
52 return r;
53 }
54 }
55
56 *ret = TAKE_PTR(tclass);
57
58 return 0;
59 }
60
61 int tclass_new_static(TClassKind kind, Network *network, const char *filename, unsigned section_line, TClass **ret) {
62 _cleanup_(config_section_freep) ConfigSection *n = NULL;
63 _cleanup_(tclass_freep) TClass *tclass = NULL;
64 TClass *existing;
65 int r;
66
67 assert(network);
68 assert(ret);
69 assert(filename);
70 assert(section_line > 0);
71
72 r = config_section_new(filename, section_line, &n);
73 if (r < 0)
74 return r;
75
76 existing = hashmap_get(network->tclasses_by_section, n);
77 if (existing) {
78 if (existing->kind != kind)
79 return -EINVAL;
80
81 *ret = existing;
82 return 0;
83 }
84
85 r = tclass_new(kind, &tclass);
86 if (r < 0)
87 return r;
88
89 tclass->network = network;
90 tclass->section = TAKE_PTR(n);
91 tclass->source = NETWORK_CONFIG_SOURCE_STATIC;
92
93 r = hashmap_ensure_put(&network->tclasses_by_section, &config_section_hash_ops, tclass->section, tclass);
94 if (r < 0)
95 return r;
96
97 *ret = TAKE_PTR(tclass);
98 return 0;
99 }
100
101 TClass* tclass_free(TClass *tclass) {
102 if (!tclass)
103 return NULL;
104
105 if (tclass->network && tclass->section)
106 hashmap_remove(tclass->network->tclasses_by_section, tclass->section);
107
108 config_section_free(tclass->section);
109
110 if (tclass->link)
111 set_remove(tclass->link->tclasses, tclass);
112
113 free(tclass->tca_kind);
114 return mfree(tclass);
115 }
116
117 static const char *tclass_get_tca_kind(const TClass *tclass) {
118 assert(tclass);
119
120 return (TCLASS_VTABLE(tclass) && TCLASS_VTABLE(tclass)->tca_kind) ?
121 TCLASS_VTABLE(tclass)->tca_kind : tclass->tca_kind;
122 }
123
124 static void tclass_hash_func(const TClass *tclass, struct siphash *state) {
125 assert(tclass);
126 assert(state);
127
128 siphash24_compress(&tclass->classid, sizeof(tclass->classid), state);
129 siphash24_compress(&tclass->parent, sizeof(tclass->parent), state);
130 siphash24_compress_string(tclass_get_tca_kind(tclass), state);
131 }
132
133 static int tclass_compare_func(const TClass *a, const TClass *b) {
134 int r;
135
136 assert(a);
137 assert(b);
138
139 r = CMP(a->classid, b->classid);
140 if (r != 0)
141 return r;
142
143 r = CMP(a->parent, b->parent);
144 if (r != 0)
145 return r;
146
147 return strcmp_ptr(tclass_get_tca_kind(a), tclass_get_tca_kind(b));
148 }
149
150 DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
151 tclass_hash_ops,
152 TClass,
153 tclass_hash_func,
154 tclass_compare_func,
155 tclass_free);
156
157 static int tclass_get(Link *link, const TClass *in, TClass **ret) {
158 TClass *existing;
159
160 assert(link);
161 assert(in);
162
163 existing = set_get(link->tclasses, in);
164 if (!existing)
165 return -ENOENT;
166
167 if (ret)
168 *ret = existing;
169 return 0;
170 }
171
172 static int tclass_add(Link *link, TClass *tclass) {
173 int r;
174
175 assert(link);
176 assert(tclass);
177
178 r = set_ensure_put(&link->tclasses, &tclass_hash_ops, tclass);
179 if (r < 0)
180 return r;
181 if (r == 0)
182 return -EEXIST;
183
184 tclass->link = link;
185 return 0;
186 }
187
188 static int tclass_dup(const TClass *src, TClass **ret) {
189 _cleanup_(tclass_freep) TClass *dst = NULL;
190
191 assert(src);
192 assert(ret);
193
194 if (TCLASS_VTABLE(src))
195 dst = memdup(src, TCLASS_VTABLE(src)->object_size);
196 else
197 dst = newdup(TClass, src, 1);
198 if (!dst)
199 return -ENOMEM;
200
201 /* clear all pointers */
202 dst->network = NULL;
203 dst->section = NULL;
204 dst->link = NULL;
205 dst->tca_kind = NULL;
206
207 if (src->tca_kind) {
208 dst->tca_kind = strdup(src->tca_kind);
209 if (!dst->tca_kind)
210 return -ENOMEM;
211 }
212
213 *ret = TAKE_PTR(dst);
214 return 0;
215 }
216
217 int link_find_tclass(Link *link, uint32_t classid, TClass **ret) {
218 TClass *tclass;
219
220 assert(link);
221
222 SET_FOREACH(tclass, link->tclasses) {
223 if (tclass->classid != classid)
224 continue;
225
226 if (!tclass_exists(tclass))
227 continue;
228
229 if (ret)
230 *ret = tclass;
231 return 0;
232 }
233
234 return -ENOENT;
235 }
236
237 static void log_tclass_debug(TClass *tclass, Link *link, const char *str) {
238 _cleanup_free_ char *state = NULL;
239
240 assert(tclass);
241 assert(str);
242
243 if (!DEBUG_LOGGING)
244 return;
245
246 (void) network_config_state_to_string_alloc(tclass->state, &state);
247
248 log_link_debug(link, "%s %s TClass (%s): classid=%"PRIx32":%"PRIx32", parent=%"PRIx32":%"PRIx32", kind=%s",
249 str, strna(network_config_source_to_string(tclass->source)), strna(state),
250 TC_H_MAJ(tclass->classid) >> 16, TC_H_MIN(tclass->classid),
251 TC_H_MAJ(tclass->parent) >> 16, TC_H_MIN(tclass->parent),
252 strna(tclass_get_tca_kind(tclass)));
253 }
254
255 TClass* tclass_drop(TClass *tclass) {
256 QDisc *qdisc;
257 Link *link;
258
259 assert(tclass);
260
261 link = ASSERT_PTR(tclass->link);
262
263 /* Also drop all child qdiscs assigned to the class. */
264 SET_FOREACH(qdisc, link->qdiscs) {
265 if (qdisc->parent != tclass->classid)
266 continue;
267
268 qdisc_drop(qdisc);
269 }
270
271 tclass_enter_removed(tclass);
272
273 if (tclass->state == 0) {
274 log_tclass_debug(tclass, link, "Forgetting");
275 tclass = tclass_free(tclass);
276 } else
277 log_tclass_debug(tclass, link, "Removed");
278
279 return tclass;
280 }
281
282 static int tclass_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, TClass *tclass) {
283 int r;
284
285 assert(m);
286 assert(link);
287
288 r = sd_netlink_message_get_errno(m);
289 if (r < 0 && r != -EEXIST) {
290 log_link_message_error_errno(link, m, r, "Could not set TClass");
291 link_enter_failed(link);
292 return 1;
293 }
294
295 if (link->tc_messages == 0) {
296 log_link_debug(link, "Traffic control configured");
297 link->tc_configured = true;
298 link_check_ready(link);
299 }
300
301 return 1;
302 }
303
304 static int tclass_configure(TClass *tclass, Link *link, Request *req) {
305 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
306 int r;
307
308 assert(tclass);
309 assert(link);
310 assert(link->manager);
311 assert(link->manager->rtnl);
312 assert(link->ifindex > 0);
313 assert(req);
314
315 log_tclass_debug(tclass, link, "Configuring");
316
317 r = sd_rtnl_message_new_traffic_control(link->manager->rtnl, &m, RTM_NEWTCLASS,
318 link->ifindex, tclass->classid, tclass->parent);
319 if (r < 0)
320 return r;
321
322 r = sd_netlink_message_append_string(m, TCA_KIND, TCLASS_VTABLE(tclass)->tca_kind);
323 if (r < 0)
324 return r;
325
326 if (TCLASS_VTABLE(tclass)->fill_message) {
327 r = TCLASS_VTABLE(tclass)->fill_message(link, tclass, m);
328 if (r < 0)
329 return r;
330 }
331
332 return request_call_netlink_async(link->manager->rtnl, m, req);
333 }
334
335 static bool tclass_is_ready_to_configure(TClass *tclass, Link *link) {
336 assert(tclass);
337 assert(link);
338
339 if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
340 return false;
341
342 return link_find_qdisc(link, tclass->classid, tclass->parent, tclass_get_tca_kind(tclass), NULL) >= 0;
343 }
344
345 static int tclass_process_request(Request *req, Link *link, TClass *tclass) {
346 int r;
347
348 assert(req);
349 assert(link);
350 assert(tclass);
351
352 if (!tclass_is_ready_to_configure(tclass, link))
353 return 0;
354
355 r = tclass_configure(tclass, link, req);
356 if (r < 0)
357 return log_link_warning_errno(link, r, "Failed to configure TClass: %m");
358
359 tclass_enter_configuring(tclass);
360 return 1;
361 }
362
363 int link_request_tclass(Link *link, TClass *tclass) {
364 TClass *existing;
365 int r;
366
367 assert(link);
368 assert(tclass);
369
370 if (tclass_get(link, tclass, &existing) < 0) {
371 _cleanup_(tclass_freep) TClass *tmp = NULL;
372
373 r = tclass_dup(tclass, &tmp);
374 if (r < 0)
375 return log_oom();
376
377 r = tclass_add(link, tmp);
378 if (r < 0)
379 return log_link_warning_errno(link, r, "Failed to store TClass: %m");
380
381 existing = TAKE_PTR(tmp);
382 } else
383 existing->source = tclass->source;
384
385 log_tclass_debug(existing, link, "Requesting");
386 r = link_queue_request_safe(link, REQUEST_TYPE_TC_CLASS,
387 existing, NULL,
388 tclass_hash_func,
389 tclass_compare_func,
390 tclass_process_request,
391 &link->tc_messages,
392 tclass_handler,
393 NULL);
394 if (r < 0)
395 return log_link_warning_errno(link, r, "Failed to request TClass: %m");
396 if (r == 0)
397 return 0;
398
399 tclass_enter_requesting(existing);
400 return 1;
401 }
402
403 int manager_rtnl_process_tclass(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) {
404 _cleanup_(tclass_freep) TClass *tmp = NULL;
405 TClass *tclass = NULL;
406 Link *link;
407 uint16_t type;
408 int ifindex, r;
409
410 assert(rtnl);
411 assert(message);
412 assert(m);
413
414 if (sd_netlink_message_is_error(message)) {
415 r = sd_netlink_message_get_errno(message);
416 if (r < 0)
417 log_message_warning_errno(message, r, "rtnl: failed to receive TClass message, ignoring");
418
419 return 0;
420 }
421
422 r = sd_netlink_message_get_type(message, &type);
423 if (r < 0) {
424 log_warning_errno(r, "rtnl: could not get message type, ignoring: %m");
425 return 0;
426 } else if (!IN_SET(type, RTM_NEWTCLASS, RTM_DELTCLASS)) {
427 log_warning("rtnl: received unexpected message type %u when processing TClass, ignoring.", type);
428 return 0;
429 }
430
431 r = sd_rtnl_message_traffic_control_get_ifindex(message, &ifindex);
432 if (r < 0) {
433 log_warning_errno(r, "rtnl: could not get ifindex from message, ignoring: %m");
434 return 0;
435 } else if (ifindex <= 0) {
436 log_warning("rtnl: received TClass message with invalid ifindex %d, ignoring.", ifindex);
437 return 0;
438 }
439
440 if (link_get_by_index(m, ifindex, &link) < 0) {
441 if (!m->enumerating)
442 log_warning("rtnl: received TClass for link '%d' we don't know about, ignoring.", ifindex);
443 return 0;
444 }
445
446 r = tclass_new(_TCLASS_KIND_INVALID, &tmp);
447 if (r < 0)
448 return log_oom();
449
450 r = sd_rtnl_message_traffic_control_get_handle(message, &tmp->classid);
451 if (r < 0) {
452 log_link_warning_errno(link, r, "rtnl: received TClass message without handle, ignoring: %m");
453 return 0;
454 }
455
456 r = sd_rtnl_message_traffic_control_get_parent(message, &tmp->parent);
457 if (r < 0) {
458 log_link_warning_errno(link, r, "rtnl: received TClass message without parent, ignoring: %m");
459 return 0;
460 }
461
462 r = sd_netlink_message_read_string_strdup(message, TCA_KIND, &tmp->tca_kind);
463 if (r < 0) {
464 log_link_warning_errno(link, r, "rtnl: received TClass message without kind, ignoring: %m");
465 return 0;
466 }
467
468 (void) tclass_get(link, tmp, &tclass);
469
470 switch (type) {
471 case RTM_NEWTCLASS:
472 if (tclass) {
473 tclass_enter_configured(tclass);
474 log_tclass_debug(tclass, link, "Received remembered");
475 } else {
476 tclass_enter_configured(tmp);
477 log_tclass_debug(tmp, link, "Received new");
478
479 r = tclass_add(link, tmp);
480 if (r < 0) {
481 log_link_warning_errno(link, r, "Failed to remember TClass, ignoring: %m");
482 return 0;
483 }
484
485 tclass = TAKE_PTR(tmp);
486 }
487
488 break;
489
490 case RTM_DELTCLASS:
491 if (tclass)
492 (void) tclass_drop(tclass);
493 else
494 log_tclass_debug(tmp, link, "Kernel removed unknown");
495
496 break;
497
498 default:
499 assert_not_reached();
500 }
501
502 return 1;
503 }
504
505 int link_enumerate_tclass(Link *link, uint32_t parent) {
506 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
507 int r;
508
509 assert(link);
510 assert(link->manager);
511 assert(link->manager->rtnl);
512
513 r = sd_rtnl_message_new_traffic_control(link->manager->rtnl, &req, RTM_GETTCLASS, link->ifindex, 0, parent);
514 if (r < 0)
515 return r;
516
517 return manager_enumerate_internal(link->manager, link->manager->rtnl, req, manager_rtnl_process_tclass);
518 }
519
520 static int tclass_section_verify(TClass *tclass) {
521 int r;
522
523 assert(tclass);
524
525 if (section_is_invalid(tclass->section))
526 return -EINVAL;
527
528 if (TCLASS_VTABLE(tclass)->verify) {
529 r = TCLASS_VTABLE(tclass)->verify(tclass);
530 if (r < 0)
531 return r;
532 }
533
534 return 0;
535 }
536
537 void network_drop_invalid_tclass(Network *network) {
538 TClass *tclass;
539
540 assert(network);
541
542 HASHMAP_FOREACH(tclass, network->tclasses_by_section)
543 if (tclass_section_verify(tclass) < 0)
544 tclass_free(tclass);
545 }
546
547 int config_parse_tclass_parent(
548 const char *unit,
549 const char *filename,
550 unsigned line,
551 const char *section,
552 unsigned section_line,
553 const char *lvalue,
554 int ltype,
555 const char *rvalue,
556 void *data,
557 void *userdata) {
558
559 _cleanup_(tclass_free_or_set_invalidp) TClass *tclass = NULL;
560 Network *network = ASSERT_PTR(data);
561 int r;
562
563 assert(filename);
564 assert(lvalue);
565 assert(rvalue);
566
567 r = tclass_new_static(ltype, network, filename, section_line, &tclass);
568 if (r == -ENOMEM)
569 return log_oom();
570 if (r < 0) {
571 log_syntax(unit, LOG_WARNING, filename, line, r,
572 "Failed to create traffic control class, ignoring assignment: %m");
573 return 0;
574 }
575
576 if (streq(rvalue, "root"))
577 tclass->parent = TC_H_ROOT;
578 else {
579 r = parse_handle(rvalue, &tclass->parent);
580 if (r < 0) {
581 log_syntax(unit, LOG_WARNING, filename, line, r,
582 "Failed to parse 'Parent=', ignoring assignment: %s",
583 rvalue);
584 return 0;
585 }
586 }
587
588 TAKE_PTR(tclass);
589
590 return 0;
591 }
592
593 int config_parse_tclass_classid(
594 const char *unit,
595 const char *filename,
596 unsigned line,
597 const char *section,
598 unsigned section_line,
599 const char *lvalue,
600 int ltype,
601 const char *rvalue,
602 void *data,
603 void *userdata) {
604
605 _cleanup_(tclass_free_or_set_invalidp) TClass *tclass = NULL;
606 Network *network = ASSERT_PTR(data);
607 int r;
608
609 assert(filename);
610 assert(lvalue);
611 assert(rvalue);
612
613 r = tclass_new_static(ltype, network, filename, section_line, &tclass);
614 if (r == -ENOMEM)
615 return log_oom();
616 if (r < 0) {
617 log_syntax(unit, LOG_WARNING, filename, line, r,
618 "Failed to create traffic control class, ignoring assignment: %m");
619 return 0;
620 }
621
622 if (isempty(rvalue)) {
623 tclass->classid = TC_H_UNSPEC;
624 TAKE_PTR(tclass);
625 return 0;
626 }
627
628 r = parse_handle(rvalue, &tclass->classid);
629 if (r < 0) {
630 log_syntax(unit, LOG_WARNING, filename, line, r,
631 "Failed to parse 'ClassId=', ignoring assignment: %s",
632 rvalue);
633 return 0;
634 }
635
636 TAKE_PTR(tclass);
637
638 return 0;
639 }