diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:40 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:40 +0000 |
commit | fc53809803cd2bc2434e312b19a18fa36776da12 (patch) | |
tree | b4b43bd6538f51965ce32856e9c053d0f90919c8 /src/network/networkd-route-nexthop.c | |
parent | Adding upstream version 255.5. (diff) | |
download | systemd-fc53809803cd2bc2434e312b19a18fa36776da12.tar.xz systemd-fc53809803cd2bc2434e312b19a18fa36776da12.zip |
Adding upstream version 256.upstream/256
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/network/networkd-route-nexthop.c | 1225 |
1 files changed, 1225 insertions, 0 deletions
diff --git a/src/network/networkd-route-nexthop.c b/src/network/networkd-route-nexthop.c new file mode 100644 index 0000000..11215c3 --- /dev/null +++ b/src/network/networkd-route-nexthop.c @@ -0,0 +1,1225 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <linux/nexthop.h> + +#include "alloc-util.h" +#include "extract-word.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "networkd-nexthop.h" +#include "networkd-route.h" +#include "networkd-route-nexthop.h" +#include "networkd-route-util.h" +#include "parse-util.h" +#include "string-util.h" + +void route_detach_from_nexthop(Route *route) { + NextHop *nh; + + assert(route); + assert(route->manager); + + if (route->nexthop_id == 0) + return; + + if (nexthop_get_by_id(route->manager, route->nexthop_id, &nh) < 0) + return; + + route_unref(set_remove(nh->routes, route)); +} + +void route_attach_to_nexthop(Route *route) { + NextHop *nh; + int r; + + assert(route); + assert(route->manager); + + if (route->nexthop_id == 0) + return; + + r = nexthop_get_by_id(route->manager, route->nexthop_id, &nh); + if (r < 0) { + if (route->manager->manage_foreign_nexthops) + log_debug_errno(r, "Route has unknown nexthop ID (%"PRIu32"), ignoring.", + route->nexthop_id); + return; + } + + r = set_ensure_put(&nh->routes, &route_hash_ops_unref, route); + if (r < 0) + return (void) log_debug_errno(r, "Failed to save route to nexthop, ignoring: %m"); + if (r == 0) + return (void) log_debug("Duplicated route assigned to nexthop, ignoring."); + + route_ref(route); +} + +static void route_nexthop_done(RouteNextHop *nh) { + assert(nh); + + free(nh->ifname); +} + +RouteNextHop* route_nexthop_free(RouteNextHop *nh) { + if (!nh) + return NULL; + + route_nexthop_done(nh); + + return mfree(nh); +} + +void route_nexthops_done(Route *route) { + assert(route); + + route_nexthop_done(&route->nexthop); + ordered_set_free(route->nexthops); +} + +static void route_nexthop_hash_func_full(const RouteNextHop *nh, struct siphash *state, bool with_weight) { + assert(nh); + assert(state); + + /* See nh_comp() in net/ipv4/fib_semantics.c of the kernel. */ + + siphash24_compress_typesafe(nh->family, state); + if (!IN_SET(nh->family, AF_INET, AF_INET6)) + return; + + in_addr_hash_func(&nh->gw, nh->family, state); + if (with_weight) + siphash24_compress_typesafe(nh->weight, state); + siphash24_compress_typesafe(nh->ifindex, state); + if (nh->ifindex == 0) + siphash24_compress_string(nh->ifname, state); /* For Network or Request object. */ +} + +static int route_nexthop_compare_func_full(const RouteNextHop *a, const RouteNextHop *b, bool with_weight) { + int r; + + assert(a); + assert(b); + + r = CMP(a->family, b->family); + if (r != 0) + return r; + + if (!IN_SET(a->family, AF_INET, AF_INET6)) + return 0; + + r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + + if (with_weight) { + r = CMP(a->weight, b->weight); + if (r != 0) + return r; + } + + r = CMP(a->ifindex, b->ifindex); + if (r != 0) + return r; + + if (a->ifindex == 0) { + r = strcmp_ptr(a->ifname, b->ifname); + if (r != 0) + return r; + } + + return 0; +} + +static void route_nexthop_hash_func(const RouteNextHop *nh, struct siphash *state) { + route_nexthop_hash_func_full(nh, state, /* with_weight = */ true); +} + +static int route_nexthop_compare_func(const RouteNextHop *a, const RouteNextHop *b) { + return route_nexthop_compare_func_full(a, b, /* with_weight = */ true); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + route_nexthop_hash_ops, + RouteNextHop, + route_nexthop_hash_func, + route_nexthop_compare_func, + route_nexthop_free); + +static size_t route_n_nexthops(const Route *route) { + if (route->nexthop_id != 0 || route_type_is_reject(route)) + return 0; + + if (ordered_set_isempty(route->nexthops)) + return 1; + + return ordered_set_size(route->nexthops); +} + +void route_nexthops_hash_func(const Route *route, struct siphash *state) { + assert(route); + + size_t nhs = route_n_nexthops(route); + siphash24_compress_typesafe(nhs, state); + + switch (nhs) { + case 0: + siphash24_compress_typesafe(route->nexthop_id, state); + return; + + case 1: + route_nexthop_hash_func_full(&route->nexthop, state, /* with_weight = */ false); + return; + + default: { + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) + route_nexthop_hash_func(nh, state); + }} +} + +int route_nexthops_compare_func(const Route *a, const Route *b) { + int r; + + assert(a); + assert(b); + + size_t a_nhs = route_n_nexthops(a); + size_t b_nhs = route_n_nexthops(b); + r = CMP(a_nhs, b_nhs); + if (r != 0) + return r; + + switch (a_nhs) { + case 0: + return CMP(a->nexthop_id, b->nexthop_id); + + case 1: + return route_nexthop_compare_func_full(&a->nexthop, &b->nexthop, /* with_weight = */ false); + + default: { + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, a->nexthops) { + r = CMP(nh, (RouteNextHop*) ordered_set_get(a->nexthops, nh)); + if (r != 0) + return r; + } + return 0; + }} +} + +static int route_nexthop_copy(const RouteNextHop *src, RouteNextHop *dest) { + assert(src); + assert(dest); + + *dest = *src; + + /* unset pointer copied in the above. */ + dest->ifname = NULL; + + return strdup_to(&dest->ifname, src->ifindex > 0 ? NULL : src->ifname); +} + +static int route_nexthop_dup(const RouteNextHop *src, RouteNextHop **ret) { + _cleanup_(route_nexthop_freep) RouteNextHop *dest = NULL; + int r; + + assert(src); + assert(ret); + + dest = new(RouteNextHop, 1); + if (!dest) + return -ENOMEM; + + r = route_nexthop_copy(src, dest); + if (r < 0) + return r; + + *ret = TAKE_PTR(dest); + return 0; +} + +int route_nexthops_copy(const Route *src, const RouteNextHop *nh, Route *dest) { + int r; + + assert(src); + assert(dest); + + if (src->nexthop_id != 0 || route_type_is_reject(src)) + return 0; + + if (nh) + return route_nexthop_copy(nh, &dest->nexthop); + + if (ordered_set_isempty(src->nexthops)) + return route_nexthop_copy(&src->nexthop, &dest->nexthop); + + ORDERED_SET_FOREACH(nh, src->nexthops) { + _cleanup_(route_nexthop_freep) RouteNextHop *nh_dup = NULL; + + r = route_nexthop_dup(nh, &nh_dup); + if (r < 0) + return r; + + r = ordered_set_ensure_put(&dest->nexthops, &route_nexthop_hash_ops, nh_dup); + if (r < 0) + return r; + assert(r > 0); + + TAKE_PTR(nh_dup); + } + + return 0; +} + +static bool multipath_routes_needs_adjust(const Route *route) { + assert(route); + + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) + if (route->nexthop.ifindex == 0) + return true; + + return false; +} + +bool route_nexthops_needs_adjust(const Route *route) { + assert(route); + + if (route->nexthop_id != 0) + /* At this stage, the nexthop may not be configured, or may be under reconfiguring. + * Hence, we cannot know if the nexthop is blackhole or not. */ + return route->type != RTN_BLACKHOLE; + + if (route_type_is_reject(route)) + return false; + + if (ordered_set_isempty(route->nexthops)) + return route->nexthop.ifindex == 0; + + return multipath_routes_needs_adjust(route); +} + +static bool route_nexthop_set_ifindex(RouteNextHop *nh, Link *link) { + assert(nh); + assert(link); + assert(link->manager); + + if (nh->ifindex > 0) { + nh->ifname = mfree(nh->ifname); + return false; + } + + /* If an interface name is specified, use it. Otherwise, use the interface that requests this route. */ + if (nh->ifname && link_get_by_name(link->manager, nh->ifname, &link) < 0) + return false; + + nh->ifindex = link->ifindex; + nh->ifname = mfree(nh->ifname); + return true; /* updated */ +} + +int route_adjust_nexthops(Route *route, Link *link) { + int r; + + assert(route); + assert(link); + assert(link->manager); + + /* When an IPv4 route has nexthop id and the nexthop type is blackhole, even though kernel sends + * RTM_NEWROUTE netlink message with blackhole type, kernel's internal route type fib_rt_info::type + * may not be blackhole. Thus, we cannot know the internal value. Moreover, on route removal, the + * matching is done with the hidden value if we set non-zero type in RTM_DELROUTE message. So, + * here let's set route type to BLACKHOLE when the nexthop is blackhole. */ + if (route->nexthop_id != 0) { + NextHop *nexthop; + + r = nexthop_is_ready(link->manager, route->nexthop_id, &nexthop); + if (r <= 0) + return r; /* r == 0 means the nexthop is under (re-)configuring. + * We cannot use the currently remembered information. */ + + if (!nexthop->blackhole) + return false; + + if (route->type == RTN_BLACKHOLE) + return false; + + route->type = RTN_BLACKHOLE; + return true; /* updated */ + } + + if (route_type_is_reject(route)) + return false; + + if (ordered_set_isempty(route->nexthops)) + return route_nexthop_set_ifindex(&route->nexthop, link); + + if (!multipath_routes_needs_adjust(route)) + return false; + + _cleanup_ordered_set_free_ OrderedSet *nexthops = NULL; + for (;;) { + _cleanup_(route_nexthop_freep) RouteNextHop *nh = NULL; + + nh = ordered_set_steal_first(route->nexthops); + if (!nh) + break; + + (void) route_nexthop_set_ifindex(nh, link); + + r = ordered_set_ensure_put(&nexthops, &route_nexthop_hash_ops, nh); + if (r == -EEXIST) + continue; /* Duplicated? Let's drop the nexthop. */ + if (r < 0) + return r; + assert(r > 0); + + TAKE_PTR(nh); + } + + ordered_set_free(route->nexthops); + route->nexthops = TAKE_PTR(nexthops); + return true; /* updated */ +} + +int route_nexthop_get_link(Manager *manager, const RouteNextHop *nh, Link **ret) { + assert(manager); + assert(nh); + + if (nh->ifindex > 0) + return link_get_by_index(manager, nh->ifindex, ret); + if (nh->ifname) + return link_get_by_name(manager, nh->ifname, ret); + + return -ENOENT; +} + +static bool route_nexthop_is_ready_to_configure(const RouteNextHop *nh, Manager *manager, bool onlink) { + Link *link; + + assert(nh); + assert(manager); + + if (route_nexthop_get_link(manager, nh, &link) < 0) + return false; + + if (!link_is_ready_to_configure(link, /* allow_unmanaged = */ true)) + return false; + + /* If the interface is not managed by us, we request that the interface has carrier. + * That is, ConfigureWithoutCarrier=no is the default even for unamanaged interfaces. */ + if (!link->network && !link_has_carrier(link)) + return false; + + return gateway_is_ready(link, onlink, nh->family, &nh->gw); +} + +int route_nexthops_is_ready_to_configure(const Route *route, Manager *manager) { + int r; + + assert(route); + assert(manager); + + if (route->nexthop_id != 0) { + struct nexthop_grp *nhg; + NextHop *nh; + + r = nexthop_is_ready(manager, route->nexthop_id, &nh); + if (r <= 0) + return r; + + HASHMAP_FOREACH(nhg, nh->group) { + r = nexthop_is_ready(manager, nhg->id, NULL); + if (r <= 0) + return r; + } + + return true; + } + + if (route_type_is_reject(route)) + return true; + + if (ordered_set_isempty(route->nexthops)) + return route_nexthop_is_ready_to_configure(&route->nexthop, manager, FLAGS_SET(route->flags, RTNH_F_ONLINK)); + + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) + if (!route_nexthop_is_ready_to_configure(nh, manager, FLAGS_SET(route->flags, RTNH_F_ONLINK))) + return false; + + return true; +} + +int route_nexthops_to_string(const Route *route, char **ret) { + _cleanup_free_ char *buf = NULL; + int r; + + assert(route); + assert(ret); + + if (route->nexthop_id != 0) { + if (asprintf(&buf, "nexthop: %"PRIu32, route->nexthop_id) < 0) + return -ENOMEM; + + *ret = TAKE_PTR(buf); + return 0; + } + + if (route_type_is_reject(route)) { + buf = strdup("gw: n/a"); + if (!buf) + return -ENOMEM; + + *ret = TAKE_PTR(buf); + return 0; + } + + if (ordered_set_isempty(route->nexthops)) { + if (in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) + buf = strjoin("gw: ", IN_ADDR_TO_STRING(route->nexthop.family, &route->nexthop.gw)); + else if (route->gateway_from_dhcp_or_ra) { + if (route->nexthop.family == AF_INET) + buf = strdup("gw: _dhcp4"); + else if (route->nexthop.family == AF_INET6) + buf = strdup("gw: _ipv6ra"); + else + buf = strdup("gw: _dhcp"); + } else + buf = strdup("gw: n/a"); + if (!buf) + return -ENOMEM; + + *ret = TAKE_PTR(buf); + return 0; + } + + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) { + const char *s = in_addr_is_set(nh->family, &nh->gw) ? IN_ADDR_TO_STRING(nh->family, &nh->gw) : NULL; + + if (nh->ifindex > 0) + r = strextendf_with_separator(&buf, ",", "%s@%i:%"PRIu32, strempty(s), nh->ifindex, nh->weight + 1); + else if (nh->ifname) + r = strextendf_with_separator(&buf, ",", "%s@%s:%"PRIu32, strempty(s), nh->ifname, nh->weight + 1); + else + r = strextendf_with_separator(&buf, ",", "%s:%"PRIu32, strempty(s), nh->weight + 1); + if (r < 0) + return r; + } + + char *p = strjoin("gw: ", strna(buf)); + if (!p) + return -ENOMEM; + + *ret = p; + return 0; +} + +static int append_nexthop_one(const Route *route, const RouteNextHop *nh, struct rtattr **rta, size_t offset) { + struct rtnexthop *rtnh; + struct rtattr *new_rta; + int r; + + assert(route); + assert(IN_SET(route->family, AF_INET, AF_INET6)); + assert(nh); + assert(rta); + assert(*rta); + + new_rta = realloc(*rta, RTA_ALIGN((*rta)->rta_len) + RTA_SPACE(sizeof(struct rtnexthop))); + if (!new_rta) + return -ENOMEM; + *rta = new_rta; + + rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); + *rtnh = (struct rtnexthop) { + .rtnh_len = sizeof(*rtnh), + .rtnh_ifindex = nh->ifindex, + .rtnh_hops = nh->weight, + }; + + (*rta)->rta_len += sizeof(struct rtnexthop); + + if (nh->family == route->family) { + r = rtattr_append_attribute(rta, RTA_GATEWAY, &nh->gw, FAMILY_ADDRESS_SIZE(nh->family)); + if (r < 0) + goto clear; + + rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); + rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(nh->family)); + + } else if (nh->family == AF_INET6) { + assert(route->family == AF_INET); + + r = rtattr_append_attribute(rta, RTA_VIA, + &(RouteVia) { + .family = nh->family, + .address = nh->gw, + }, sizeof(RouteVia)); + if (r < 0) + goto clear; + + rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); + rtnh->rtnh_len += RTA_SPACE(sizeof(RouteVia)); + + } else if (nh->family == AF_INET) + assert_not_reached(); + + return 0; + +clear: + (*rta)->rta_len -= sizeof(struct rtnexthop); + return r; +} + +static int netlink_message_append_multipath_route(const Route *route, sd_netlink_message *message) { + _cleanup_free_ struct rtattr *rta = NULL; + size_t offset; + int r; + + assert(route); + assert(message); + + rta = new(struct rtattr, 1); + if (!rta) + return -ENOMEM; + + *rta = (struct rtattr) { + .rta_type = RTA_MULTIPATH, + .rta_len = RTA_LENGTH(0), + }; + offset = (uint8_t *) RTA_DATA(rta) - (uint8_t *) rta; + + if (ordered_set_isempty(route->nexthops)) { + r = append_nexthop_one(route, &route->nexthop, &rta, offset); + if (r < 0) + return r; + + } else { + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) { + struct rtnexthop *rtnh; + + r = append_nexthop_one(route, nh, &rta, offset); + if (r < 0) + return r; + + rtnh = (struct rtnexthop *)((uint8_t *) rta + offset); + offset = (uint8_t *) RTNH_NEXT(rtnh) - (uint8_t *) rta; + } + } + + return sd_netlink_message_append_data(message, RTA_MULTIPATH, RTA_DATA(rta), RTA_PAYLOAD(rta)); +} + +int route_nexthops_set_netlink_message(const Route *route, sd_netlink_message *message) { + int r; + + assert(route); + assert(IN_SET(route->family, AF_INET, AF_INET6)); + assert(message); + + if (route->nexthop_id != 0) + return sd_netlink_message_append_u32(message, RTA_NH_ID, route->nexthop_id); + + if (route_type_is_reject(route)) + return 0; + + /* We request IPv6 multipath routes separately. Even though, if weight is non-zero, we need to use + * RTA_MULTIPATH, as we have no way to specify the weight of the nexthop. */ + if (ordered_set_isempty(route->nexthops) && route->nexthop.weight == 0) { + + if (in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) { + if (route->nexthop.family == route->family) + r = netlink_message_append_in_addr_union(message, RTA_GATEWAY, route->nexthop.family, &route->nexthop.gw); + else { + assert(route->family == AF_INET); + r = sd_netlink_message_append_data(message, RTA_VIA, + &(const RouteVia) { + .family = route->nexthop.family, + .address = route->nexthop.gw, + }, sizeof(RouteVia)); + } + if (r < 0) + return r; + } + + assert(route->nexthop.ifindex > 0); + return sd_netlink_message_append_u32(message, RTA_OIF, route->nexthop.ifindex); + } + + return netlink_message_append_multipath_route(route, message); +} + +static int route_parse_nexthops(Route *route, const struct rtnexthop *rtnh, size_t size) { + _cleanup_ordered_set_free_ OrderedSet *nexthops = NULL; + int r; + + assert(route); + assert(IN_SET(route->family, AF_INET, AF_INET6)); + assert(rtnh); + + if (size < sizeof(struct rtnexthop)) + return -EBADMSG; + + for (; size >= sizeof(struct rtnexthop); ) { + _cleanup_(route_nexthop_freep) RouteNextHop *nh = NULL; + + if (NLMSG_ALIGN(rtnh->rtnh_len) > size) + return -EBADMSG; + + if (rtnh->rtnh_len < sizeof(struct rtnexthop)) + return -EBADMSG; + + nh = new(RouteNextHop, 1); + if (!nh) + return -ENOMEM; + + *nh = (RouteNextHop) { + .ifindex = rtnh->rtnh_ifindex, + .weight = rtnh->rtnh_hops, + }; + + if (rtnh->rtnh_len > sizeof(struct rtnexthop)) { + size_t len = rtnh->rtnh_len - sizeof(struct rtnexthop); + bool have_gw = false; + + for (struct rtattr *attr = RTNH_DATA(rtnh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) { + + switch (attr->rta_type) { + case RTA_GATEWAY: + if (have_gw) + return -EBADMSG; + + if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(route->family))) + return -EBADMSG; + + nh->family = route->family; + memcpy(&nh->gw, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(nh->family)); + have_gw = true; + break; + + case RTA_VIA: + if (have_gw) + return -EBADMSG; + + if (route->family != AF_INET) + return -EBADMSG; + + if (attr->rta_len != RTA_LENGTH(sizeof(RouteVia))) + return -EBADMSG; + + RouteVia *via = RTA_DATA(attr); + if (via->family != AF_INET6) + return -EBADMSG; + + nh->family = via->family; + nh->gw = via->address; + have_gw = true; + break; + } + } + } + + r = ordered_set_ensure_put(&nexthops, &route_nexthop_hash_ops, nh); + assert(r != 0); + if (r > 0) + TAKE_PTR(nh); + else if (r != -EEXIST) + return r; + + size -= NLMSG_ALIGN(rtnh->rtnh_len); + rtnh = RTNH_NEXT(rtnh); + } + + ordered_set_free(route->nexthops); + route->nexthops = TAKE_PTR(nexthops); + return 0; +} + +int route_nexthops_read_netlink_message(Route *route, sd_netlink_message *message) { + int r; + + assert(route); + assert(message); + + r = sd_netlink_message_read_u32(message, RTA_NH_ID, &route->nexthop_id); + if (r < 0 && r != -ENODATA) + return log_warning_errno(r, "rtnl: received route message with invalid nexthop id, ignoring: %m"); + + if (route->nexthop_id != 0 || route_type_is_reject(route)) + /* IPv6 routes with reject type are always assigned to the loopback interface. See kernel's + * fib6_nh_init() in net/ipv6/route.c. However, we'd like to make it consistent with IPv4 + * routes. Hence, skip reading of RTA_OIF. */ + return 0; + + uint32_t ifindex; + r = sd_netlink_message_read_u32(message, RTA_OIF, &ifindex); + if (r >= 0) + route->nexthop.ifindex = (int) ifindex; + else if (r != -ENODATA) + return log_warning_errno(r, "rtnl: could not get ifindex from route message, ignoring: %m"); + + if (route->nexthop.ifindex > 0) { + r = netlink_message_read_in_addr_union(message, RTA_GATEWAY, route->family, &route->nexthop.gw); + if (r >= 0) { + route->nexthop.family = route->family; + return 0; + } + if (r != -ENODATA) + return log_warning_errno(r, "rtnl: received route message without valid gateway, ignoring: %m"); + + if (route->family != AF_INET) + return 0; + + RouteVia via; + r = sd_netlink_message_read(message, RTA_VIA, sizeof(via), &via); + if (r >= 0) { + route->nexthop.family = via.family; + route->nexthop.gw = via.address; + return 0; + } + if (r != -ENODATA) + return log_warning_errno(r, "rtnl: received route message without valid gateway, ignoring: %m"); + + return 0; + } + + size_t rta_len; + _cleanup_free_ void *rta = NULL; + r = sd_netlink_message_read_data(message, RTA_MULTIPATH, &rta_len, &rta); + if (r == -ENODATA) + return 0; + if (r < 0) + return log_warning_errno(r, "rtnl: failed to read RTA_MULTIPATH attribute, ignoring: %m"); + + r = route_parse_nexthops(route, rta, rta_len); + if (r < 0) + return log_warning_errno(r, "rtnl: failed to parse RTA_MULTIPATH attribute, ignoring: %m"); + + return 0; +} + +int route_section_verify_nexthops(Route *route) { + assert(route); + assert(route->section); + + if (route->gateway_from_dhcp_or_ra) { + assert(route->network); + + if (route->nexthop.family == AF_UNSPEC) + /* When deprecated Gateway=_dhcp is set, then assume gateway family based on other settings. */ + switch (route->family) { + case AF_UNSPEC: + log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. " + "Please use \"_dhcp4\" or \"_ipv6ra\" instead. Assuming \"_dhcp4\".", + route->section->filename, route->section->line); + + route->nexthop.family = route->family = AF_INET; + break; + case AF_INET: + case AF_INET6: + log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. " + "Assuming \"%s\" based on Destination=, Source=, or PreferredSource= setting.", + route->section->filename, route->section->line, route->family == AF_INET ? "_dhcp4" : "_ipv6ra"); + + route->nexthop.family = route->family; + break; + default: + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Invalid route family. Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + } + + if (route->nexthop.family == AF_INET && !FLAGS_SET(route->network->dhcp, ADDRESS_FAMILY_IPV4)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Gateway=\"_dhcp4\" is specified but DHCPv4 client is disabled. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + + if (route->nexthop.family == AF_INET6 && route->network->ndisc == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Gateway=\"_ipv6ra\" is specified but IPv6AcceptRA= is disabled. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + } + + /* When only Gateway= is specified, assume the route family based on the Gateway address. */ + if (route->family == AF_UNSPEC) + route->family = route->nexthop.family; + + if (route->family == AF_UNSPEC) { + assert(route->section); + + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Route section without Gateway=, Destination=, Source=, " + "or PreferredSource= field configured. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + } + + if (route->gateway_onlink < 0 && in_addr_is_set(route->nexthop.family, &route->nexthop.gw) && + route->network && ordered_hashmap_isempty(route->network->addresses_by_section)) { + /* If no address is configured, in most cases the gateway cannot be reachable. + * TODO: we may need to improve the condition above. */ + log_warning("%s: Gateway= without static address configured. " + "Enabling GatewayOnLink= option.", + route->section->filename); + route->gateway_onlink = true; + } + + if (route->gateway_onlink >= 0) + SET_FLAG(route->flags, RTNH_F_ONLINK, route->gateway_onlink); + + if (route->family == AF_INET6) { + if (route->nexthop.family == AF_INET) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: IPv4 gateway is configured for IPv6 route. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) + if (nh->family == AF_INET) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: IPv4 multipath route is specified for IPv6 route. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + } + + if (route->nexthop_id != 0 && + (route->gateway_from_dhcp_or_ra || + in_addr_is_set(route->nexthop.family, &route->nexthop.gw) || + !ordered_set_isempty(route->nexthops))) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: NextHopId= cannot be specified with Gateway= or MultiPathRoute=. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + + if (route_type_is_reject(route) && + (route->gateway_from_dhcp_or_ra || + in_addr_is_set(route->nexthop.family, &route->nexthop.gw) || + !ordered_set_isempty(route->nexthops))) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: reject type route cannot be specified with Gateway= or MultiPathRoute=. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + + if ((route->gateway_from_dhcp_or_ra || + in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) && + !ordered_set_isempty(route->nexthops)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Gateway= cannot be specified with MultiPathRoute=. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + + if (ordered_set_size(route->nexthops) == 1) { + _cleanup_(route_nexthop_freep) RouteNextHop *nh = ordered_set_steal_first(route->nexthops); + + route_nexthop_done(&route->nexthop); + route->nexthop = TAKE_STRUCT(*nh); + + assert(ordered_set_isempty(route->nexthops)); + route->nexthops = ordered_set_free(route->nexthops); + } + + return 0; +} + +int config_parse_gateway( + 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) { + + Network *network = userdata; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(section, "Network")) { + /* we are not in an Route section, so use line number instead */ + r = route_new_static(network, filename, line, &route); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + } else { + r = route_new_static(network, filename, section_line, &route); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + if (isempty(rvalue)) { + route->gateway_from_dhcp_or_ra = false; + route->nexthop.family = AF_UNSPEC; + route->nexthop.gw = IN_ADDR_NULL; + TAKE_PTR(route); + return 0; + } + + if (streq(rvalue, "_dhcp")) { + route->gateway_from_dhcp_or_ra = true; + route->nexthop.family = AF_UNSPEC; + route->nexthop.gw = IN_ADDR_NULL; + TAKE_PTR(route); + return 0; + } + + if (streq(rvalue, "_dhcp4")) { + route->gateway_from_dhcp_or_ra = true; + route->nexthop.family = AF_INET; + route->nexthop.gw = IN_ADDR_NULL; + TAKE_PTR(route); + return 0; + } + + if (streq(rvalue, "_ipv6ra")) { + route->gateway_from_dhcp_or_ra = true; + route->nexthop.family = AF_INET6; + route->nexthop.gw = IN_ADDR_NULL; + TAKE_PTR(route); + return 0; + } + } + + r = in_addr_from_string_auto(rvalue, &route->nexthop.family, &route->nexthop.gw); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); + return 0; + } + + route->gateway_from_dhcp_or_ra = false; + TAKE_PTR(route); + return 0; +} + +int config_parse_route_gateway_onlink( + 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) { + + Network *network = userdata; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &route); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + r = config_parse_tristate(unit, filename, line, section, section_line, lvalue, ltype, rvalue, + &route->gateway_onlink, network); + if (r <= 0) + return r; + + TAKE_PTR(route); + return 0; +} + +int config_parse_route_nexthop( + 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) { + + Network *network = userdata; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; + uint32_t id; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &route); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + if (isempty(rvalue)) { + route->nexthop_id = 0; + TAKE_PTR(route); + return 0; + } + + r = safe_atou32(rvalue, &id); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse nexthop ID, ignoring assignment: %s", rvalue); + return 0; + } + if (id == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid nexthop ID, ignoring assignment: %s", rvalue); + return 0; + } + + route->nexthop_id = id; + TAKE_PTR(route); + return 0; +} + +int config_parse_multipath_route( + 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) { + + _cleanup_(route_nexthop_freep) RouteNextHop *nh = NULL; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; + _cleanup_free_ char *word = NULL; + Network *network = userdata; + const char *p; + char *dev; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &route); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + if (isempty(rvalue)) { + route->nexthops = ordered_set_free(route->nexthops); + TAKE_PTR(route); + return 0; + } + + nh = new0(RouteNextHop, 1); + if (!nh) + return log_oom(); + + p = rvalue; + r = extract_first_word(&p, &word, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r <= 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid multipath route option, ignoring assignment: %s", rvalue); + return 0; + } + + dev = strchr(word, '@'); + if (dev) { + *dev++ = '\0'; + + r = parse_ifindex(dev); + if (r > 0) + nh->ifindex = r; + else { + if (!ifname_valid_full(dev, IFNAME_VALID_ALTERNATIVE)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid interface name '%s' in %s=, ignoring: %s", dev, lvalue, rvalue); + return 0; + } + + nh->ifname = strdup(dev); + if (!nh->ifname) + return log_oom(); + } + } + + r = in_addr_from_string_auto(word, &nh->family, &nh->gw); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid multipath route gateway '%s', ignoring assignment: %m", rvalue); + return 0; + } + + if (!isempty(p)) { + r = safe_atou32(p, &nh->weight); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid multipath route weight, ignoring assignment: %s", p); + return 0; + } + /* ip command takes weight in the range 1…255, while kernel takes the value in the + * range 0…254. MultiPathRoute= setting also takes weight in the same range which ip + * command uses, then networkd decreases by one and stores it to match the range which + * kernel uses. */ + if (nh->weight == 0 || nh->weight > 256) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid multipath route weight, ignoring assignment: %s", p); + return 0; + } + nh->weight--; + } + + r = ordered_set_ensure_put(&route->nexthops, &route_nexthop_hash_ops, nh); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store multipath route, ignoring assignment: %m"); + return 0; + } + + TAKE_PTR(nh); + TAKE_PTR(route); + return 0; +} |