/src/systemd/src/network/tc/htb.c
Line | Count | Source |
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 | 452 | #define HTB_DEFAULT_RATE_TO_QUANTUM 10 |
16 | 2.25k | #define HTB_DEFAULT_MTU 1600 /* Ethernet packet length */ |
17 | | |
18 | 0 | static int hierarchy_token_bucket_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) { |
19 | 0 | HierarchyTokenBucket *htb; |
20 | 0 | int r; |
21 | |
|
22 | 0 | assert(link); |
23 | 0 | assert(qdisc); |
24 | 0 | assert(req); |
25 | |
|
26 | 0 | assert_se(htb = HTB(qdisc)); |
27 | |
|
28 | 0 | struct tc_htb_glob opt = { |
29 | 0 | .version = 3, |
30 | 0 | .rate2quantum = htb->rate_to_quantum, |
31 | 0 | .defcls = htb->default_class, |
32 | 0 | }; |
33 | |
|
34 | 0 | r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "htb"); |
35 | 0 | if (r < 0) |
36 | 0 | return r; |
37 | | |
38 | 0 | r = sd_netlink_message_append_data(req, TCA_HTB_INIT, &opt, sizeof(opt)); |
39 | 0 | if (r < 0) |
40 | 0 | return r; |
41 | | |
42 | 0 | r = sd_netlink_message_close_container(req); |
43 | 0 | if (r < 0) |
44 | 0 | return r; |
45 | 0 | return 0; |
46 | 0 | } |
47 | | |
48 | | int config_parse_htb_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 | 583 | void *userdata) { |
59 | | |
60 | 583 | _cleanup_(qdisc_unref_or_set_invalidp) QDisc *qdisc = NULL; |
61 | 583 | HierarchyTokenBucket *htb; |
62 | 583 | Network *network = ASSERT_PTR(data); |
63 | 583 | int r; |
64 | | |
65 | 583 | assert(filename); |
66 | 583 | assert(lvalue); |
67 | 583 | assert(rvalue); |
68 | | |
69 | 583 | r = qdisc_new_static(QDISC_KIND_HTB, network, filename, section_line, &qdisc); |
70 | 583 | if (r == -ENOMEM) |
71 | 0 | return log_oom(); |
72 | 583 | if (r < 0) { |
73 | 0 | log_syntax(unit, LOG_WARNING, filename, line, r, |
74 | 0 | "More than one kind of queueing discipline, ignoring assignment: %m"); |
75 | 0 | return 0; |
76 | 0 | } |
77 | | |
78 | 583 | htb = HTB(qdisc); |
79 | | |
80 | 583 | if (isempty(rvalue)) { |
81 | 194 | htb->default_class = 0; |
82 | | |
83 | 194 | TAKE_PTR(qdisc); |
84 | 194 | return 0; |
85 | 194 | } |
86 | | |
87 | 389 | r = safe_atou32_full(rvalue, 16, &htb->default_class); |
88 | 389 | if (r < 0) { |
89 | 195 | log_syntax(unit, LOG_WARNING, filename, line, r, |
90 | 195 | "Failed to parse '%s=', ignoring assignment: %s", |
91 | 195 | lvalue, rvalue); |
92 | 195 | return 0; |
93 | 195 | } |
94 | | |
95 | 194 | TAKE_PTR(qdisc); |
96 | | |
97 | 194 | return 0; |
98 | 389 | } |
99 | | |
100 | | int config_parse_htb_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 | 700 | void *userdata) { |
111 | | |
112 | 700 | _cleanup_(qdisc_unref_or_set_invalidp) QDisc *qdisc = NULL; |
113 | 700 | HierarchyTokenBucket *htb; |
114 | 700 | Network *network = ASSERT_PTR(data); |
115 | 700 | int r; |
116 | | |
117 | 700 | assert(filename); |
118 | 700 | assert(lvalue); |
119 | 700 | assert(rvalue); |
120 | | |
121 | 700 | r = qdisc_new_static(QDISC_KIND_HTB, network, filename, section_line, &qdisc); |
122 | 700 | if (r == -ENOMEM) |
123 | 0 | return log_oom(); |
124 | 700 | if (r < 0) { |
125 | 0 | log_syntax(unit, LOG_WARNING, filename, line, r, |
126 | 0 | "More than one kind of queueing discipline, ignoring assignment: %m"); |
127 | 0 | return 0; |
128 | 0 | } |
129 | | |
130 | 700 | htb = HTB(qdisc); |
131 | | |
132 | 700 | if (isempty(rvalue)) { |
133 | 194 | htb->rate_to_quantum = HTB_DEFAULT_RATE_TO_QUANTUM; |
134 | | |
135 | 194 | TAKE_PTR(qdisc); |
136 | 194 | return 0; |
137 | 194 | } |
138 | | |
139 | 506 | r = safe_atou32(rvalue, &htb->rate_to_quantum); |
140 | 506 | if (r < 0) { |
141 | 311 | log_syntax(unit, LOG_WARNING, filename, line, r, |
142 | 311 | "Failed to parse '%s=', ignoring assignment: %s", |
143 | 311 | lvalue, rvalue); |
144 | 311 | return 0; |
145 | 311 | } |
146 | | |
147 | 195 | TAKE_PTR(qdisc); |
148 | | |
149 | 195 | return 0; |
150 | 506 | } |
151 | | |
152 | 258 | static int hierarchy_token_bucket_init(QDisc *qdisc) { |
153 | 258 | HierarchyTokenBucket *htb; |
154 | | |
155 | 258 | assert(qdisc); |
156 | | |
157 | 258 | htb = HTB(qdisc); |
158 | | |
159 | 258 | htb->rate_to_quantum = HTB_DEFAULT_RATE_TO_QUANTUM; |
160 | | |
161 | 258 | return 0; |
162 | 258 | } |
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 | 0 | static int hierarchy_token_bucket_class_fill_message(Link *link, TClass *tclass, sd_netlink_message *req) { |
172 | 0 | HierarchyTokenBucketClass *htb; |
173 | 0 | uint32_t rtab[256], ctab[256]; |
174 | 0 | int r; |
175 | |
|
176 | 0 | assert(link); |
177 | 0 | assert(tclass); |
178 | 0 | assert(req); |
179 | |
|
180 | 0 | assert_se(htb = TCLASS_TO_HTB(tclass)); |
181 | |
|
182 | 0 | struct tc_htb_opt opt = { |
183 | 0 | .prio = htb->priority, |
184 | 0 | .quantum = htb->quantum, |
185 | 0 | .rate.rate = (htb->rate >= (1ULL << 32)) ? ~0U : htb->rate, |
186 | 0 | .ceil.rate = (htb->ceil_rate >= (1ULL << 32)) ? ~0U : htb->ceil_rate, |
187 | 0 | .rate.overhead = htb->overhead, |
188 | 0 | .ceil.overhead = htb->overhead, |
189 | 0 | }; |
190 | |
|
191 | 0 | r = tc_transmit_time(htb->rate, htb->buffer, &opt.buffer); |
192 | 0 | if (r < 0) |
193 | 0 | return log_link_debug_errno(link, r, "Failed to calculate buffer size: %m"); |
194 | | |
195 | 0 | r = tc_transmit_time(htb->ceil_rate, htb->ceil_buffer, &opt.cbuffer); |
196 | 0 | if (r < 0) |
197 | 0 | return log_link_debug_errno(link, r, "Failed to calculate ceil buffer size: %m"); |
198 | | |
199 | 0 | r = tc_fill_ratespec_and_table(&opt.rate, rtab, htb->mtu); |
200 | 0 | if (r < 0) |
201 | 0 | return log_link_debug_errno(link, r, "Failed to calculate rate table: %m"); |
202 | | |
203 | 0 | r = tc_fill_ratespec_and_table(&opt.ceil, ctab, htb->mtu); |
204 | 0 | if (r < 0) |
205 | 0 | return log_link_debug_errno(link, r, "Failed to calculate ceil rate table: %m"); |
206 | | |
207 | 0 | r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "htb"); |
208 | 0 | if (r < 0) |
209 | 0 | return r; |
210 | | |
211 | 0 | r = sd_netlink_message_append_data(req, TCA_HTB_PARMS, &opt, sizeof(opt)); |
212 | 0 | if (r < 0) |
213 | 0 | return r; |
214 | | |
215 | 0 | if (htb->rate >= (1ULL << 32)) { |
216 | 0 | r = sd_netlink_message_append_u64(req, TCA_HTB_RATE64, htb->rate); |
217 | 0 | if (r < 0) |
218 | 0 | return r; |
219 | 0 | } |
220 | | |
221 | 0 | if (htb->ceil_rate >= (1ULL << 32)) { |
222 | 0 | r = sd_netlink_message_append_u64(req, TCA_HTB_CEIL64, htb->ceil_rate); |
223 | 0 | if (r < 0) |
224 | 0 | return r; |
225 | 0 | } |
226 | | |
227 | 0 | r = sd_netlink_message_append_data(req, TCA_HTB_RTAB, rtab, sizeof(rtab)); |
228 | 0 | if (r < 0) |
229 | 0 | return r; |
230 | | |
231 | 0 | r = sd_netlink_message_append_data(req, TCA_HTB_CTAB, ctab, sizeof(ctab)); |
232 | 0 | if (r < 0) |
233 | 0 | return r; |
234 | | |
235 | 0 | r = sd_netlink_message_close_container(req); |
236 | 0 | if (r < 0) |
237 | 0 | return r; |
238 | | |
239 | 0 | return 0; |
240 | 0 | } |
241 | | |
242 | | int config_parse_htb_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 | 784 | void *userdata) { |
253 | | |
254 | 784 | _cleanup_(tclass_unref_or_set_invalidp) TClass *tclass = NULL; |
255 | 784 | HierarchyTokenBucketClass *htb; |
256 | 784 | Network *network = ASSERT_PTR(data); |
257 | 784 | uint32_t v; |
258 | 784 | int r; |
259 | | |
260 | 784 | assert(filename); |
261 | 784 | assert(lvalue); |
262 | 784 | assert(rvalue); |
263 | | |
264 | 784 | r = tclass_new_static(TCLASS_KIND_HTB, network, filename, section_line, &tclass); |
265 | 784 | if (r == -ENOMEM) |
266 | 0 | return log_oom(); |
267 | 784 | if (r < 0) { |
268 | 0 | log_syntax(unit, LOG_WARNING, filename, line, r, |
269 | 0 | "Failed to create traffic control class, ignoring assignment: %m"); |
270 | 0 | return 0; |
271 | 0 | } |
272 | | |
273 | 784 | htb = TCLASS_TO_HTB(tclass); |
274 | | |
275 | 784 | if (isempty(rvalue)) { |
276 | 322 | htb->priority = 0; |
277 | 322 | tclass = NULL; |
278 | 322 | return 0; |
279 | 322 | } |
280 | | |
281 | 462 | r = safe_atou32(rvalue, &v); |
282 | 462 | if (r < 0) { |
283 | 268 | log_syntax(unit, LOG_WARNING, filename, line, r, |
284 | 268 | "Failed to parse '%s=', ignoring assignment: %s", |
285 | 268 | lvalue, rvalue); |
286 | 268 | return 0; |
287 | 268 | } |
288 | | |
289 | 194 | htb->priority = v; |
290 | 194 | tclass = NULL; |
291 | | |
292 | 194 | return 0; |
293 | 462 | } |
294 | | |
295 | | int config_parse_htb_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 | 4.24k | void *userdata) { |
306 | | |
307 | 4.24k | _cleanup_(tclass_unref_or_set_invalidp) TClass *tclass = NULL; |
308 | 4.24k | HierarchyTokenBucketClass *htb; |
309 | 4.24k | Network *network = ASSERT_PTR(data); |
310 | 4.24k | uint64_t v; |
311 | 4.24k | int r; |
312 | | |
313 | 4.24k | assert(filename); |
314 | 4.24k | assert(lvalue); |
315 | 4.24k | assert(rvalue); |
316 | | |
317 | 4.24k | r = tclass_new_static(TCLASS_KIND_HTB, network, filename, section_line, &tclass); |
318 | 4.24k | if (r == -ENOMEM) |
319 | 0 | return log_oom(); |
320 | 4.24k | if (r < 0) { |
321 | 0 | log_syntax(unit, LOG_WARNING, filename, line, r, |
322 | 0 | "Failed to create traffic control class, ignoring assignment: %m"); |
323 | 0 | return 0; |
324 | 0 | } |
325 | | |
326 | 4.24k | htb = TCLASS_TO_HTB(tclass); |
327 | | |
328 | 4.24k | if (isempty(rvalue)) { |
329 | 1.42k | if (streq(lvalue, "QuantumBytes")) |
330 | 194 | htb->quantum = 0; |
331 | 1.23k | else if (streq(lvalue, "MTUBytes")) |
332 | 320 | htb->mtu = HTB_DEFAULT_MTU; |
333 | 913 | else if (streq(lvalue, "OverheadBytes")) |
334 | 194 | htb->overhead = 0; |
335 | 719 | else if (streq(lvalue, "BufferBytes")) |
336 | 196 | htb->buffer = 0; |
337 | 523 | else if (streq(lvalue, "CeilBufferBytes")) |
338 | 523 | htb->ceil_buffer = 0; |
339 | 0 | else |
340 | 0 | assert_not_reached(); |
341 | | |
342 | 1.42k | tclass = NULL; |
343 | 1.42k | return 0; |
344 | 1.42k | } |
345 | | |
346 | 2.81k | r = parse_size(rvalue, 1024, &v); |
347 | 2.81k | if (r < 0) { |
348 | 223 | log_syntax(unit, LOG_WARNING, filename, line, r, |
349 | 223 | "Failed to parse '%s=', ignoring assignment: %s", |
350 | 223 | lvalue, rvalue); |
351 | 223 | return 0; |
352 | 223 | } |
353 | 2.59k | if ((streq(lvalue, "OverheadBytes") && v > UINT16_MAX) || v > UINT32_MAX) { |
354 | 514 | log_syntax(unit, LOG_WARNING, filename, line, 0, |
355 | 514 | "Invalid '%s=', ignoring assignment: %s", |
356 | 514 | lvalue, rvalue); |
357 | 514 | return 0; |
358 | 514 | } |
359 | | |
360 | 2.07k | if (streq(lvalue, "QuantumBytes")) |
361 | 194 | htb->quantum = v; |
362 | 1.88k | else if (streq(lvalue, "OverheadBytes")) |
363 | 207 | htb->overhead = v; |
364 | 1.67k | else if (streq(lvalue, "MTUBytes")) |
365 | 797 | htb->mtu = v; |
366 | 881 | else if (streq(lvalue, "BufferBytes")) |
367 | 445 | htb->buffer = v; |
368 | 436 | else if (streq(lvalue, "CeilBufferBytes")) |
369 | 436 | htb->ceil_buffer = v; |
370 | 0 | else |
371 | 0 | assert_not_reached(); |
372 | | |
373 | 2.07k | tclass = NULL; |
374 | | |
375 | 2.07k | return 0; |
376 | 2.59k | } |
377 | | |
378 | | int config_parse_htb_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 | 2.55k | void *userdata) { |
389 | | |
390 | 2.55k | _cleanup_(tclass_unref_or_set_invalidp) TClass *tclass = NULL; |
391 | 2.55k | HierarchyTokenBucketClass *htb; |
392 | 2.55k | Network *network = ASSERT_PTR(data); |
393 | 2.55k | uint64_t *v; |
394 | 2.55k | int r; |
395 | | |
396 | 2.55k | assert(filename); |
397 | 2.55k | assert(lvalue); |
398 | 2.55k | assert(rvalue); |
399 | | |
400 | 2.55k | r = tclass_new_static(TCLASS_KIND_HTB, network, filename, section_line, &tclass); |
401 | 2.55k | if (r == -ENOMEM) |
402 | 0 | return log_oom(); |
403 | 2.55k | if (r < 0) { |
404 | 0 | log_syntax(unit, LOG_WARNING, filename, line, r, |
405 | 0 | "Failed to create traffic control class, ignoring assignment: %m"); |
406 | 0 | return 0; |
407 | 0 | } |
408 | | |
409 | 2.55k | htb = TCLASS_TO_HTB(tclass); |
410 | 2.55k | if (streq(lvalue, "Rate")) |
411 | 2.12k | v = &htb->rate; |
412 | 426 | else if (streq(lvalue, "CeilRate")) |
413 | 426 | v = &htb->ceil_rate; |
414 | 0 | else |
415 | 0 | assert_not_reached(); |
416 | | |
417 | 2.55k | if (isempty(rvalue)) { |
418 | 958 | *v = 0; |
419 | | |
420 | 958 | tclass = NULL; |
421 | 958 | return 0; |
422 | 958 | } |
423 | | |
424 | 1.59k | r = parse_size(rvalue, 1000, v); |
425 | 1.59k | if (r < 0) { |
426 | 231 | log_syntax(unit, LOG_WARNING, filename, line, r, |
427 | 231 | "Failed to parse '%s=', ignoring assignment: %s", |
428 | 231 | lvalue, rvalue); |
429 | 231 | return 0; |
430 | 231 | } |
431 | | |
432 | 1.36k | *v /= 8; |
433 | 1.36k | tclass = NULL; |
434 | | |
435 | 1.36k | return 0; |
436 | 1.59k | } |
437 | | |
438 | 1.93k | static int hierarchy_token_bucket_class_init(TClass *tclass) { |
439 | 1.93k | HierarchyTokenBucketClass *htb; |
440 | | |
441 | 1.93k | assert(tclass); |
442 | | |
443 | 1.93k | htb = TCLASS_TO_HTB(tclass); |
444 | | |
445 | 1.93k | htb->mtu = HTB_DEFAULT_MTU; |
446 | | |
447 | 1.93k | return 0; |
448 | 1.93k | } |
449 | | |
450 | 1.19k | static int hierarchy_token_bucket_class_verify(TClass *tclass) { |
451 | 1.19k | HierarchyTokenBucketClass *htb; |
452 | 1.19k | uint32_t hz; |
453 | 1.19k | int r; |
454 | | |
455 | 1.19k | assert(tclass); |
456 | | |
457 | 1.19k | htb = TCLASS_TO_HTB(tclass); |
458 | | |
459 | 1.19k | if (htb->rate == 0) |
460 | 277 | return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), |
461 | 918 | "%s: Rate= is mandatory. " |
462 | 918 | "Ignoring [HierarchyTokenBucketClass] section from line %u.", |
463 | 918 | tclass->section->filename, tclass->section->line); |
464 | | |
465 | | /* if CeilRate= setting is missing, use the same as Rate= */ |
466 | 918 | if (htb->ceil_rate == 0) |
467 | 695 | htb->ceil_rate = htb->rate; |
468 | | |
469 | 918 | r = tc_init(NULL, &hz); |
470 | 918 | if (r < 0) |
471 | 0 | return log_error_errno(r, "Failed to read /proc/net/psched: %m"); |
472 | | |
473 | | /* Kernel would never hand us 0 Hz. */ |
474 | 918 | assert(hz > 0); |
475 | 918 | if (htb->buffer == 0) |
476 | 687 | htb->buffer = htb->rate / hz + htb->mtu; |
477 | 918 | if (htb->ceil_buffer == 0) |
478 | 688 | htb->ceil_buffer = htb->ceil_rate / hz + htb->mtu; |
479 | | |
480 | 918 | return 0; |
481 | 918 | } |
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 | | }; |