1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include <linux/rtnetlink.h>
5 #include "alloc-util.h"
7 #include "missing_threads.h"
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"
17 #include "sysctl-util.h"
19 #define ROUTES_DEFAULT_MAX_PER_FAMILY 4096U
21 unsigned 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
;
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
;
34 if (sysctl_read_ip_property(AF_INET6
, NULL
, "route/max_size", &s6
) >= 0)
35 (void) safe_atou(s6
, &val6
);
37 cached
= MAX(ROUTES_DEFAULT_MAX_PER_FAMILY
, val4
) +
38 MAX(ROUTES_DEFAULT_MAX_PER_FAMILY
, val6
);
42 bool route_type_is_reject(const Route
*route
) {
45 return IN_SET(route
->type
, RTN_UNREACHABLE
, RTN_PROHIBIT
, RTN_BLACKHOLE
, RTN_THROW
);
48 static bool route_lifetime_is_valid(const Route
*route
) {
52 route
->lifetime_usec
== USEC_INFINITY
||
53 route
->lifetime_usec
> now(CLOCK_BOOTTIME
);
56 bool link_find_default_gateway(Link
*link
, int family
, Route
**gw
) {
61 assert(link
->manager
);
63 SET_FOREACH(route
, link
->manager
->routes
) {
64 if (route
->nexthop
.ifindex
!= link
->ifindex
)
66 if (!route_exists(route
))
68 if (family
!= AF_UNSPEC
&& route
->family
!= family
)
70 if (route
->dst_prefixlen
!= 0)
72 if (route
->src_prefixlen
!= 0)
74 if (route
->table
!= RT_TABLE_MAIN
)
76 if (route
->type
!= RTN_UNICAST
)
78 if (route
->scope
!= RT_SCOPE_UNIVERSE
)
80 if (!in_addr_is_set(route
->nexthop
.family
, &route
->nexthop
.gw
))
83 /* Found a default gateway. */
87 /* If we have already found another gw, then let's compare their weight and priority. */
89 if (route
->nexthop
.weight
> (*gw
)->nexthop
.weight
)
91 if (route
->priority
>= (*gw
)->priority
)
102 int manager_find_uplink(Manager
*m
, int family
, Link
*exclude
, Link
**ret
) {
107 assert(IN_SET(family
, AF_UNSPEC
, AF_INET
, AF_INET6
));
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. */
112 HASHMAP_FOREACH(link
, m
->links_by_index
) {
116 if (link
->state
!= LINK_STATE_CONFIGURED
)
119 link_find_default_gateway(link
, family
, &gw
);
125 return link_get_by_index(m
, gw
->nexthop
.ifindex
, ret
);
128 bool gateway_is_ready(Link
*link
, bool onlink
, int family
, const union in_addr_union
*gw
) {
133 assert(link
->manager
);
138 if (!gw
|| !in_addr_is_set(family
, gw
))
141 if (family
== AF_INET6
&& in6_addr_is_link_local(&gw
->in6
))
144 SET_FOREACH(route
, link
->manager
->routes
) {
145 if (route
->nexthop
.ifindex
!= link
->ifindex
)
147 if (!route_exists(route
))
149 if (!route_lifetime_is_valid(route
))
151 if (route
->family
!= family
)
153 if (!in_addr_is_set(route
->family
, &route
->dst
) && route
->dst_prefixlen
== 0)
155 if (in_addr_prefix_covers(family
, &route
->dst
, route
->dst_prefixlen
, gw
) > 0)
159 if (link
->manager
->manage_foreign_routes
)
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
))
167 if (a
->family
!= family
)
169 if (FLAGS_SET(a
->flags
, IFA_F_NOPREFIXROUTE
))
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)
180 static int link_address_is_reachable_internal(
183 const union in_addr_union
*address
,
184 const union in_addr_union
*prefsrc
, /* optional */
187 Route
*route
, *found
= NULL
;
190 assert(link
->manager
);
191 assert(IN_SET(family
, AF_INET
, AF_INET6
));
194 SET_FOREACH(route
, link
->manager
->routes
) {
195 if (route
->nexthop
.ifindex
!= link
->ifindex
)
198 if (!route_exists(route
))
201 if (!route_lifetime_is_valid(route
))
204 if (route
->type
!= RTN_UNICAST
)
207 if (route
->family
!= family
)
210 if (in_addr_prefix_covers(family
, &route
->dst
, route
->dst_prefixlen
, address
) <= 0)
214 in_addr_is_set(family
, prefsrc
) &&
215 in_addr_is_set(family
, &route
->prefsrc
) &&
216 !in_addr_equal(family
, prefsrc
, &route
->prefsrc
))
219 if (found
&& found
->priority
<= route
->priority
)
234 int link_address_is_reachable(
237 const union in_addr_union
*address
,
238 const union in_addr_union
*prefsrc
, /* optional */
246 assert(IN_SET(family
, AF_INET
, AF_INET6
));
249 /* This checks if the address is reachable, and optionally return the Address object of the
250 * preferred source to access the address. */
252 r
= link_address_is_reachable_internal(link
, family
, address
, prefsrc
, &route
);
256 if (!in_addr_is_set(route
->family
, &route
->prefsrc
)) {
262 r
= link_get_address(link
, route
->family
, &route
->prefsrc
, 0, &a
);
266 if (!address_is_ready(a
))
275 int manager_address_is_reachable(
278 const union in_addr_union
*address
,
279 const union in_addr_union
*prefsrc
, /* optional */
282 Route
*route
, *found
= NULL
;
289 HASHMAP_FOREACH(link
, manager
->links_by_index
) {
290 if (!IN_SET(link
->state
, LINK_STATE_CONFIGURING
, LINK_STATE_CONFIGURED
))
293 if (link_address_is_reachable_internal(link
, family
, address
, prefsrc
, &route
) < 0)
296 if (found
&& found
->priority
<= route
->priority
)
305 if (!in_addr_is_set(found
->family
, &found
->prefsrc
)) {
311 r
= link_get_by_index(manager
, found
->nexthop
.ifindex
, &link
);
315 r
= link_get_address(link
, found
->family
, &found
->prefsrc
, 0, &a
);
319 if (!address_is_ready(a
))
328 static 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",
339 [RTN_XRESOLVE
] = "xresolve",
342 assert_cc(__RTN_MAX
<= UCHAR_MAX
);
343 DEFINE_STRING_TABLE_LOOKUP(route_type
, int);
345 static 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",
353 DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(route_scope
, int, UINT8_MAX
);
355 static const char * const route_protocol_table
[] = {
356 [RTPROT_KERNEL
] = "kernel",
357 [RTPROT_BOOT
] = "boot",
358 [RTPROT_STATIC
] = "static",
361 DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(route_protocol
, int, UINT8_MAX
);
363 static 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",
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",
386 DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(route_protocol_full
, int, UINT8_MAX
);
388 int route_flags_to_string_alloc(uint32_t flags
, char **ret
) {
389 _cleanup_free_
char *str
= NULL
;
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 */
402 for (size_t i
= 0; i
< ELEMENTSOF(map
); i
++)
403 if (FLAGS_SET(flags
, 1 << i
) && map
[i
])
404 if (!strextend_with_separator(&str
, ",", map
[i
]))
407 *ret
= TAKE_PTR(str
);
411 static const char * const route_table_table
[] = {
412 [RT_TABLE_DEFAULT
] = "default",
413 [RT_TABLE_MAIN
] = "main",
414 [RT_TABLE_LOCAL
] = "local",
417 DEFINE_PRIVATE_STRING_TABLE_LOOKUP(route_table
, int);
419 int manager_get_route_table_from_string(const Manager
*m
, const char *s
, uint32_t *ret
) {
427 r
= route_table_from_string(s
);
433 t
= PTR_TO_UINT32(hashmap_get(m
->route_table_numbers_by_name
, s
));
439 r
= safe_atou32(s
, &t
);
450 int manager_get_route_table_to_string(const Manager
*m
, uint32_t table
, bool append_num
, char **ret
) {
451 _cleanup_free_
char *str
= NULL
;
457 /* Unlike manager_get_route_table_from_string(), this accepts 0, as the kernel may create routes with
458 * table 0. See issue #25089. */
460 s
= route_table_to_string(table
);
462 s
= hashmap_get(m
->route_table_names_by_number
, UINT32_TO_PTR(table
));
464 if (s
&& !append_num
) {
469 } else if (asprintf(&str
, "%s%s%" PRIu32
"%s",
476 *ret
= TAKE_PTR(str
);
480 int config_parse_route_table_names(
482 const char *filename
,
485 unsigned section_line
,
492 Manager
*m
= ASSERT_PTR(userdata
);
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
);
505 for (const char *p
= rvalue
;;) {
506 _cleanup_free_
char *name
= NULL
;
510 r
= extract_first_word(&p
, &name
, NULL
, 0);
514 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
515 "Invalid RouteTable=, ignoring assignment: %s", rvalue
);
521 num
= strchr(name
, ':');
523 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0,
524 "Invalid route table name and number pair, ignoring assignment: %s", name
);
531 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0,
532 "Route table name cannot be empty. Ignoring assignment: %s:%s", name
, num
);
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
);
540 if (route_table_from_string(name
) >= 0) {
541 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0,
542 "Route table name %s is predefined for %i. Ignoring assignment: %s:%s",
543 name
, route_table_from_string(name
), name
, num
);
547 r
= safe_atou32(num
, &table
);
549 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
550 "Failed to parse route table number '%s', ignoring assignment: %s:%s", num
, name
, num
);
554 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0,
555 "Invalid route table number, ignoring assignment: %s:%s", name
, num
);
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
);
565 r
= hashmap_ensure_put(&m
->route_table_numbers_by_name
, &string_hash_ops_free
, name
, UINT32_TO_PTR(table
));
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
);
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
);
579 /* The entry is duplicated. It should not be added to route_table_names_by_number hashmap. */
582 r
= hashmap_ensure_put(&m
->route_table_names_by_number
, NULL
, UINT32_TO_PTR(table
), name
);
584 hashmap_remove(m
->route_table_numbers_by_name
, name
);
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
);
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
);