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