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