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
tclass_new(TClassKind kind,TClass ** ret)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
tclass_new_static(TClassKind kind,Network * network,const char * filename,unsigned section_line,TClass ** ret)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
tclass_free(TClass * tclass)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
tclass_get_tca_kind(const TClass * tclass)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
tclass_hash_func(const TClass * tclass,struct siphash * state)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
tclass_compare_func(const TClass * a,const TClass * b)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
tclass_get(Link * link,const TClass * in,TClass ** ret)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
tclass_add(Link * link,TClass * tclass)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
tclass_dup(const TClass * src,TClass ** ret)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
link_find_tclass(Link * link,uint32_t classid,TClass ** ret)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->source == NETWORK_CONFIG_SOURCE_FOREIGN)
227 continue;
228
229 if (!tclass_exists(tclass))
230 continue;
231
232 if (ret)
233 *ret = tclass;
234 return 0;
235 }
236
237 return -ENOENT;
238 }
239
log_tclass_debug(TClass * tclass,Link * link,const char * str)240 static void log_tclass_debug(TClass *tclass, Link *link, const char *str) {
241 _cleanup_free_ char *state = NULL;
242
243 assert(tclass);
244 assert(str);
245
246 if (!DEBUG_LOGGING)
247 return;
248
249 (void) network_config_state_to_string_alloc(tclass->state, &state);
250
251 log_link_debug(link, "%s %s TClass (%s): classid=%"PRIx32":%"PRIx32", parent=%"PRIx32":%"PRIx32", kind=%s",
252 str, strna(network_config_source_to_string(tclass->source)), strna(state),
253 TC_H_MAJ(tclass->classid) >> 16, TC_H_MIN(tclass->classid),
254 TC_H_MAJ(tclass->parent) >> 16, TC_H_MIN(tclass->parent),
255 strna(tclass_get_tca_kind(tclass)));
256 }
257
tclass_handler(sd_netlink * rtnl,sd_netlink_message * m,Request * req,Link * link,TClass * tclass)258 static int tclass_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, TClass *tclass) {
259 int r;
260
261 assert(m);
262 assert(link);
263
264 r = sd_netlink_message_get_errno(m);
265 if (r < 0 && r != -EEXIST) {
266 log_link_message_error_errno(link, m, r, "Could not set TClass");
267 link_enter_failed(link);
268 return 1;
269 }
270
271 if (link->tc_messages == 0) {
272 log_link_debug(link, "Traffic control configured");
273 link->tc_configured = true;
274 link_check_ready(link);
275 }
276
277 return 1;
278 }
279
tclass_configure(TClass * tclass,Link * link,Request * req)280 static int tclass_configure(TClass *tclass, Link *link, Request *req) {
281 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
282 int r;
283
284 assert(tclass);
285 assert(link);
286 assert(link->manager);
287 assert(link->manager->rtnl);
288 assert(link->ifindex > 0);
289 assert(req);
290
291 log_tclass_debug(tclass, link, "Configuring");
292
293 r = sd_rtnl_message_new_traffic_control(link->manager->rtnl, &m, RTM_NEWTCLASS,
294 link->ifindex, tclass->classid, tclass->parent);
295 if (r < 0)
296 return r;
297
298 r = sd_netlink_message_append_string(m, TCA_KIND, TCLASS_VTABLE(tclass)->tca_kind);
299 if (r < 0)
300 return r;
301
302 if (TCLASS_VTABLE(tclass)->fill_message) {
303 r = TCLASS_VTABLE(tclass)->fill_message(link, tclass, m);
304 if (r < 0)
305 return r;
306 }
307
308 return request_call_netlink_async(link->manager->rtnl, m, req);
309 }
310
tclass_is_ready_to_configure(TClass * tclass,Link * link)311 static bool tclass_is_ready_to_configure(TClass *tclass, Link *link) {
312 assert(tclass);
313 assert(link);
314
315 if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
316 return false;
317
318 return link_find_qdisc(link, tclass->classid, tclass->parent, tclass_get_tca_kind(tclass), NULL) >= 0;
319 }
320
tclass_process_request(Request * req,Link * link,TClass * tclass)321 static int tclass_process_request(Request *req, Link *link, TClass *tclass) {
322 int r;
323
324 assert(req);
325 assert(link);
326 assert(tclass);
327
328 if (!tclass_is_ready_to_configure(tclass, link))
329 return 0;
330
331 r = tclass_configure(tclass, link, req);
332 if (r < 0)
333 return log_link_warning_errno(link, r, "Failed to configure TClass: %m");
334
335 tclass_enter_configuring(tclass);
336 return 1;
337 }
338
link_request_tclass(Link * link,TClass * tclass)339 int link_request_tclass(Link *link, TClass *tclass) {
340 TClass *existing;
341 int r;
342
343 assert(link);
344 assert(tclass);
345
346 if (tclass_get(link, tclass, &existing) < 0) {
347 _cleanup_(tclass_freep) TClass *tmp = NULL;
348
349 r = tclass_dup(tclass, &tmp);
350 if (r < 0)
351 return log_oom();
352
353 r = tclass_add(link, tmp);
354 if (r < 0)
355 return log_link_warning_errno(link, r, "Failed to store TClass: %m");
356
357 existing = TAKE_PTR(tmp);
358 } else
359 existing->source = tclass->source;
360
361 log_tclass_debug(existing, link, "Requesting");
362 r = link_queue_request_safe(link, REQUEST_TYPE_TC_CLASS,
363 existing, NULL,
364 tclass_hash_func,
365 tclass_compare_func,
366 tclass_process_request,
367 &link->tc_messages,
368 tclass_handler,
369 NULL);
370 if (r < 0)
371 return log_link_warning_errno(link, r, "Failed to request TClass: %m");
372 if (r == 0)
373 return 0;
374
375 tclass_enter_requesting(existing);
376 return 1;
377 }
378
manager_rtnl_process_tclass(sd_netlink * rtnl,sd_netlink_message * message,Manager * m)379 int manager_rtnl_process_tclass(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) {
380 _cleanup_(tclass_freep) TClass *tmp = NULL;
381 TClass *tclass = NULL;
382 Link *link;
383 uint16_t type;
384 int ifindex, r;
385
386 assert(rtnl);
387 assert(message);
388 assert(m);
389
390 if (sd_netlink_message_is_error(message)) {
391 r = sd_netlink_message_get_errno(message);
392 if (r < 0)
393 log_message_warning_errno(message, r, "rtnl: failed to receive TClass message, ignoring");
394
395 return 0;
396 }
397
398 r = sd_netlink_message_get_type(message, &type);
399 if (r < 0) {
400 log_warning_errno(r, "rtnl: could not get message type, ignoring: %m");
401 return 0;
402 } else if (!IN_SET(type, RTM_NEWTCLASS, RTM_DELTCLASS)) {
403 log_warning("rtnl: received unexpected message type %u when processing TClass, ignoring.", type);
404 return 0;
405 }
406
407 r = sd_rtnl_message_traffic_control_get_ifindex(message, &ifindex);
408 if (r < 0) {
409 log_warning_errno(r, "rtnl: could not get ifindex from message, ignoring: %m");
410 return 0;
411 } else if (ifindex <= 0) {
412 log_warning("rtnl: received TClass message with invalid ifindex %d, ignoring.", ifindex);
413 return 0;
414 }
415
416 if (link_get_by_index(m, ifindex, &link) < 0) {
417 if (!m->enumerating)
418 log_warning("rtnl: received TClass for link '%d' we don't know about, ignoring.", ifindex);
419 return 0;
420 }
421
422 r = tclass_new(_TCLASS_KIND_INVALID, &tmp);
423 if (r < 0)
424 return log_oom();
425
426 r = sd_rtnl_message_traffic_control_get_handle(message, &tmp->classid);
427 if (r < 0) {
428 log_link_warning_errno(link, r, "rtnl: received TClass message without handle, ignoring: %m");
429 return 0;
430 }
431
432 r = sd_rtnl_message_traffic_control_get_parent(message, &tmp->parent);
433 if (r < 0) {
434 log_link_warning_errno(link, r, "rtnl: received TClass message without parent, ignoring: %m");
435 return 0;
436 }
437
438 r = sd_netlink_message_read_string_strdup(message, TCA_KIND, &tmp->tca_kind);
439 if (r < 0) {
440 log_link_warning_errno(link, r, "rtnl: received TClass message without kind, ignoring: %m");
441 return 0;
442 }
443
444 (void) tclass_get(link, tmp, &tclass);
445
446 switch (type) {
447 case RTM_NEWTCLASS:
448 if (tclass) {
449 tclass_enter_configured(tclass);
450 log_tclass_debug(tclass, link, "Received remembered");
451 } else {
452 tclass_enter_configured(tmp);
453 log_tclass_debug(tmp, link, "Received new");
454
455 r = tclass_add(link, tmp);
456 if (r < 0) {
457 log_link_warning_errno(link, r, "Failed to remember TClass, ignoring: %m");
458 return 0;
459 }
460
461 tclass = TAKE_PTR(tmp);
462 }
463
464 break;
465
466 case RTM_DELTCLASS:
467 if (tclass) {
468 tclass_enter_removed(tclass);
469 if (tclass->state == 0) {
470 log_tclass_debug(tclass, link, "Forgetting");
471 tclass_free(tclass);
472 } else
473 log_tclass_debug(tclass, link, "Removed");
474 } else
475 log_tclass_debug(tmp, link, "Kernel removed unknown");
476
477 break;
478
479 default:
480 assert_not_reached();
481 }
482
483 return 1;
484 }
485
tclass_section_verify(TClass * tclass)486 static int tclass_section_verify(TClass *tclass) {
487 int r;
488
489 assert(tclass);
490
491 if (section_is_invalid(tclass->section))
492 return -EINVAL;
493
494 if (TCLASS_VTABLE(tclass)->verify) {
495 r = TCLASS_VTABLE(tclass)->verify(tclass);
496 if (r < 0)
497 return r;
498 }
499
500 return 0;
501 }
502
network_drop_invalid_tclass(Network * network)503 void network_drop_invalid_tclass(Network *network) {
504 TClass *tclass;
505
506 assert(network);
507
508 HASHMAP_FOREACH(tclass, network->tclasses_by_section)
509 if (tclass_section_verify(tclass) < 0)
510 tclass_free(tclass);
511 }
512
config_parse_tclass_parent(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)513 int config_parse_tclass_parent(
514 const char *unit,
515 const char *filename,
516 unsigned line,
517 const char *section,
518 unsigned section_line,
519 const char *lvalue,
520 int ltype,
521 const char *rvalue,
522 void *data,
523 void *userdata) {
524
525 _cleanup_(tclass_free_or_set_invalidp) TClass *tclass = NULL;
526 Network *network = data;
527 int r;
528
529 assert(filename);
530 assert(lvalue);
531 assert(rvalue);
532 assert(data);
533
534 r = tclass_new_static(ltype, network, filename, section_line, &tclass);
535 if (r == -ENOMEM)
536 return log_oom();
537 if (r < 0) {
538 log_syntax(unit, LOG_WARNING, filename, line, r,
539 "Failed to create traffic control class, ignoring assignment: %m");
540 return 0;
541 }
542
543 if (streq(rvalue, "root"))
544 tclass->parent = TC_H_ROOT;
545 else {
546 r = parse_handle(rvalue, &tclass->parent);
547 if (r < 0) {
548 log_syntax(unit, LOG_WARNING, filename, line, r,
549 "Failed to parse 'Parent=', ignoring assignment: %s",
550 rvalue);
551 return 0;
552 }
553 }
554
555 TAKE_PTR(tclass);
556
557 return 0;
558 }
559
config_parse_tclass_classid(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)560 int config_parse_tclass_classid(
561 const char *unit,
562 const char *filename,
563 unsigned line,
564 const char *section,
565 unsigned section_line,
566 const char *lvalue,
567 int ltype,
568 const char *rvalue,
569 void *data,
570 void *userdata) {
571
572 _cleanup_(tclass_free_or_set_invalidp) TClass *tclass = NULL;
573 Network *network = data;
574 int r;
575
576 assert(filename);
577 assert(lvalue);
578 assert(rvalue);
579 assert(data);
580
581 r = tclass_new_static(ltype, network, filename, section_line, &tclass);
582 if (r == -ENOMEM)
583 return log_oom();
584 if (r < 0) {
585 log_syntax(unit, LOG_WARNING, filename, line, r,
586 "Failed to create traffic control class, ignoring assignment: %m");
587 return 0;
588 }
589
590 if (isempty(rvalue)) {
591 tclass->classid = TC_H_UNSPEC;
592 TAKE_PTR(tclass);
593 return 0;
594 }
595
596 r = parse_handle(rvalue, &tclass->classid);
597 if (r < 0) {
598 log_syntax(unit, LOG_WARNING, filename, line, r,
599 "Failed to parse 'ClassId=', ignoring assignment: %s",
600 rvalue);
601 return 0;
602 }
603
604 TAKE_PTR(tclass);
605
606 return 0;
607 }
608