/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include "alloc-util.h" #include "logarithm.h" #include "missing_threads.h" #include "networkd-address.h" #include "networkd-link.h" #include "networkd-manager.h" #include "networkd-route-util.h" #include "networkd-route.h" #include "parse-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" #include "sysctl-util.h" #define ROUTES_DEFAULT_MAX_PER_FAMILY 4096 unsigned routes_max(void) { static thread_local unsigned cached = 0; int val4 = ROUTES_DEFAULT_MAX_PER_FAMILY, val6 = ROUTES_DEFAULT_MAX_PER_FAMILY; if (cached > 0) return cached; /* The kernel internally stores these maximum size in int. */ if (sysctl_read_ip_property_int(AF_INET, /* ifname = */ NULL, "route/max_size", &val4) >= 0) if (val4 == INT_MAX) /* This is the default "no limit" value in the kernel */ val4 = ROUTES_DEFAULT_MAX_PER_FAMILY; if (sysctl_read_ip_property_int(AF_INET6, /* ifname = */ NULL, "route/max_size", &val6) >= 0) if (val6 == INT_MAX) /* This is the default "no limit" value in the kernel */ val6 = ROUTES_DEFAULT_MAX_PER_FAMILY; cached = MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val4) + MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val6); return cached; } bool route_type_is_reject(uint8_t type) { return IN_SET(type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW); } bool route_is_reject(const Route *route) { return route_type_is_reject(ASSERT_PTR(route)->type); } static bool route_lifetime_is_valid(const Route *route) { assert(route); return route->lifetime_usec == USEC_INFINITY || route->lifetime_usec > now(CLOCK_BOOTTIME); } bool link_find_default_gateway(Link *link, int family, Route **gw) { bool found = false; Route *route; assert(link); assert(link->manager); SET_FOREACH(route, link->manager->routes) { if (route->nexthop.ifindex != link->ifindex) continue; if (!route_exists(route)) continue; if (family != AF_UNSPEC && route->family != family) continue; if (route->dst_prefixlen != 0) continue; if (route->src_prefixlen != 0) continue; if (route->table != RT_TABLE_MAIN) continue; if (route->type != RTN_UNICAST) continue; if (route->scope != RT_SCOPE_UNIVERSE) continue; if (!in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) continue; /* Found a default gateway. */ if (!gw) return true; /* If we have already found another gw, then let's compare their weight and priority. */ if (*gw) { if (route->nexthop.weight > (*gw)->nexthop.weight) continue; if (route->priority >= (*gw)->priority) continue; } *gw = route; found = true; } return found; } int manager_find_uplink(Manager *m, int family, Link *exclude, Link **ret) { Route *gw = NULL; Link *link; assert(m); assert(IN_SET(family, AF_UNSPEC, AF_INET, AF_INET6)); /* Looks for a suitable "uplink", via black magic: an interface that is up and where the * default route with the highest priority points to. */ HASHMAP_FOREACH(link, m->links_by_index) { if (link == exclude) continue; if (link->state != LINK_STATE_CONFIGURED) continue; link_find_default_gateway(link, family, &gw); } if (!gw) return -ENOENT; return link_get_by_index(m, gw->nexthop.ifindex, ret); } bool gateway_is_ready(Link *link, bool onlink, int family, const union in_addr_union *gw) { Route *route; Address *a; assert(link); assert(link->manager); if (onlink) return true; if (!gw || !in_addr_is_set(family, gw)) return true; if (family == AF_INET6 && in6_addr_is_link_local(&gw->in6)) return true; SET_FOREACH(route, link->manager->routes) { if (route->nexthop.ifindex != link->ifindex) continue; if (!route_exists(route)) continue; if (!route_lifetime_is_valid(route)) continue; if (route->family != family) continue; if (!in_addr_is_set(route->family, &route->dst) && route->dst_prefixlen == 0) continue; if (in_addr_prefix_covers(family, &route->dst, route->dst_prefixlen, gw) > 0) return true; } if (link->manager->manage_foreign_routes) return false; /* If we do not manage foreign routes, then there may exist a prefix route we do not know, * which was created on configuring an address. Hence, also check the addresses. */ SET_FOREACH(a, link->addresses) { if (!address_is_ready(a)) continue; if (a->family != family) continue; if (FLAGS_SET(a->flags, IFA_F_NOPREFIXROUTE)) continue; if (in_addr_prefix_covers(a->family, in_addr_is_set(a->family, &a->in_addr_peer) ? &a->in_addr_peer : &a->in_addr, a->prefixlen, gw) > 0) return true; } return false; } static int link_address_is_reachable_internal( Link *link, int family, const union in_addr_union *address, const union in_addr_union *prefsrc, /* optional */ Route **ret) { Route *route, *found = NULL; assert(link); assert(link->manager); assert(IN_SET(family, AF_INET, AF_INET6)); assert(address); SET_FOREACH(route, link->manager->routes) { if (route->nexthop.ifindex != link->ifindex) continue; if (!route_exists(route)) continue; if (!route_lifetime_is_valid(route)) continue; if (route->type != RTN_UNICAST) continue; if (route->family != family) continue; if (in_addr_prefix_covers(family, &route->dst, route->dst_prefixlen, address) <= 0) continue; if (prefsrc && in_addr_is_set(family, prefsrc) && in_addr_is_set(family, &route->prefsrc) && !in_addr_equal(family, prefsrc, &route->prefsrc)) continue; if (found && found->priority <= route->priority) continue; found = route; } if (!found) return -ENOENT; if (ret) *ret = found; return 0; } int link_address_is_reachable( Link *link, int family, const union in_addr_union *address, const union in_addr_union *prefsrc, /* optional */ Address **ret) { Route *route; Address *a; int r; assert(link); assert(IN_SET(family, AF_INET, AF_INET6)); assert(address); /* This checks if the address is reachable, and optionally return the Address object of the * preferred source to access the address. */ r = link_address_is_reachable_internal(link, family, address, prefsrc, &route); if (r < 0) return r; if (!in_addr_is_set(route->family, &route->prefsrc)) { if (ret) *ret = NULL; return 0; } r = link_get_address(link, route->family, &route->prefsrc, &a); if (r < 0) return r; if (!address_is_ready(a)) return -EBUSY; if (ret) *ret = a; return 0; } int manager_address_is_reachable( Manager *manager, int family, const union in_addr_union *address, const union in_addr_union *prefsrc, /* optional */ Address **ret) { Route *route, *found = NULL; Address *a; Link *link; int r; assert(manager); HASHMAP_FOREACH(link, manager->links_by_index) { if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) continue; if (link_address_is_reachable_internal(link, family, address, prefsrc, &route) < 0) continue; if (found && found->priority <= route->priority) continue; found = route; } if (!found) return -ENOENT; if (!in_addr_is_set(found->family, &found->prefsrc)) { if (ret) *ret = NULL; return 0; } r = link_get_by_index(manager, found->nexthop.ifindex, &link); if (r < 0) return r; r = link_get_address(link, found->family, &found->prefsrc, &a); if (r < 0) return r; if (!address_is_ready(a)) return -EBUSY; if (ret) *ret = a; return 0; } static const char * const route_type_table[__RTN_MAX] = { [RTN_UNICAST] = "unicast", [RTN_LOCAL] = "local", [RTN_BROADCAST] = "broadcast", [RTN_ANYCAST] = "anycast", [RTN_MULTICAST] = "multicast", [RTN_BLACKHOLE] = "blackhole", [RTN_UNREACHABLE] = "unreachable", [RTN_PROHIBIT] = "prohibit", [RTN_THROW] = "throw", [RTN_NAT] = "nat", [RTN_XRESOLVE] = "xresolve", }; assert_cc(__RTN_MAX <= UCHAR_MAX); DEFINE_STRING_TABLE_LOOKUP(route_type, int); static const char * const route_scope_table[] = { [RT_SCOPE_UNIVERSE] = "global", [RT_SCOPE_SITE] = "site", [RT_SCOPE_LINK] = "link", [RT_SCOPE_HOST] = "host", [RT_SCOPE_NOWHERE] = "nowhere", }; DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(route_scope, int, UINT8_MAX); static const char * const route_protocol_table[] = { [RTPROT_KERNEL] = "kernel", [RTPROT_BOOT] = "boot", [RTPROT_STATIC] = "static", }; DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(route_protocol, int, UINT8_MAX); static const char * const route_protocol_full_table[] = { [RTPROT_REDIRECT] = "redirect", [RTPROT_KERNEL] = "kernel", [RTPROT_BOOT] = "boot", [RTPROT_STATIC] = "static", [RTPROT_GATED] = "gated", [RTPROT_RA] = "ra", [RTPROT_MRT] = "mrt", [RTPROT_ZEBRA] = "zebra", [RTPROT_BIRD] = "bird", [RTPROT_DNROUTED] = "dnrouted", [RTPROT_XORP] = "xorp", [RTPROT_NTK] = "ntk", [RTPROT_DHCP] = "dhcp", [RTPROT_MROUTED] = "mrouted", [RTPROT_BABEL] = "babel", [RTPROT_BGP] = "bgp", [RTPROT_ISIS] = "isis", [RTPROT_OSPF] = "ospf", [RTPROT_RIP] = "rip", [RTPROT_EIGRP] = "eigrp", }; DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(route_protocol_full, int, UINT8_MAX); int route_flags_to_string_alloc(uint32_t flags, char **ret) { _cleanup_free_ char *str = NULL; static const char* map[] = { [LOG2U(RTNH_F_DEAD)] = "dead", /* Nexthop is dead (used by multipath) */ [LOG2U(RTNH_F_PERVASIVE)] = "pervasive", /* Do recursive gateway lookup */ [LOG2U(RTNH_F_ONLINK)] = "onlink" , /* Gateway is forced on link */ [LOG2U(RTNH_F_OFFLOAD)] = "offload", /* Nexthop is offloaded */ [LOG2U(RTNH_F_LINKDOWN)] = "linkdown", /* carrier-down on nexthop */ [LOG2U(RTNH_F_UNRESOLVED)] = "unresolved", /* The entry is unresolved (ipmr) */ [LOG2U(RTNH_F_TRAP)] = "trap", /* Nexthop is trapping packets */ }; assert(ret); for (size_t i = 0; i < ELEMENTSOF(map); i++) if (FLAGS_SET(flags, 1 << i) && map[i]) if (!strextend_with_separator(&str, ",", map[i])) return -ENOMEM; *ret = TAKE_PTR(str); return 0; } static const char * const route_table_table[] = { [RT_TABLE_DEFAULT] = "default", [RT_TABLE_MAIN] = "main", [RT_TABLE_LOCAL] = "local", }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP(route_table, int); int manager_get_route_table_from_string(const Manager *m, const char *s, uint32_t *ret) { uint32_t t; int r; assert(m); assert(s); assert(ret); r = route_table_from_string(s); if (r >= 0) { *ret = (uint32_t) r; return 0; } t = PTR_TO_UINT32(hashmap_get(m->route_table_numbers_by_name, s)); if (t != 0) { *ret = t; return 0; } r = safe_atou32(s, &t); if (r < 0) return r; if (t == 0) return -ERANGE; *ret = t; return 0; } int manager_get_route_table_to_string(const Manager *m, uint32_t table, bool append_num, char **ret) { _cleanup_free_ char *str = NULL; const char *s; assert(m); assert(ret); /* Unlike manager_get_route_table_from_string(), this accepts 0, as the kernel may create routes with * table 0. See issue #25089. */ s = route_table_to_string(table); if (!s) s = hashmap_get(m->route_table_names_by_number, UINT32_TO_PTR(table)); if (s && !append_num) { str = strdup(s); if (!str) return -ENOMEM; } else if (asprintf(&str, "%s%s%" PRIu32 "%s", strempty(s), s ? "(" : "", table, s ? ")" : "") < 0) return -ENOMEM; *ret = TAKE_PTR(str); return 0; } int config_parse_route_table_names( const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) { Manager *m = ASSERT_PTR(userdata); int r; assert(filename); assert(lvalue); assert(rvalue); if (isempty(rvalue)) { m->route_table_names_by_number = hashmap_free(m->route_table_names_by_number); m->route_table_numbers_by_name = hashmap_free(m->route_table_numbers_by_name); return 0; } for (const char *p = rvalue;;) { _cleanup_free_ char *name = NULL; uint32_t table; char *num; r = extract_first_word(&p, &name, NULL, 0); if (r == -ENOMEM) return log_oom(); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid RouteTable=, ignoring assignment: %s", rvalue); return 0; } if (r == 0) return 0; num = strchr(name, ':'); if (!num) { log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid route table name and number pair, ignoring assignment: %s", name); continue; } *num++ = '\0'; if (isempty(name)) { log_syntax(unit, LOG_WARNING, filename, line, 0, "Route table name cannot be empty. Ignoring assignment: %s:%s", name, num); continue; } if (in_charset(name, DIGITS)) { log_syntax(unit, LOG_WARNING, filename, line, 0, "Route table name cannot be numeric. Ignoring assignment: %s:%s", name, num); continue; } if (route_table_from_string(name) >= 0) { log_syntax(unit, LOG_WARNING, filename, line, 0, "Route table name %s is predefined for %i. Ignoring assignment: %s:%s", name, route_table_from_string(name), name, num); continue; } r = safe_atou32(num, &table); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse route table number '%s', ignoring assignment: %s:%s", num, name, num); continue; } if (table == 0) { log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid route table number, ignoring assignment: %s:%s", name, num); continue; } if (route_table_to_string(table)) { log_syntax(unit, LOG_WARNING, filename, line, 0, "Route table name for %s is predefined (%s). Ignoring assignment: %s:%s", num, route_table_to_string(table), name, num); continue; } r = hashmap_ensure_put(&m->route_table_numbers_by_name, &string_hash_ops_free, name, UINT32_TO_PTR(table)); if (r == -ENOMEM) return log_oom(); if (r == -EEXIST) { log_syntax(unit, LOG_WARNING, filename, line, r, "Specified route table name and number pair conflicts with others, ignoring assignment: %s:%s", name, num); continue; } if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to store route table name and number pair, ignoring assignment: %s:%s", name, num); continue; } if (r == 0) /* The entry is duplicated. It should not be added to route_table_names_by_number hashmap. */ continue; r = hashmap_ensure_put(&m->route_table_names_by_number, NULL, UINT32_TO_PTR(table), name); if (r < 0) { hashmap_remove(m->route_table_numbers_by_name, name); if (r == -ENOMEM) return log_oom(); if (r == -EEXIST) log_syntax(unit, LOG_WARNING, filename, line, r, "Specified route table name and number pair conflicts with others, ignoring assignment: %s:%s", name, num); else log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to store route table name and number pair, ignoring assignment: %s:%s", name, num); continue; } assert(r > 0); TAKE_PTR(name); } }