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