1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include <linux/rtnetlink.h>
6 #include "alloc-util.h"
8 #include "extract-word.h"
10 #include "networkd-address.h"
11 #include "networkd-link.h"
12 #include "networkd-manager.h"
13 #include "networkd-route.h"
14 #include "networkd-route-util.h"
15 #include "parse-util.h"
17 #include "string-table.h"
18 #include "string-util.h"
19 #include "sysctl-util.h"
21 #define ROUTES_DEFAULT_MAX_PER_FAMILY 4096
23 unsigned routes_max(void) {
24 static thread_local
unsigned cached
= 0;
25 int val4
= ROUTES_DEFAULT_MAX_PER_FAMILY
, val6
= ROUTES_DEFAULT_MAX_PER_FAMILY
;
30 /* The kernel internally stores these maximum size in int. */
32 if (sysctl_read_ip_property_int(AF_INET
, /* ifname = */ NULL
, "route/max_size", &val4
) >= 0)
34 /* This is the default "no limit" value in the kernel */
35 val4
= ROUTES_DEFAULT_MAX_PER_FAMILY
;
37 if (sysctl_read_ip_property_int(AF_INET6
, /* ifname = */ NULL
, "route/max_size", &val6
) >= 0)
39 /* This is the default "no limit" value in the kernel */
40 val6
= ROUTES_DEFAULT_MAX_PER_FAMILY
;
42 cached
= MAX(ROUTES_DEFAULT_MAX_PER_FAMILY
, val4
) +
43 MAX(ROUTES_DEFAULT_MAX_PER_FAMILY
, val6
);
47 bool route_type_is_reject(uint8_t type
) {
48 return IN_SET(type
, RTN_UNREACHABLE
, RTN_PROHIBIT
, RTN_BLACKHOLE
, RTN_THROW
);
51 bool route_is_reject(const Route
*route
) {
52 return route_type_is_reject(ASSERT_PTR(route
)->type
);
55 static bool route_lifetime_is_valid(const Route
*route
) {
59 route
->lifetime_usec
== USEC_INFINITY
||
60 route
->lifetime_usec
> now(CLOCK_BOOTTIME
);
63 bool link_find_default_gateway(Link
*link
, int family
, Route
**gw
) {
68 assert(link
->manager
);
70 SET_FOREACH(route
, link
->manager
->routes
) {
71 if (route
->nexthop
.ifindex
!= link
->ifindex
)
73 if (!route_exists(route
))
75 if (family
!= AF_UNSPEC
&& route
->family
!= family
)
77 if (route
->dst_prefixlen
!= 0)
79 if (route
->src_prefixlen
!= 0)
81 if (route
->table
!= RT_TABLE_MAIN
)
83 if (route
->type
!= RTN_UNICAST
)
85 if (route
->scope
!= RT_SCOPE_UNIVERSE
)
87 if (!in_addr_is_set(route
->nexthop
.family
, &route
->nexthop
.gw
))
90 /* Found a default gateway. */
94 /* If we have already found another gw, then let's compare their weight and priority. */
96 if (route
->nexthop
.weight
> (*gw
)->nexthop
.weight
)
98 if (route
->priority
>= (*gw
)->priority
)
109 int manager_find_uplink(Manager
*m
, int family
, Link
*exclude
, Link
**ret
) {
114 assert(IN_SET(family
, AF_UNSPEC
, AF_INET
, AF_INET6
));
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. */
119 HASHMAP_FOREACH(link
, m
->links_by_index
) {
123 if (link
->state
!= LINK_STATE_CONFIGURED
)
126 link_find_default_gateway(link
, family
, &gw
);
132 return link_get_by_index(m
, gw
->nexthop
.ifindex
, ret
);
135 bool gateway_is_ready(Link
*link
, bool onlink
, int family
, const union in_addr_union
*gw
) {
140 assert(link
->manager
);
145 if (!gw
|| !in_addr_is_set(family
, gw
))
148 if (family
== AF_INET6
&& in6_addr_is_link_local(&gw
->in6
))
151 SET_FOREACH(route
, link
->manager
->routes
) {
152 if (route
->nexthop
.ifindex
!= link
->ifindex
)
154 if (!route_exists(route
))
156 if (!route_lifetime_is_valid(route
))
158 if (route
->family
!= family
)
160 if (!in_addr_is_set(route
->family
, &route
->dst
) && route
->dst_prefixlen
== 0)
162 if (in_addr_prefix_covers(family
, &route
->dst
, route
->dst_prefixlen
, gw
) > 0)
166 if (link
->manager
->manage_foreign_routes
)
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
))
174 if (a
->family
!= family
)
176 if (FLAGS_SET(a
->flags
, IFA_F_NOPREFIXROUTE
))
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)
187 static int link_address_is_reachable_internal(
190 const union in_addr_union
*address
,
191 const union in_addr_union
*prefsrc
, /* optional */
194 Route
*route
, *found
= NULL
;
197 assert(link
->manager
);
198 assert(IN_SET(family
, AF_INET
, AF_INET6
));
201 SET_FOREACH(route
, link
->manager
->routes
) {
202 if (route
->nexthop
.ifindex
!= link
->ifindex
)
205 if (!route_exists(route
))
208 if (!route_lifetime_is_valid(route
))
211 if (route
->type
!= RTN_UNICAST
)
214 if (route
->family
!= family
)
217 if (in_addr_prefix_covers(family
, &route
->dst
, route
->dst_prefixlen
, address
) <= 0)
221 in_addr_is_set(family
, prefsrc
) &&
222 in_addr_is_set(family
, &route
->prefsrc
) &&
223 !in_addr_equal(family
, prefsrc
, &route
->prefsrc
))
226 if (found
&& found
->priority
<= route
->priority
)
241 int link_address_is_reachable(
244 const union in_addr_union
*address
,
245 const union in_addr_union
*prefsrc
, /* optional */
253 assert(IN_SET(family
, AF_INET
, AF_INET6
));
256 /* This checks if the address is reachable, and optionally return the Address object of the
257 * preferred source to access the address. */
259 r
= link_address_is_reachable_internal(link
, family
, address
, prefsrc
, &route
);
263 if (!in_addr_is_set(route
->family
, &route
->prefsrc
)) {
269 r
= link_get_address(link
, route
->family
, &route
->prefsrc
, &a
);
273 if (!address_is_ready(a
))
282 int manager_address_is_reachable(
285 const union in_addr_union
*address
,
286 const union in_addr_union
*prefsrc
, /* optional */
289 Route
*route
, *found
= NULL
;
296 HASHMAP_FOREACH(link
, manager
->links_by_index
) {
297 if (!IN_SET(link
->state
, LINK_STATE_CONFIGURING
, LINK_STATE_CONFIGURED
))
300 if (link_address_is_reachable_internal(link
, family
, address
, prefsrc
, &route
) < 0)
303 if (found
&& found
->priority
<= route
->priority
)
312 if (!in_addr_is_set(found
->family
, &found
->prefsrc
)) {
318 r
= link_get_by_index(manager
, found
->nexthop
.ifindex
, &link
);
322 r
= link_get_address(link
, found
->family
, &found
->prefsrc
, &a
);
326 if (!address_is_ready(a
))
335 static 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",
346 [RTN_XRESOLVE
] = "xresolve",
349 assert_cc(__RTN_MAX
<= UCHAR_MAX
);
350 DEFINE_STRING_TABLE_LOOKUP(route_type
, int);
352 static 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",
360 DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(route_scope
, int, UINT8_MAX
);
362 static const char * const route_protocol_table
[] = {
363 [RTPROT_KERNEL
] = "kernel",
364 [RTPROT_BOOT
] = "boot",
365 [RTPROT_STATIC
] = "static",
368 DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(route_protocol
, int, UINT8_MAX
);
370 static 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",
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",
393 DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(route_protocol_full
, int, UINT8_MAX
);
395 int route_flags_to_string_alloc(uint32_t flags
, char **ret
) {
396 _cleanup_free_
char *str
= NULL
;
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 */
409 for (size_t i
= 0; i
< ELEMENTSOF(map
); i
++)
410 if (BIT_SET(flags
, i
) && map
[i
])
411 if (!strextend_with_separator(&str
, ",", map
[i
]))
414 *ret
= TAKE_PTR(str
);
418 static const char * const route_table_table
[] = {
419 [RT_TABLE_DEFAULT
] = "default",
420 [RT_TABLE_MAIN
] = "main",
421 [RT_TABLE_LOCAL
] = "local",
424 DEFINE_PRIVATE_STRING_TABLE_LOOKUP(route_table
, int);
426 int manager_get_route_table_from_string(const Manager
*m
, const char *s
, uint32_t *ret
) {
434 r
= route_table_from_string(s
);
440 t
= PTR_TO_UINT32(hashmap_get(m
->route_table_numbers_by_name
, s
));
446 r
= safe_atou32(s
, &t
);
457 int manager_get_route_table_to_string(const Manager
*m
, uint32_t table
, bool append_num
, char **ret
) {
458 _cleanup_free_
char *str
= NULL
;
464 /* Unlike manager_get_route_table_from_string(), this accepts 0, as the kernel may create routes with
465 * table 0. See issue #25089. */
467 s
= route_table_to_string(table
);
469 s
= hashmap_get(m
->route_table_names_by_number
, UINT32_TO_PTR(table
));
471 if (s
&& !append_num
) {
476 } else if (asprintf(&str
, "%s%s%" PRIu32
"%s",
483 *ret
= TAKE_PTR(str
);
487 int config_parse_route_table_names(
489 const char *filename
,
492 unsigned section_line
,
499 Manager
*m
= ASSERT_PTR(userdata
);
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
);
512 for (const char *p
= rvalue
;;) {
513 _cleanup_free_
char *name
= NULL
;
517 r
= extract_first_word(&p
, &name
, NULL
, 0);
521 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
522 "Invalid RouteTable=, ignoring assignment: %s", rvalue
);
528 num
= strchr(name
, ':');
530 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0,
531 "Invalid route table name and number pair, ignoring assignment: %s", name
);
538 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0,
539 "Route table name cannot be empty. Ignoring assignment: %s:%s", name
, num
);
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
);
547 if (route_table_from_string(name
) >= 0) {
548 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0,
549 "Route table name %s is predefined for %i. Ignoring assignment: %s:%s",
550 name
, route_table_from_string(name
), name
, num
);
554 r
= safe_atou32(num
, &table
);
556 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
557 "Failed to parse route table number '%s', ignoring assignment: %s:%s", num
, name
, num
);
561 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0,
562 "Invalid route table number, ignoring assignment: %s:%s", name
, num
);
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
);
572 r
= hashmap_ensure_put(&m
->route_table_numbers_by_name
, &string_hash_ops_free
, name
, UINT32_TO_PTR(table
));
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
);
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
);
586 /* The entry is duplicated. It should not be added to route_table_names_by_number hashmap. */
589 r
= hashmap_ensure_put(&m
->route_table_names_by_number
, NULL
, UINT32_TO_PTR(table
), name
);
591 hashmap_remove(m
->route_table_numbers_by_name
, name
);
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
);
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
);