]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include "alloc-util.h" | |
4 | #include "netlink-util.h" | |
5 | #include "networkd-route.h" | |
6 | #include "networkd-route-metric.h" | |
7 | #include "parse-util.h" | |
8 | #include "string-util.h" | |
9 | ||
10 | void route_metric_done(RouteMetric *metric) { | |
11 | assert(metric); | |
12 | ||
13 | free(metric->metrics); | |
14 | free(metric->metrics_set); | |
15 | free(metric->tcp_congestion_control_algo); | |
16 | } | |
17 | ||
18 | int route_metric_copy(const RouteMetric *src, RouteMetric *dest) { | |
19 | assert(src); | |
20 | assert(dest); | |
21 | ||
22 | dest->n_metrics = src->n_metrics; | |
23 | if (src->n_metrics > 0) { | |
24 | assert(src->n_metrics != 1); | |
25 | ||
26 | dest->metrics = newdup(uint32_t, src->metrics, src->n_metrics); | |
27 | if (!dest->metrics) | |
28 | return -ENOMEM; | |
29 | } else | |
30 | dest->metrics = NULL; | |
31 | ||
32 | dest->n_metrics_set = src->n_metrics_set; | |
33 | if (src->n_metrics_set > 0) { | |
34 | assert(src->n_metrics_set != 1); | |
35 | ||
36 | dest->metrics_set = newdup(bool, src->metrics_set, src->n_metrics_set); | |
37 | if (!dest->metrics_set) | |
38 | return -ENOMEM; | |
39 | } else | |
40 | dest->metrics_set = NULL; | |
41 | ||
42 | return strdup_to(&dest->tcp_congestion_control_algo, src->tcp_congestion_control_algo); | |
43 | } | |
44 | ||
45 | void route_metric_hash_func(const RouteMetric *metric, struct siphash *state) { | |
46 | assert(metric); | |
47 | ||
48 | siphash24_compress_typesafe(metric->n_metrics, state); | |
49 | siphash24_compress_safe(metric->metrics, sizeof(uint32_t) * metric->n_metrics, state); | |
50 | siphash24_compress_string(metric->tcp_congestion_control_algo, state); | |
51 | } | |
52 | ||
53 | int route_metric_compare_func(const RouteMetric *a, const RouteMetric *b) { | |
54 | int r; | |
55 | ||
56 | assert(a); | |
57 | assert(b); | |
58 | ||
59 | r = memcmp_nn(a->metrics, a->n_metrics * sizeof(uint32_t), b->metrics, b->n_metrics * sizeof(uint32_t)); | |
60 | if (r != 0) | |
61 | return r; | |
62 | ||
63 | return strcmp_ptr(a->tcp_congestion_control_algo, b->tcp_congestion_control_algo); | |
64 | } | |
65 | ||
66 | bool route_metric_can_update(const RouteMetric *a, const RouteMetric *b, bool expiration_by_kernel) { | |
67 | assert(a); | |
68 | assert(b); | |
69 | ||
70 | /* If the kernel has expiration timer for the route, then only MTU can be updated. */ | |
71 | ||
72 | if (!expiration_by_kernel) | |
73 | return route_metric_compare_func(a, b) == 0; | |
74 | ||
75 | if (a->n_metrics != b->n_metrics) | |
76 | return false; | |
77 | ||
78 | for (size_t i = 1; i < a->n_metrics; i++) { | |
79 | if (i != RTAX_MTU) | |
80 | continue; | |
81 | if (a->metrics[i] != b->metrics[i]) | |
82 | return false; | |
83 | } | |
84 | ||
85 | return streq_ptr(a->tcp_congestion_control_algo, b->tcp_congestion_control_algo); | |
86 | } | |
87 | ||
88 | int route_metric_set_full(RouteMetric *metric, uint16_t attr, uint32_t value, bool force) { | |
89 | assert(metric); | |
90 | ||
91 | if (force) { | |
92 | if (!GREEDY_REALLOC0(metric->metrics_set, attr + 1)) | |
93 | return -ENOMEM; | |
94 | ||
95 | metric->metrics_set[attr] = true; | |
96 | metric->n_metrics_set = MAX(metric->n_metrics_set, (size_t) (attr + 1)); | |
97 | } else { | |
98 | /* Do not override the values specified in conf parsers. */ | |
99 | if (metric->n_metrics_set > attr && metric->metrics_set[attr]) | |
100 | return 0; | |
101 | } | |
102 | ||
103 | if (value != 0) { | |
104 | if (!GREEDY_REALLOC0(metric->metrics, attr + 1)) | |
105 | return -ENOMEM; | |
106 | ||
107 | metric->metrics[attr] = value; | |
108 | metric->n_metrics = MAX(metric->n_metrics, (size_t) (attr + 1)); | |
109 | return 0; | |
110 | } | |
111 | ||
112 | if (metric->n_metrics <= attr) | |
113 | return 0; | |
114 | ||
115 | metric->metrics[attr] = 0; | |
116 | ||
117 | for (size_t i = metric->n_metrics; i > 0; i--) | |
118 | if (metric->metrics[i-1] != 0) { | |
119 | metric->n_metrics = i; | |
120 | return 0; | |
121 | } | |
122 | ||
123 | metric->n_metrics = 0; | |
124 | return 0; | |
125 | } | |
126 | ||
127 | static void route_metric_unset(RouteMetric *metric, uint16_t attr) { | |
128 | assert(metric); | |
129 | ||
130 | if (metric->n_metrics_set > attr) | |
131 | metric->metrics_set[attr] = false; | |
132 | ||
133 | assert_se(route_metric_set_full(metric, attr, 0, /* force = */ false) >= 0); | |
134 | } | |
135 | ||
136 | uint32_t route_metric_get(const RouteMetric *metric, uint16_t attr) { | |
137 | assert(metric); | |
138 | ||
139 | if (metric->n_metrics <= attr) | |
140 | return 0; | |
141 | ||
142 | return metric->metrics[attr]; | |
143 | } | |
144 | ||
145 | int route_metric_set_netlink_message(const RouteMetric *metric, sd_netlink_message *m) { | |
146 | int r; | |
147 | ||
148 | assert(metric); | |
149 | assert(m); | |
150 | ||
151 | if (metric->n_metrics <= 0 && isempty(metric->tcp_congestion_control_algo)) | |
152 | return 0; | |
153 | ||
154 | r = sd_netlink_message_open_container(m, RTA_METRICS); | |
155 | if (r < 0) | |
156 | return r; | |
157 | ||
158 | for (size_t i = 0; i < metric->n_metrics; i++) { | |
159 | if (i == RTAX_CC_ALGO) | |
160 | continue; | |
161 | ||
162 | if (metric->metrics[i] == 0) | |
163 | continue; | |
164 | ||
165 | r = sd_netlink_message_append_u32(m, i, metric->metrics[i]); | |
166 | if (r < 0) | |
167 | return r; | |
168 | } | |
169 | ||
170 | if (!isempty(metric->tcp_congestion_control_algo)) { | |
171 | r = sd_netlink_message_append_string(m, RTAX_CC_ALGO, metric->tcp_congestion_control_algo); | |
172 | if (r < 0) | |
173 | return r; | |
174 | } | |
175 | ||
176 | r = sd_netlink_message_close_container(m); | |
177 | if (r < 0) | |
178 | return r; | |
179 | ||
180 | return 0; | |
181 | } | |
182 | ||
183 | int route_metric_read_netlink_message(RouteMetric *metric, sd_netlink_message *m) { | |
184 | _cleanup_free_ void *data = NULL; | |
185 | size_t len; | |
186 | int r; | |
187 | ||
188 | assert(metric); | |
189 | assert(m); | |
190 | ||
191 | r = sd_netlink_message_read_data(m, RTA_METRICS, &len, &data); | |
192 | if (r == -ENODATA) | |
193 | return 0; | |
194 | if (r < 0) | |
195 | return log_warning_errno(r, "rtnl: Could not read RTA_METRICS attribute, ignoring: %m"); | |
196 | ||
197 | for (struct rtattr *rta = data; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { | |
198 | size_t rta_type = RTA_TYPE(rta); | |
199 | ||
200 | if (rta_type == RTAX_CC_ALGO) { | |
201 | char *p = memdup_suffix0(RTA_DATA(rta), RTA_PAYLOAD(rta)); | |
202 | if (!p) | |
203 | return log_oom(); | |
204 | ||
205 | free_and_replace(metric->tcp_congestion_control_algo, p); | |
206 | ||
207 | } else { | |
208 | if (RTA_PAYLOAD(rta) != sizeof(uint32_t)) | |
209 | continue; | |
210 | ||
211 | r = route_metric_set(metric, rta_type, *(uint32_t*) RTA_DATA(rta)); | |
212 | if (r < 0) | |
213 | return log_oom(); | |
214 | } | |
215 | } | |
216 | ||
217 | return 0; | |
218 | } | |
219 | ||
220 | static int config_parse_route_metric_advmss_impl( | |
221 | const char *unit, | |
222 | const char *filename, | |
223 | unsigned line, | |
224 | const char *section, | |
225 | unsigned section_line, | |
226 | const char *lvalue, | |
227 | int ltype, | |
228 | const char *rvalue, | |
229 | void *data, | |
230 | void *userdata) { | |
231 | ||
232 | uint32_t *val = ASSERT_PTR(data); | |
233 | uint64_t u; | |
234 | int r; | |
235 | ||
236 | assert(rvalue); | |
237 | ||
238 | r = parse_size(rvalue, 1024, &u); | |
239 | if (r < 0) { | |
240 | log_syntax(unit, LOG_WARNING, filename, line, r, | |
241 | "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue); | |
242 | return 0; | |
243 | } | |
244 | ||
245 | if (u == 0 || u > UINT32_MAX) { | |
246 | log_syntax(unit, LOG_WARNING, filename, line, 0, | |
247 | "Invalid %s=, ignoring assignment: %s", lvalue, rvalue); | |
248 | return 0; | |
249 | } | |
250 | ||
251 | *val = (uint32_t) u; | |
252 | return 1; | |
253 | } | |
254 | ||
255 | static int config_parse_route_metric_hop_limit_impl( | |
256 | const char *unit, | |
257 | const char *filename, | |
258 | unsigned line, | |
259 | const char *section, | |
260 | unsigned section_line, | |
261 | const char *lvalue, | |
262 | int ltype, | |
263 | const char *rvalue, | |
264 | void *data, | |
265 | void *userdata) { | |
266 | ||
267 | uint32_t k, *val = ASSERT_PTR(data); | |
268 | int r; | |
269 | ||
270 | assert(rvalue); | |
271 | ||
272 | r = safe_atou32(rvalue, &k); | |
273 | if (r < 0) { | |
274 | log_syntax(unit, LOG_WARNING, filename, line, r, | |
275 | "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue); | |
276 | return 0; | |
277 | } | |
278 | if (k == 0 || k > 255) { | |
279 | log_syntax(unit, LOG_WARNING, filename, line, 0, | |
280 | "Invalid %s=, ignoring assignment: %s", lvalue, rvalue); | |
281 | return 0; | |
282 | } | |
283 | ||
284 | *val = k; | |
285 | return 1; | |
286 | } | |
287 | ||
288 | int config_parse_tcp_window( | |
289 | const char *unit, | |
290 | const char *filename, | |
291 | unsigned line, | |
292 | const char *section, | |
293 | unsigned section_line, | |
294 | const char *lvalue, | |
295 | int ltype, | |
296 | const char *rvalue, | |
297 | void *data, | |
298 | void *userdata) { | |
299 | ||
300 | uint32_t k, *val = ASSERT_PTR(data); | |
301 | int r; | |
302 | ||
303 | assert(rvalue); | |
304 | ||
305 | r = safe_atou32(rvalue, &k); | |
306 | if (r < 0) { | |
307 | log_syntax(unit, LOG_WARNING, filename, line, r, | |
308 | "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue); | |
309 | return 0; | |
310 | } | |
311 | if (k == 0 || k >= 1024) { | |
312 | log_syntax(unit, LOG_WARNING, filename, line, 0, | |
313 | "Invalid %s=, ignoring assignment: %s", lvalue, rvalue); | |
314 | return 0; | |
315 | } | |
316 | ||
317 | *val = k; | |
318 | return 1; | |
319 | } | |
320 | ||
321 | static int config_parse_route_metric_tcp_rto_impl( | |
322 | const char *unit, | |
323 | const char *filename, | |
324 | unsigned line, | |
325 | const char *section, | |
326 | unsigned section_line, | |
327 | const char *lvalue, | |
328 | int ltype, | |
329 | const char *rvalue, | |
330 | void *data, | |
331 | void *userdata) { | |
332 | ||
333 | uint32_t *val = ASSERT_PTR(data); | |
334 | usec_t usec; | |
335 | int r; | |
336 | ||
337 | assert(rvalue); | |
338 | ||
339 | r = parse_sec(rvalue, &usec); | |
340 | if (r < 0) { | |
341 | log_syntax(unit, LOG_WARNING, filename, line, r, | |
342 | "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue); | |
343 | return 0; | |
344 | } | |
345 | ||
346 | if (!timestamp_is_set(usec) || | |
347 | DIV_ROUND_UP(usec, USEC_PER_MSEC) > UINT32_MAX) { | |
348 | log_syntax(unit, LOG_WARNING, filename, line, 0, | |
349 | "Invalid %s=, ignoring assignment: %s", lvalue, rvalue); | |
350 | return 0; | |
351 | } | |
352 | ||
353 | *val = (uint32_t) DIV_ROUND_UP(usec, USEC_PER_MSEC); | |
354 | return 1; | |
355 | } | |
356 | ||
357 | static int config_parse_route_metric_boolean_impl( | |
358 | const char *unit, | |
359 | const char *filename, | |
360 | unsigned line, | |
361 | const char *section, | |
362 | unsigned section_line, | |
363 | const char *lvalue, | |
364 | int ltype, | |
365 | const char *rvalue, | |
366 | void *data, | |
367 | void *userdata) { | |
368 | ||
369 | uint32_t *val = ASSERT_PTR(data); | |
370 | int r; | |
371 | ||
372 | assert(rvalue); | |
373 | ||
374 | r = parse_boolean(rvalue); | |
375 | if (r < 0) { | |
376 | log_syntax(unit, LOG_WARNING, filename, line, r, | |
377 | "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue); | |
378 | return 0; | |
379 | } | |
380 | ||
381 | *val = r; | |
382 | return 1; | |
383 | } | |
384 | ||
385 | #define DEFINE_CONFIG_PARSE_ROUTE_METRIC(name, parser) \ | |
386 | int config_parse_route_metric_##name( \ | |
387 | const char *unit, \ | |
388 | const char *filename, \ | |
389 | unsigned line, \ | |
390 | const char *section, \ | |
391 | unsigned section_line, \ | |
392 | const char *lvalue, \ | |
393 | int ltype, \ | |
394 | const char *rvalue, \ | |
395 | void *data, \ | |
396 | void *userdata) { \ | |
397 | \ | |
398 | Network *network = ASSERT_PTR(userdata); \ | |
399 | _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; \ | |
400 | uint16_t attr_type = ltype; \ | |
401 | int r; \ | |
402 | \ | |
403 | assert(filename); \ | |
404 | assert(section); \ | |
405 | assert(lvalue); \ | |
406 | assert(rvalue); \ | |
407 | \ | |
408 | r = route_new_static(network, filename, section_line, &route); \ | |
409 | if (r == -ENOMEM) \ | |
410 | return log_oom(); \ | |
411 | if (r < 0) { \ | |
412 | log_syntax(unit, LOG_WARNING, filename, line, r, \ | |
413 | "Failed to allocate route, ignoring assignment: %m"); \ | |
414 | return 0; \ | |
415 | } \ | |
416 | \ | |
417 | if (isempty(rvalue)) { \ | |
418 | route_metric_unset(&route->metric, attr_type); \ | |
419 | TAKE_PTR(route); \ | |
420 | return 0; \ | |
421 | } \ | |
422 | \ | |
423 | uint32_t k; \ | |
424 | r = parser(unit, filename, line, section, section_line, \ | |
425 | lvalue, /* ltype = */ 0, rvalue, \ | |
426 | &k, userdata); \ | |
427 | if (r <= 0) \ | |
428 | return r; \ | |
429 | \ | |
430 | if (route_metric_set_full( \ | |
431 | &route->metric, \ | |
432 | attr_type, \ | |
433 | k, \ | |
434 | /* force = */ true) < 0) \ | |
435 | return log_oom(); \ | |
436 | \ | |
437 | TAKE_PTR(route); \ | |
438 | return 0; \ | |
439 | } | |
440 | ||
441 | DEFINE_CONFIG_PARSE_ROUTE_METRIC(mtu, config_parse_mtu); | |
442 | DEFINE_CONFIG_PARSE_ROUTE_METRIC(advmss, config_parse_route_metric_advmss_impl); | |
443 | DEFINE_CONFIG_PARSE_ROUTE_METRIC(hop_limit, config_parse_route_metric_hop_limit_impl); | |
444 | DEFINE_CONFIG_PARSE_ROUTE_METRIC(tcp_window, config_parse_tcp_window); | |
445 | DEFINE_CONFIG_PARSE_ROUTE_METRIC(tcp_rto, config_parse_route_metric_tcp_rto_impl); | |
446 | DEFINE_CONFIG_PARSE_ROUTE_METRIC(boolean, config_parse_route_metric_boolean_impl); | |
447 | ||
448 | int config_parse_route_metric_tcp_congestion( | |
449 | const char *unit, | |
450 | const char *filename, | |
451 | unsigned line, | |
452 | const char *section, | |
453 | unsigned section_line, | |
454 | const char *lvalue, | |
455 | int ltype, | |
456 | const char *rvalue, | |
457 | void *data, | |
458 | void *userdata) { | |
459 | ||
460 | Network *network = ASSERT_PTR(userdata); | |
461 | _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; | |
462 | int r; | |
463 | ||
464 | assert(filename); | |
465 | assert(rvalue); | |
466 | ||
467 | r = route_new_static(network, filename, section_line, &route); | |
468 | if (r == -ENOMEM) | |
469 | return log_oom(); | |
470 | if (r < 0) { | |
471 | log_syntax(unit, LOG_WARNING, filename, line, r, | |
472 | "Failed to allocate route, ignoring assignment: %m"); | |
473 | return 0; | |
474 | } | |
475 | ||
476 | r = config_parse_string(unit, filename, line, section, section_line, lvalue, 0, | |
477 | rvalue, &route->metric.tcp_congestion_control_algo, userdata); | |
478 | if (r <= 0) | |
479 | return r; | |
480 | ||
481 | TAKE_PTR(route); | |
482 | return 0; | |
483 | } |