1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <linux/pkt_sched.h>
4 
5 #include "alloc-util.h"
6 #include "conf-parser.h"
7 #include "netlink-util.h"
8 #include "networkd-link.h"
9 #include "parse-util.h"
10 #include "qdisc.h"
11 #include "htb.h"
12 #include "string-util.h"
13 #include "tc-util.h"
14 
15 #define HTB_DEFAULT_RATE_TO_QUANTUM  10
16 #define HTB_DEFAULT_MTU              1600  /* Ethernet packet length */
17 
hierarchy_token_bucket_fill_message(Link * link,QDisc * qdisc,sd_netlink_message * req)18 static int hierarchy_token_bucket_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) {
19         HierarchyTokenBucket *htb;
20         int r;
21 
22         assert(link);
23         assert(qdisc);
24         assert(req);
25 
26         assert_se(htb = HTB(qdisc));
27 
28         struct tc_htb_glob opt = {
29                 .version = 3,
30                 .rate2quantum = htb->rate_to_quantum,
31                 .defcls = htb->default_class,
32         };
33 
34         r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "htb");
35         if (r < 0)
36                 return r;
37 
38         r = sd_netlink_message_append_data(req, TCA_HTB_INIT, &opt, sizeof(opt));
39         if (r < 0)
40                 return r;
41 
42         r = sd_netlink_message_close_container(req);
43         if (r < 0)
44                 return r;
45         return 0;
46 }
47 
config_parse_hierarchy_token_bucket_default_class(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)48 int config_parse_hierarchy_token_bucket_default_class(
49                 const char *unit,
50                 const char *filename,
51                 unsigned line,
52                 const char *section,
53                 unsigned section_line,
54                 const char *lvalue,
55                 int ltype,
56                 const char *rvalue,
57                 void *data,
58                 void *userdata) {
59 
60         _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
61         HierarchyTokenBucket *htb;
62         Network *network = data;
63         int r;
64 
65         assert(filename);
66         assert(lvalue);
67         assert(rvalue);
68         assert(data);
69 
70         r = qdisc_new_static(QDISC_KIND_HTB, network, filename, section_line, &qdisc);
71         if (r == -ENOMEM)
72                 return log_oom();
73         if (r < 0) {
74                 log_syntax(unit, LOG_WARNING, filename, line, r,
75                            "More than one kind of queueing discipline, ignoring assignment: %m");
76                 return 0;
77         }
78 
79         htb = HTB(qdisc);
80 
81         if (isempty(rvalue)) {
82                 htb->default_class = 0;
83 
84                 TAKE_PTR(qdisc);
85                 return 0;
86         }
87 
88         r = safe_atou32_full(rvalue, 16, &htb->default_class);
89         if (r < 0) {
90                 log_syntax(unit, LOG_WARNING, filename, line, r,
91                            "Failed to parse '%s=', ignoring assignment: %s",
92                            lvalue, rvalue);
93                 return 0;
94         }
95 
96         TAKE_PTR(qdisc);
97 
98         return 0;
99 }
100 
config_parse_hierarchy_token_bucket_u32(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)101 int config_parse_hierarchy_token_bucket_u32(
102                 const char *unit,
103                 const char *filename,
104                 unsigned line,
105                 const char *section,
106                 unsigned section_line,
107                 const char *lvalue,
108                 int ltype,
109                 const char *rvalue,
110                 void *data,
111                 void *userdata) {
112 
113         _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
114         HierarchyTokenBucket *htb;
115         Network *network = data;
116         int r;
117 
118         assert(filename);
119         assert(lvalue);
120         assert(rvalue);
121         assert(data);
122 
123         r = qdisc_new_static(QDISC_KIND_HTB, network, filename, section_line, &qdisc);
124         if (r == -ENOMEM)
125                 return log_oom();
126         if (r < 0) {
127                 log_syntax(unit, LOG_WARNING, filename, line, r,
128                            "More than one kind of queueing discipline, ignoring assignment: %m");
129                 return 0;
130         }
131 
132         htb = HTB(qdisc);
133 
134         if (isempty(rvalue)) {
135                 htb->rate_to_quantum = HTB_DEFAULT_RATE_TO_QUANTUM;
136 
137                 TAKE_PTR(qdisc);
138                 return 0;
139         }
140 
141         r = safe_atou32(rvalue, &htb->rate_to_quantum);
142         if (r < 0) {
143                 log_syntax(unit, LOG_WARNING, filename, line, r,
144                            "Failed to parse '%s=', ignoring assignment: %s",
145                            lvalue, rvalue);
146                 return 0;
147         }
148 
149         TAKE_PTR(qdisc);
150 
151         return 0;
152 }
153 
hierarchy_token_bucket_init(QDisc * qdisc)154 static int hierarchy_token_bucket_init(QDisc *qdisc) {
155         HierarchyTokenBucket *htb;
156 
157         assert(qdisc);
158 
159         htb = HTB(qdisc);
160 
161         htb->rate_to_quantum = HTB_DEFAULT_RATE_TO_QUANTUM;
162 
163         return 0;
164 }
165 
166 const QDiscVTable htb_vtable = {
167         .object_size = sizeof(HierarchyTokenBucket),
168         .tca_kind = "htb",
169         .fill_message = hierarchy_token_bucket_fill_message,
170         .init = hierarchy_token_bucket_init,
171 };
172 
hierarchy_token_bucket_class_fill_message(Link * link,TClass * tclass,sd_netlink_message * req)173 static int hierarchy_token_bucket_class_fill_message(Link *link, TClass *tclass, sd_netlink_message *req) {
174         HierarchyTokenBucketClass *htb;
175         uint32_t rtab[256], ctab[256];
176         int r;
177 
178         assert(link);
179         assert(tclass);
180         assert(req);
181 
182         assert_se(htb = TCLASS_TO_HTB(tclass));
183 
184         struct tc_htb_opt opt = {
185                 .prio = htb->priority,
186                 .quantum = htb->quantum,
187                 .rate.rate = (htb->rate >= (1ULL << 32)) ? ~0U : htb->rate,
188                 .ceil.rate = (htb->ceil_rate >= (1ULL << 32)) ? ~0U : htb->ceil_rate,
189                 .rate.overhead = htb->overhead,
190                 .ceil.overhead = htb->overhead,
191         };
192 
193         r = tc_transmit_time(htb->rate, htb->buffer, &opt.buffer);
194         if (r < 0)
195                 return log_link_debug_errno(link, r, "Failed to calculate buffer size: %m");
196 
197         r = tc_transmit_time(htb->ceil_rate, htb->ceil_buffer, &opt.cbuffer);
198         if (r < 0)
199                 return log_link_debug_errno(link, r, "Failed to calculate ceil buffer size: %m");
200 
201         r = tc_fill_ratespec_and_table(&opt.rate, rtab, htb->mtu);
202         if (r < 0)
203                 return log_link_debug_errno(link, r, "Failed to calculate rate table: %m");
204 
205         r = tc_fill_ratespec_and_table(&opt.ceil, ctab, htb->mtu);
206         if (r < 0)
207                 return log_link_debug_errno(link, r, "Failed to calculate ceil rate table: %m");
208 
209         r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "htb");
210         if (r < 0)
211                 return r;
212 
213         r = sd_netlink_message_append_data(req, TCA_HTB_PARMS, &opt, sizeof(opt));
214         if (r < 0)
215                 return r;
216 
217         if (htb->rate >= (1ULL << 32)) {
218                 r = sd_netlink_message_append_u64(req, TCA_HTB_RATE64, htb->rate);
219                 if (r < 0)
220                         return r;
221         }
222 
223         if (htb->ceil_rate >= (1ULL << 32)) {
224                 r = sd_netlink_message_append_u64(req, TCA_HTB_CEIL64, htb->ceil_rate);
225                 if (r < 0)
226                         return r;
227         }
228 
229         r = sd_netlink_message_append_data(req, TCA_HTB_RTAB, rtab, sizeof(rtab));
230         if (r < 0)
231                 return r;
232 
233         r = sd_netlink_message_append_data(req, TCA_HTB_CTAB, ctab, sizeof(ctab));
234         if (r < 0)
235                 return r;
236 
237         r = sd_netlink_message_close_container(req);
238         if (r < 0)
239                 return r;
240 
241         return 0;
242 }
243 
config_parse_hierarchy_token_bucket_class_u32(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)244 int config_parse_hierarchy_token_bucket_class_u32(
245                 const char *unit,
246                 const char *filename,
247                 unsigned line,
248                 const char *section,
249                 unsigned section_line,
250                 const char *lvalue,
251                 int ltype,
252                 const char *rvalue,
253                 void *data,
254                 void *userdata) {
255 
256         _cleanup_(tclass_free_or_set_invalidp) TClass *tclass = NULL;
257         HierarchyTokenBucketClass *htb;
258         Network *network = data;
259         uint32_t v;
260         int r;
261 
262         assert(filename);
263         assert(lvalue);
264         assert(rvalue);
265         assert(data);
266 
267         r = tclass_new_static(TCLASS_KIND_HTB, network, filename, section_line, &tclass);
268         if (r == -ENOMEM)
269                 return log_oom();
270         if (r < 0) {
271                 log_syntax(unit, LOG_WARNING, filename, line, r,
272                            "Failed to create traffic control class, ignoring assignment: %m");
273                 return 0;
274         }
275 
276         htb = TCLASS_TO_HTB(tclass);
277 
278         if (isempty(rvalue)) {
279                 htb->priority = 0;
280                 tclass = NULL;
281                 return 0;
282         }
283 
284         r = safe_atou32(rvalue, &v);
285         if (r < 0) {
286                 log_syntax(unit, LOG_WARNING, filename, line, r,
287                            "Failed to parse '%s=', ignoring assignment: %s",
288                            lvalue, rvalue);
289                 return 0;
290         }
291 
292         htb->priority = v;
293         tclass = NULL;
294 
295         return 0;
296 }
297 
config_parse_hierarchy_token_bucket_class_size(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)298 int config_parse_hierarchy_token_bucket_class_size(
299                 const char *unit,
300                 const char *filename,
301                 unsigned line,
302                 const char *section,
303                 unsigned section_line,
304                 const char *lvalue,
305                 int ltype,
306                 const char *rvalue,
307                 void *data,
308                 void *userdata) {
309 
310         _cleanup_(tclass_free_or_set_invalidp) TClass *tclass = NULL;
311         HierarchyTokenBucketClass *htb;
312         Network *network = data;
313         uint64_t v;
314         int r;
315 
316         assert(filename);
317         assert(lvalue);
318         assert(rvalue);
319         assert(data);
320 
321         r = tclass_new_static(TCLASS_KIND_HTB, network, filename, section_line, &tclass);
322         if (r == -ENOMEM)
323                 return log_oom();
324         if (r < 0) {
325                 log_syntax(unit, LOG_WARNING, filename, line, r,
326                            "Failed to create traffic control class, ignoring assignment: %m");
327                 return 0;
328         }
329 
330         htb = TCLASS_TO_HTB(tclass);
331 
332         if (isempty(rvalue)) {
333                 if (streq(lvalue, "QuantumBytes"))
334                         htb->quantum = 0;
335                 else if (streq(lvalue, "MTUBytes"))
336                         htb->mtu = HTB_DEFAULT_MTU;
337                 else if (streq(lvalue, "OverheadBytes"))
338                         htb->overhead = 0;
339                 else if (streq(lvalue, "BufferBytes"))
340                         htb->buffer = 0;
341                 else if (streq(lvalue, "CeilBufferBytes"))
342                         htb->ceil_buffer = 0;
343                 else
344                         assert_not_reached();
345 
346                 tclass = NULL;
347                 return 0;
348         }
349 
350         r = parse_size(rvalue, 1024, &v);
351         if (r < 0) {
352                 log_syntax(unit, LOG_WARNING, filename, line, r,
353                            "Failed to parse '%s=', ignoring assignment: %s",
354                            lvalue, rvalue);
355                 return 0;
356         }
357         if ((streq(lvalue, "OverheadBytes") && v > UINT16_MAX) || v > UINT32_MAX) {
358                 log_syntax(unit, LOG_WARNING, filename, line, 0,
359                            "Invalid '%s=', ignoring assignment: %s",
360                            lvalue, rvalue);
361                 return 0;
362         }
363 
364         if (streq(lvalue, "QuantumBytes"))
365                 htb->quantum = v;
366         else if (streq(lvalue, "OverheadBytes"))
367                 htb->overhead = v;
368         else if (streq(lvalue, "MTUBytes"))
369                 htb->mtu = v;
370         else if (streq(lvalue, "BufferBytes"))
371                 htb->buffer = v;
372         else if (streq(lvalue, "CeilBufferBytes"))
373                 htb->ceil_buffer = v;
374         else
375                 assert_not_reached();
376 
377         tclass = NULL;
378 
379         return 0;
380 }
381 
config_parse_hierarchy_token_bucket_class_rate(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)382 int config_parse_hierarchy_token_bucket_class_rate(
383                 const char *unit,
384                 const char *filename,
385                 unsigned line,
386                 const char *section,
387                 unsigned section_line,
388                 const char *lvalue,
389                 int ltype,
390                 const char *rvalue,
391                 void *data,
392                 void *userdata) {
393 
394         _cleanup_(tclass_free_or_set_invalidp) TClass *tclass = NULL;
395         HierarchyTokenBucketClass *htb;
396         Network *network = data;
397         uint64_t *v;
398         int r;
399 
400         assert(filename);
401         assert(lvalue);
402         assert(rvalue);
403         assert(data);
404 
405         r = tclass_new_static(TCLASS_KIND_HTB, network, filename, section_line, &tclass);
406         if (r == -ENOMEM)
407                 return log_oom();
408         if (r < 0) {
409                 log_syntax(unit, LOG_WARNING, filename, line, r,
410                            "Failed to create traffic control class, ignoring assignment: %m");
411                 return 0;
412         }
413 
414         htb = TCLASS_TO_HTB(tclass);
415         if (streq(lvalue, "Rate"))
416                 v = &htb->rate;
417         else if (streq(lvalue, "CeilRate"))
418                 v = &htb->ceil_rate;
419         else
420                 assert_not_reached();
421 
422         if (isempty(rvalue)) {
423                 *v = 0;
424 
425                 tclass = NULL;
426                 return 0;
427         }
428 
429         r = parse_size(rvalue, 1000, v);
430         if (r < 0) {
431                 log_syntax(unit, LOG_WARNING, filename, line, r,
432                            "Failed to parse '%s=', ignoring assignment: %s",
433                            lvalue, rvalue);
434                 return 0;
435         }
436 
437         *v /= 8;
438         tclass = NULL;
439 
440         return 0;
441 }
442 
hierarchy_token_bucket_class_init(TClass * tclass)443 static int hierarchy_token_bucket_class_init(TClass *tclass) {
444         HierarchyTokenBucketClass *htb;
445 
446         assert(tclass);
447 
448         htb = TCLASS_TO_HTB(tclass);
449 
450         htb->mtu = HTB_DEFAULT_MTU;
451 
452         return 0;
453 }
454 
hierarchy_token_bucket_class_verify(TClass * tclass)455 static int hierarchy_token_bucket_class_verify(TClass *tclass) {
456         HierarchyTokenBucketClass *htb;
457         uint32_t hz;
458         int r;
459 
460         assert(tclass);
461 
462         htb = TCLASS_TO_HTB(tclass);
463 
464         if (htb->rate == 0)
465                 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
466                                          "%s: Rate= is mandatory. "
467                                          "Ignoring [HierarchyTokenBucketClass] section from line %u.",
468                                          tclass->section->filename, tclass->section->line);
469 
470         /* if CeilRate= setting is missing, use the same as Rate= */
471         if (htb->ceil_rate == 0)
472                 htb->ceil_rate = htb->rate;
473 
474         r = tc_init(NULL, &hz);
475         if (r < 0)
476                 return log_error_errno(r, "Failed to read /proc/net/psched: %m");
477 
478         if (htb->buffer == 0)
479                 htb->buffer = htb->rate / hz + htb->mtu;
480         if (htb->ceil_buffer == 0)
481                 htb->ceil_buffer = htb->ceil_rate / hz + htb->mtu;
482 
483         return 0;
484 }
485 
486 const TClassVTable htb_tclass_vtable = {
487         .object_size = sizeof(HierarchyTokenBucketClass),
488         .tca_kind = "htb",
489         .fill_message = hierarchy_token_bucket_class_fill_message,
490         .init = hierarchy_token_bucket_class_init,
491         .verify = hierarchy_token_bucket_class_verify,
492 };
493