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