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