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