summaryrefslogtreecommitdiffstats
path: root/src/network/networkd-route-nexthop.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 03:50:40 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 03:50:40 +0000
commitfc53809803cd2bc2434e312b19a18fa36776da12 (patch)
treeb4b43bd6538f51965ce32856e9c053d0f90919c8 /src/network/networkd-route-nexthop.c
parentAdding upstream version 255.5. (diff)
downloadsystemd-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 'src/network/networkd-route-nexthop.c')
-rw-r--r--src/network/networkd-route-nexthop.c1225
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;
+}