Coverage Report

Created: 2026-09-14 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
};