]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/network/networkd-route-util.c
network: do not append table number in TableString field in json output
[thirdparty/systemd.git] / src / network / networkd-route-util.c
CommitLineData
344b3cff
YW
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <linux/rtnetlink.h>
4
5#include "alloc-util.h"
3ae6b3bf 6#include "logarithm.h"
5545f336 7#include "missing_threads.h"
344b3cff
YW
8#include "networkd-address.h"
9#include "networkd-link.h"
10#include "networkd-manager.h"
11#include "networkd-route-util.h"
12#include "networkd-route.h"
13#include "parse-util.h"
14#include "string-table.h"
15#include "string-util.h"
16#include "strv.h"
17#include "sysctl-util.h"
18
19#define ROUTES_DEFAULT_MAX_PER_FAMILY 4096U
20
21unsigned routes_max(void) {
22 static thread_local unsigned cached = 0;
23 _cleanup_free_ char *s4 = NULL, *s6 = NULL;
24 unsigned val4 = ROUTES_DEFAULT_MAX_PER_FAMILY, val6 = ROUTES_DEFAULT_MAX_PER_FAMILY;
25
26 if (cached > 0)
27 return cached;
28
29 if (sysctl_read_ip_property(AF_INET, NULL, "route/max_size", &s4) >= 0)
30 if (safe_atou(s4, &val4) >= 0 && val4 == 2147483647U)
31 /* This is the default "no limit" value in the kernel */
32 val4 = ROUTES_DEFAULT_MAX_PER_FAMILY;
33
34 if (sysctl_read_ip_property(AF_INET6, NULL, "route/max_size", &s6) >= 0)
35 (void) safe_atou(s6, &val6);
36
37 cached = MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val4) +
38 MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val6);
39 return cached;
40}
41
42static Route *link_find_default_gateway(Link *link, int family, Route *gw) {
43 Route *route;
44
45 assert(link);
46
47 SET_FOREACH(route, link->routes) {
48 if (!route_exists(route))
49 continue;
50 if (family != AF_UNSPEC && route->family != family)
51 continue;
52 if (route->dst_prefixlen != 0)
53 continue;
54 if (route->src_prefixlen != 0)
55 continue;
56 if (route->table != RT_TABLE_MAIN)
57 continue;
58 if (route->type != RTN_UNICAST)
59 continue;
60 if (route->scope != RT_SCOPE_UNIVERSE)
61 continue;
62 if (!in_addr_is_set(route->gw_family, &route->gw))
63 continue;
64 if (gw) {
65 if (route->gw_weight > gw->gw_weight)
66 continue;
67 if (route->priority >= gw->priority)
68 continue;
69 }
70 gw = route;
71 }
72
73 return gw;
74}
75
76int manager_find_uplink(Manager *m, int family, Link *exclude, Link **ret) {
77 Route *gw = NULL;
78 Link *link;
79
80 assert(m);
81 assert(IN_SET(family, AF_UNSPEC, AF_INET, AF_INET6));
82
83 /* Looks for a suitable "uplink", via black magic: an interface that is up and where the
84 * default route with the highest priority points to. */
85
86 HASHMAP_FOREACH(link, m->links_by_index) {
87 if (link == exclude)
88 continue;
89
90 if (link->state != LINK_STATE_CONFIGURED)
91 continue;
92
93 gw = link_find_default_gateway(link, family, gw);
94 }
95
96 if (!gw)
97 return -ENOENT;
98
99 if (ret) {
100 assert(gw->link);
101 *ret = gw->link;
102 }
103
104 return 0;
105}
106
c45cbc23 107bool gateway_is_ready(Link *link, bool onlink, int family, const union in_addr_union *gw) {
344b3cff
YW
108 Route *route;
109 Address *a;
110
111 assert(link);
112 assert(link->manager);
c45cbc23
YW
113
114 if (onlink)
115 return true;
116
117 if (!gw || !in_addr_is_set(family, gw))
118 return true;
119
120 if (family == AF_INET6 && in6_addr_is_link_local(&gw->in6))
121 return true;
344b3cff
YW
122
123 SET_FOREACH(route, link->routes) {
124 if (!route_exists(route))
125 continue;
126 if (route->family != family)
127 continue;
cf477495 128 if (!in_addr_is_set(route->family, &route->dst) && route->dst_prefixlen == 0)
344b3cff 129 continue;
c45cbc23 130 if (in_addr_prefix_covers(family, &route->dst, route->dst_prefixlen, gw) > 0)
344b3cff
YW
131 return true;
132 }
133
134 if (link->manager->manage_foreign_routes)
135 return false;
136
137 /* If we do not manage foreign routes, then there may exist a prefix route we do not know,
138 * which was created on configuring an address. Hence, also check the addresses. */
139 SET_FOREACH(a, link->addresses) {
140 if (!address_is_ready(a))
141 continue;
142 if (a->family != family)
143 continue;
144 if (FLAGS_SET(a->flags, IFA_F_NOPREFIXROUTE))
145 continue;
146 if (in_addr_is_set(a->family, &a->in_addr_peer))
147 continue;
c45cbc23 148 if (in_addr_prefix_covers(family, &a->in_addr, a->prefixlen, gw) > 0)
344b3cff
YW
149 return true;
150 }
151
152 return false;
153}
154
8b7615f9
YW
155static int link_address_is_reachable_internal(
156 Link *link,
157 int family,
158 const union in_addr_union *address,
159 const union in_addr_union *prefsrc, /* optional */
160 Route **ret) {
161
162 Route *route, *found = NULL;
163
164 assert(link);
165 assert(IN_SET(family, AF_INET, AF_INET6));
166 assert(address);
167
168 SET_FOREACH(route, link->routes) {
169 if (!route_exists(route))
170 continue;
171
172 if (route->type != RTN_UNICAST)
173 continue;
174
175 if (route->family != family)
176 continue;
177
178 if (in_addr_prefix_covers(family, &route->dst, route->dst_prefixlen, address) <= 0)
179 continue;
180
181 if (prefsrc &&
182 in_addr_is_set(family, prefsrc) &&
183 in_addr_is_set(family, &route->prefsrc) &&
184 !in_addr_equal(family, prefsrc, &route->prefsrc))
185 continue;
186
187 if (found && found->priority <= route->priority)
188 continue;
189
190 found = route;
191 }
192
193 if (!found)
194 return -ENOENT;
195
196 if (ret)
197 *ret = found;
198
199 return 0;
200}
201
202int link_address_is_reachable(
203 Link *link,
204 int family,
205 const union in_addr_union *address,
206 const union in_addr_union *prefsrc, /* optional */
207 Address **ret) {
208
209 Route *route;
210 Address *a;
211 int r;
212
213 assert(link);
214 assert(IN_SET(family, AF_INET, AF_INET6));
215 assert(address);
216
217 /* This checks if the address is reachable, and optionally return the Address object of the
218 * preferred source to access the address. */
219
220 r = link_address_is_reachable_internal(link, family, address, prefsrc, &route);
221 if (r < 0)
222 return r;
223
224 if (!in_addr_is_set(route->family, &route->prefsrc)) {
225 if (ret)
226 *ret = NULL;
227 return 0;
228 }
229
230 r = link_get_address(link, route->family, &route->prefsrc, 0, &a);
231 if (r < 0)
232 return r;
233
234 if (!address_is_ready(a))
235 return -EBUSY;
236
237 if (ret)
238 *ret = a;
239
240 return 0;
241}
242
243int manager_address_is_reachable(
244 Manager *manager,
245 int family,
246 const union in_addr_union *address,
247 const union in_addr_union *prefsrc, /* optional */
248 Address **ret) {
249
250 Route *route, *found = NULL;
251 Address *a;
252 Link *link;
253 int r;
254
255 assert(manager);
256
257 HASHMAP_FOREACH(link, manager->links_by_index) {
258 if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
259 continue;
260
261 if (link_address_is_reachable_internal(link, family, address, prefsrc, &route) < 0)
262 continue;
263
264 if (found && found->priority <= route->priority)
265 continue;
266
267 found = route;
268 }
269
270 if (!found)
271 return -ENOENT;
272
273 if (!in_addr_is_set(found->family, &found->prefsrc)) {
274 if (ret)
275 *ret = NULL;
276 return 0;
277 }
278
279 r = link_get_address(found->link, found->family, &found->prefsrc, 0, &a);
280 if (r < 0)
281 return r;
282
283 if (!address_is_ready(a))
284 return -EBUSY;
285
286 if (ret)
287 *ret = a;
288
289 return 0;
290}
291
344b3cff
YW
292static const char * const route_type_table[__RTN_MAX] = {
293 [RTN_UNICAST] = "unicast",
294 [RTN_LOCAL] = "local",
295 [RTN_BROADCAST] = "broadcast",
296 [RTN_ANYCAST] = "anycast",
297 [RTN_MULTICAST] = "multicast",
298 [RTN_BLACKHOLE] = "blackhole",
299 [RTN_UNREACHABLE] = "unreachable",
300 [RTN_PROHIBIT] = "prohibit",
301 [RTN_THROW] = "throw",
302 [RTN_NAT] = "nat",
303 [RTN_XRESOLVE] = "xresolve",
304};
305
306assert_cc(__RTN_MAX <= UCHAR_MAX);
307DEFINE_STRING_TABLE_LOOKUP(route_type, int);
308
309static const char * const route_scope_table[] = {
310 [RT_SCOPE_UNIVERSE] = "global",
311 [RT_SCOPE_SITE] = "site",
312 [RT_SCOPE_LINK] = "link",
313 [RT_SCOPE_HOST] = "host",
314 [RT_SCOPE_NOWHERE] = "nowhere",
315};
316
317DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(route_scope, int, UINT8_MAX);
318
319static const char * const route_protocol_table[] = {
320 [RTPROT_KERNEL] = "kernel",
321 [RTPROT_BOOT] = "boot",
322 [RTPROT_STATIC] = "static",
323};
324
325DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(route_protocol, int, UINT8_MAX);
326
327static const char * const route_protocol_full_table[] = {
328 [RTPROT_REDIRECT] = "redirect",
329 [RTPROT_KERNEL] = "kernel",
330 [RTPROT_BOOT] = "boot",
331 [RTPROT_STATIC] = "static",
332 [RTPROT_GATED] = "gated",
333 [RTPROT_RA] = "ra",
334 [RTPROT_MRT] = "mrt",
335 [RTPROT_ZEBRA] = "zebra",
336 [RTPROT_BIRD] = "bird",
337 [RTPROT_DNROUTED] = "dnrouted",
338 [RTPROT_XORP] = "xorp",
339 [RTPROT_NTK] = "ntk",
340 [RTPROT_DHCP] = "dhcp",
341 [RTPROT_MROUTED] = "mrouted",
342 [RTPROT_BABEL] = "babel",
343 [RTPROT_BGP] = "bgp",
344 [RTPROT_ISIS] = "isis",
345 [RTPROT_OSPF] = "ospf",
346 [RTPROT_RIP] = "rip",
347 [RTPROT_EIGRP] = "eigrp",
348};
349
350DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(route_protocol_full, int, UINT8_MAX);
351
18b23bd4
YW
352int route_flags_to_string_alloc(uint32_t flags, char **ret) {
353 _cleanup_free_ char *str = NULL;
8a7da940
ZJS
354 static const char* map[] = {
355 [LOG2U(RTNH_F_DEAD)] = "dead", /* Nexthop is dead (used by multipath) */
356 [LOG2U(RTNH_F_PERVASIVE)] = "pervasive", /* Do recursive gateway lookup */
357 [LOG2U(RTNH_F_ONLINK)] = "onlink" , /* Gateway is forced on link */
358 [LOG2U(RTNH_F_OFFLOAD)] = "offload", /* Nexthop is offloaded */
359 [LOG2U(RTNH_F_LINKDOWN)] = "linkdown", /* carrier-down on nexthop */
360 [LOG2U(RTNH_F_UNRESOLVED)] = "unresolved", /* The entry is unresolved (ipmr) */
361 [LOG2U(RTNH_F_TRAP)] = "trap", /* Nexthop is trapping packets */
18b23bd4
YW
362 };
363
364 assert(ret);
365
366 for (size_t i = 0; i < ELEMENTSOF(map); i++)
8a7da940
ZJS
367 if (FLAGS_SET(flags, 1 << i) && map[i])
368 if (!strextend_with_separator(&str, ",", map[i]))
369 return -ENOMEM;
18b23bd4
YW
370
371 *ret = TAKE_PTR(str);
372 return 0;
373}
374
344b3cff
YW
375static const char * const route_table_table[] = {
376 [RT_TABLE_DEFAULT] = "default",
377 [RT_TABLE_MAIN] = "main",
378 [RT_TABLE_LOCAL] = "local",
379};
380
381DEFINE_PRIVATE_STRING_TABLE_LOOKUP(route_table, int);
382
383int manager_get_route_table_from_string(const Manager *m, const char *s, uint32_t *ret) {
384 uint32_t t;
385 int r;
386
387 assert(m);
388 assert(s);
389 assert(ret);
390
391 r = route_table_from_string(s);
392 if (r >= 0) {
393 *ret = (uint32_t) r;
394 return 0;
395 }
396
397 t = PTR_TO_UINT32(hashmap_get(m->route_table_numbers_by_name, s));
398 if (t != 0) {
399 *ret = t;
400 return 0;
401 }
402
403 r = safe_atou32(s, &t);
404 if (r < 0)
405 return r;
406
407 if (t == 0)
408 return -ERANGE;
409
410 *ret = t;
411 return 0;
412}
413
f4defbdc 414int manager_get_route_table_to_string(const Manager *m, uint32_t table, bool append_num, char **ret) {
344b3cff
YW
415 _cleanup_free_ char *str = NULL;
416 const char *s;
344b3cff
YW
417
418 assert(m);
419 assert(ret);
420
513bed29
YW
421 /* Unlike manager_get_route_table_from_string(), this accepts 0, as the kernel may create routes with
422 * table 0. See issue #25089. */
344b3cff
YW
423
424 s = route_table_to_string(table);
425 if (!s)
426 s = hashmap_get(m->route_table_names_by_number, UINT32_TO_PTR(table));
427
f4defbdc
YW
428 if (s && !append_num) {
429 str = strdup(s);
430 if (!str)
431 return -ENOMEM;
432
433 } else if (asprintf(&str, "%s%s%" PRIu32 "%s",
434 strempty(s),
435 s ? "(" : "",
436 table,
437 s ? ")" : "") < 0)
344b3cff
YW
438 return -ENOMEM;
439
440 *ret = TAKE_PTR(str);
441 return 0;
442}
443
444int config_parse_route_table_names(
445 const char *unit,
446 const char *filename,
447 unsigned line,
448 const char *section,
449 unsigned section_line,
450 const char *lvalue,
451 int ltype,
452 const char *rvalue,
453 void *data,
454 void *userdata) {
455
99534007 456 Manager *m = ASSERT_PTR(userdata);
344b3cff
YW
457 int r;
458
459 assert(filename);
460 assert(lvalue);
461 assert(rvalue);
344b3cff
YW
462
463 if (isempty(rvalue)) {
464 m->route_table_names_by_number = hashmap_free(m->route_table_names_by_number);
465 m->route_table_numbers_by_name = hashmap_free(m->route_table_numbers_by_name);
466 return 0;
467 }
468
469 for (const char *p = rvalue;;) {
470 _cleanup_free_ char *name = NULL;
471 uint32_t table;
472 char *num;
473
474 r = extract_first_word(&p, &name, NULL, 0);
475 if (r == -ENOMEM)
476 return log_oom();
477 if (r < 0) {
478 log_syntax(unit, LOG_WARNING, filename, line, r,
479 "Invalid RouteTable=, ignoring assignment: %s", rvalue);
480 return 0;
481 }
482 if (r == 0)
483 return 0;
484
485 num = strchr(name, ':');
486 if (!num) {
487 log_syntax(unit, LOG_WARNING, filename, line, 0,
488 "Invalid route table name and number pair, ignoring assignment: %s", name);
489 continue;
490 }
491
492 *num++ = '\0';
493
d2d602f4
YW
494 if (isempty(name)) {
495 log_syntax(unit, LOG_WARNING, filename, line, 0,
496 "Route table name cannot be empty. Ignoring assignment: %s:%s", name, num);
497 continue;
498 }
499 if (in_charset(name, DIGITS)) {
500 log_syntax(unit, LOG_WARNING, filename, line, 0,
501 "Route table name cannot be numeric. Ignoring assignment: %s:%s", name, num);
502 continue;
503 }
344b3cff
YW
504 if (STR_IN_SET(name, "default", "main", "local")) {
505 log_syntax(unit, LOG_WARNING, filename, line, 0,
d2d602f4 506 "Route table name %s is already predefined. Ignoring assignment: %s:%s", name, name, num);
344b3cff
YW
507 continue;
508 }
509
510 r = safe_atou32(num, &table);
511 if (r < 0) {
512 log_syntax(unit, LOG_WARNING, filename, line, r,
513 "Failed to parse route table number '%s', ignoring assignment: %s:%s", num, name, num);
514 continue;
515 }
516 if (table == 0) {
517 log_syntax(unit, LOG_WARNING, filename, line, 0,
518 "Invalid route table number, ignoring assignment: %s:%s", name, num);
519 continue;
520 }
521
522 r = hashmap_ensure_put(&m->route_table_numbers_by_name, &string_hash_ops_free, name, UINT32_TO_PTR(table));
523 if (r == -ENOMEM)
524 return log_oom();
525 if (r == -EEXIST) {
526 log_syntax(unit, LOG_WARNING, filename, line, r,
527 "Specified route table name and number pair conflicts with others, ignoring assignment: %s:%s", name, num);
528 continue;
529 }
530 if (r < 0) {
531 log_syntax(unit, LOG_WARNING, filename, line, r,
532 "Failed to store route table name and number pair, ignoring assignment: %s:%s", name, num);
533 continue;
534 }
535 if (r == 0)
536 /* The entry is duplicated. It should not be added to route_table_names_by_number hashmap. */
537 continue;
538
539 r = hashmap_ensure_put(&m->route_table_names_by_number, NULL, UINT32_TO_PTR(table), name);
540 if (r < 0) {
541 hashmap_remove(m->route_table_numbers_by_name, name);
542
543 if (r == -ENOMEM)
544 return log_oom();
545 if (r == -EEXIST)
546 log_syntax(unit, LOG_WARNING, filename, line, r,
547 "Specified route table name and number pair conflicts with others, ignoring assignment: %s:%s", name, num);
548 else
549 log_syntax(unit, LOG_WARNING, filename, line, r,
550 "Failed to store route table name and number pair, ignoring assignment: %s:%s", name, num);
551 continue;
552 }
553 assert(r > 0);
554
555 TAKE_PTR(name);
556 }
557}