1 /* SPDX-License-Identifier: LGPL-2.1-or-later
2  * Copyright © 2019 VMware, Inc. */
3 
4 #include <linux/pkt_sched.h>
5 #include <math.h>
6 
7 #include "alloc-util.h"
8 #include "conf-parser.h"
9 #include "netem.h"
10 #include "netlink-util.h"
11 #include "networkd-manager.h"
12 #include "parse-util.h"
13 #include "qdisc.h"
14 #include "string-util.h"
15 #include "strv.h"
16 #include "tc-util.h"
17 
token_bucket_filter_fill_message(Link * link,QDisc * qdisc,sd_netlink_message * req)18 static int token_bucket_filter_fill_message(Link *link, QDisc *qdisc, sd_netlink_message *req) {
19         uint32_t rtab[256], ptab[256];
20         TokenBucketFilter *tbf;
21         int r;
22 
23         assert(link);
24         assert(qdisc);
25         assert(req);
26 
27         assert_se(tbf = TBF(qdisc));
28 
29         struct tc_tbf_qopt opt = {
30                 .rate.rate = tbf->rate >= (1ULL << 32) ? ~0U : tbf->rate,
31                 .peakrate.rate = tbf->peak_rate >= (1ULL << 32) ? ~0U : tbf->peak_rate,
32                 .rate.mpu = tbf->mpu,
33         };
34 
35         if (tbf->limit > 0)
36                 opt.limit = tbf->limit;
37         else {
38                 double lim, lim2;
39 
40                 lim = tbf->rate * (double) tbf->latency / USEC_PER_SEC + tbf->burst;
41                 if (tbf->peak_rate > 0) {
42                         lim2 = tbf->peak_rate * (double) tbf->latency / USEC_PER_SEC + tbf->mtu;
43                         lim = MIN(lim, lim2);
44                 }
45                 opt.limit = lim;
46         }
47 
48         r = tc_fill_ratespec_and_table(&opt.rate, rtab, tbf->mtu);
49         if (r < 0)
50                 return log_link_debug_errno(link, r, "Failed to calculate ratespec: %m");
51 
52         r = tc_transmit_time(opt.rate.rate, tbf->burst, &opt.buffer);
53         if (r < 0)
54                 return log_link_debug_errno(link, r, "Failed to calculate buffer size: %m");
55 
56         if (opt.peakrate.rate > 0) {
57                 opt.peakrate.mpu = tbf->mpu;
58 
59                 r = tc_fill_ratespec_and_table(&opt.peakrate, ptab, tbf->mtu);
60                 if (r < 0)
61                         return log_link_debug_errno(link, r, "Failed to calculate ratespec: %m");
62 
63                 r = tc_transmit_time(opt.peakrate.rate, tbf->mtu, &opt.mtu);
64                 if (r < 0)
65                         return log_link_debug_errno(link, r, "Failed to calculate mtu size: %m");
66         }
67 
68         r = sd_netlink_message_open_container_union(req, TCA_OPTIONS, "tbf");
69         if (r < 0)
70                 return r;
71 
72         r = sd_netlink_message_append_data(req, TCA_TBF_PARMS, &opt, sizeof(opt));
73         if (r < 0)
74                 return r;
75 
76         r = sd_netlink_message_append_data(req, TCA_TBF_BURST, &tbf->burst, sizeof(tbf->burst));
77         if (r < 0)
78                 return r;
79 
80         if (tbf->rate >= (1ULL << 32)) {
81                 r = sd_netlink_message_append_u64(req, TCA_TBF_RATE64, tbf->rate);
82                 if (r < 0)
83                         return r;
84         }
85 
86         r = sd_netlink_message_append_data(req, TCA_TBF_RTAB, rtab, sizeof(rtab));
87         if (r < 0)
88                 return r;
89 
90         if (opt.peakrate.rate > 0) {
91                 if (tbf->peak_rate >= (1ULL << 32)) {
92                         r = sd_netlink_message_append_u64(req, TCA_TBF_PRATE64, tbf->peak_rate);
93                         if (r < 0)
94                                 return r;
95                 }
96 
97                 r = sd_netlink_message_append_u32(req, TCA_TBF_PBURST, tbf->mtu);
98                 if (r < 0)
99                         return r;
100 
101                 r = sd_netlink_message_append_data(req, TCA_TBF_PTAB, ptab, sizeof(ptab));
102                 if (r < 0)
103                         return r;
104         }
105 
106         r = sd_netlink_message_close_container(req);
107         if (r < 0)
108                 return r;
109 
110         return 0;
111 }
112 
config_parse_token_bucket_filter_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)113 int config_parse_token_bucket_filter_size(
114                 const char *unit,
115                 const char *filename,
116                 unsigned line,
117                 const char *section,
118                 unsigned section_line,
119                 const char *lvalue,
120                 int ltype,
121                 const char *rvalue,
122                 void *data,
123                 void *userdata) {
124 
125         _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
126         Network *network = data;
127         TokenBucketFilter *tbf;
128         uint64_t k;
129         int r;
130 
131         assert(filename);
132         assert(lvalue);
133         assert(rvalue);
134         assert(data);
135 
136         r = qdisc_new_static(QDISC_KIND_TBF, network, filename, section_line, &qdisc);
137         if (r == -ENOMEM)
138                 return log_oom();
139         if (r < 0) {
140                 log_syntax(unit, LOG_WARNING, filename, line, r,
141                            "More than one kind of queueing discipline, ignoring assignment: %m");
142                 return 0;
143         }
144 
145         tbf = TBF(qdisc);
146 
147         if (isempty(rvalue)) {
148                 if (STR_IN_SET(lvalue, "BurstBytes", "Burst"))
149                         tbf->burst = 0;
150                 else if (STR_IN_SET(lvalue, "LimitBytes", "LimitSize"))
151                         tbf->limit = 0;
152                 else if (streq(lvalue, "MTUBytes"))
153                         tbf->mtu = 0;
154                 else if (streq(lvalue, "MPUBytes"))
155                         tbf->mpu = 0;
156                 else
157                         assert_not_reached();
158 
159                 TAKE_PTR(qdisc);
160                 return 0;
161         }
162 
163         r = parse_size(rvalue, 1024, &k);
164         if (r < 0) {
165                 log_syntax(unit, LOG_WARNING, filename, line, r,
166                            "Failed to parse '%s=', ignoring assignment: %s",
167                            lvalue, rvalue);
168                 return 0;
169         }
170 
171         if (STR_IN_SET(lvalue, "BurstBytes", "Burst"))
172                 tbf->burst = k;
173         else if (STR_IN_SET(lvalue, "LimitBytes", "LimitSize"))
174                 tbf->limit = k;
175         else if (streq(lvalue, "MPUBytes"))
176                 tbf->mpu = k;
177         else if (streq(lvalue, "MTUBytes"))
178                 tbf->mtu = k;
179         else
180                 assert_not_reached();
181 
182         TAKE_PTR(qdisc);
183 
184         return 0;
185 }
186 
config_parse_token_bucket_filter_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)187 int config_parse_token_bucket_filter_rate(
188                 const char *unit,
189                 const char *filename,
190                 unsigned line,
191                 const char *section,
192                 unsigned section_line,
193                 const char *lvalue,
194                 int ltype,
195                 const char *rvalue,
196                 void *data,
197                 void *userdata) {
198 
199         _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
200         Network *network = data;
201         TokenBucketFilter *tbf;
202         uint64_t k, *p;
203         int r;
204 
205         assert(filename);
206         assert(lvalue);
207         assert(rvalue);
208         assert(data);
209 
210         r = qdisc_new_static(QDISC_KIND_TBF, network, filename, section_line, &qdisc);
211         if (r == -ENOMEM)
212                 return log_oom();
213         if (r < 0) {
214                 log_syntax(unit, LOG_WARNING, filename, line, r,
215                            "More than one kind of queueing discipline, ignoring assignment: %m");
216                 return 0;
217         }
218 
219         tbf = TBF(qdisc);
220         if (streq(lvalue, "Rate"))
221                 p = &tbf->rate;
222         else if (streq(lvalue, "PeakRate"))
223                 p = &tbf->peak_rate;
224         else
225                 assert_not_reached();
226 
227         if (isempty(rvalue)) {
228                 *p = 0;
229 
230                 TAKE_PTR(qdisc);
231                 return 0;
232         }
233 
234         r = parse_size(rvalue, 1000, &k);
235         if (r < 0) {
236                 log_syntax(unit, LOG_WARNING, filename, line, r,
237                            "Failed to parse '%s=', ignoring assignment: %s",
238                            lvalue, rvalue);
239                 return 0;
240         }
241 
242         *p = k / 8;
243 
244         qdisc = NULL;
245 
246         return 0;
247 }
248 
config_parse_token_bucket_filter_latency(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)249 int config_parse_token_bucket_filter_latency(
250                 const char *unit,
251                 const char *filename,
252                 unsigned line,
253                 const char *section,
254                 unsigned section_line,
255                 const char *lvalue,
256                 int ltype,
257                 const char *rvalue,
258                 void *data,
259                 void *userdata) {
260 
261         _cleanup_(qdisc_free_or_set_invalidp) QDisc *qdisc = NULL;
262         Network *network = data;
263         TokenBucketFilter *tbf;
264         usec_t u;
265         int r;
266 
267         assert(filename);
268         assert(lvalue);
269         assert(rvalue);
270         assert(data);
271 
272         r = qdisc_new_static(QDISC_KIND_TBF, network, filename, section_line, &qdisc);
273         if (r == -ENOMEM)
274                 return log_oom();
275         if (r < 0) {
276                 log_syntax(unit, LOG_WARNING, filename, line, r,
277                            "More than one kind of queueing discipline, ignoring assignment: %m");
278                 return 0;
279         }
280 
281         tbf = TBF(qdisc);
282 
283         if (isempty(rvalue)) {
284                 tbf->latency = 0;
285 
286                 qdisc = NULL;
287                 return 0;
288         }
289 
290         r = parse_sec(rvalue, &u);
291         if (r < 0) {
292                 log_syntax(unit, LOG_WARNING, filename, line, r,
293                            "Failed to parse '%s=', ignoring assignment: %s",
294                            lvalue, rvalue);
295                 return 0;
296         }
297 
298         tbf->latency = u;
299 
300         qdisc = NULL;
301 
302         return 0;
303 }
304 
token_bucket_filter_verify(QDisc * qdisc)305 static int token_bucket_filter_verify(QDisc *qdisc) {
306         TokenBucketFilter *tbf = TBF(qdisc);
307 
308         if (tbf->limit > 0 && tbf->latency > 0)
309                 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
310                                          "%s: Specifying both LimitSize= and LatencySec= is not allowed. "
311                                          "Ignoring [TokenBucketFilter] section from line %u.",
312                                          qdisc->section->filename, qdisc->section->line);
313 
314         if (tbf->limit == 0 && tbf->latency == 0)
315                 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
316                                          "%s: Either LimitSize= or LatencySec= is required. "
317                                          "Ignoring [TokenBucketFilter] section from line %u.",
318                                          qdisc->section->filename, qdisc->section->line);
319 
320         if (tbf->rate == 0)
321                 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
322                                          "%s: Rate= is mandatory. "
323                                          "Ignoring [TokenBucketFilter] section from line %u.",
324                                          qdisc->section->filename, qdisc->section->line);
325 
326         if (tbf->burst == 0)
327                 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
328                                          "%s: Burst= is mandatory. "
329                                          "Ignoring [TokenBucketFilter] section from line %u.",
330                                          qdisc->section->filename, qdisc->section->line);
331 
332         if (tbf->peak_rate > 0 && tbf->mtu == 0)
333                 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
334                                          "%s: MTUBytes= is mandatory when PeakRate= is specified. "
335                                          "Ignoring [TokenBucketFilter] section from line %u.",
336                                          qdisc->section->filename, qdisc->section->line);
337 
338         return 0;
339 }
340 
341 const QDiscVTable tbf_vtable = {
342         .object_size = sizeof(TokenBucketFilter),
343         .tca_kind = "tbf",
344         .fill_message = token_bucket_filter_fill_message,
345         .verify = token_bucket_filter_verify
346 };
347