diff --git a/src/network/networkd-dhcp-prefix-delegation.c b/src/network/networkd-dhcp-prefix-delegation.c index b30527e99f..aecdaa5b25 100644 --- a/src/network/networkd-dhcp-prefix-delegation.c +++ b/src/network/networkd-dhcp-prefix-delegation.c @@ -300,6 +300,10 @@ static int dhcp_pd_request_route(Link *link, const struct in6_addr *prefix, usec route->priority = link->network->dhcp_pd_route_metric; route->lifetime_usec = lifetime_usec; + r = route_adjust_nexthops(route, link); + if (r < 0) + return r; + if (route_get(NULL, link, route, &existing) < 0) link->dhcp_pd_configured = false; else @@ -695,6 +699,10 @@ static int dhcp_request_unreachable_route( route->priority = IP6_RT_PRIO_USER; route->lifetime_usec = lifetime_usec; + r = route_adjust_nexthops(route, link); + if (r < 0) + return r; + if (route_get(link->manager, NULL, route, &existing) < 0) *configured = false; else @@ -789,6 +797,10 @@ static int dhcp4_pd_request_default_gateway_on_6rd_tunnel(Link *link, const stru route->priority = IP6_RT_PRIO_USER; route->lifetime_usec = lifetime_usec; + r = route_adjust_nexthops(route, link); + if (r < 0) + return r; + if (route_get(NULL, link, route, &existing) < 0) /* This is a new route. */ link->dhcp_pd_configured = false; else diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index b56106867b..2afda41ab5 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -390,6 +390,10 @@ static int dhcp4_request_route(Route *in, Link *link) { if (r < 0) return r; + r = route_adjust_nexthops(route, link); + if (r < 0) + return r; + if (route_get(NULL, link, route, &existing) < 0) /* This is a new route. */ link->dhcp4_configured = false; else @@ -659,7 +663,7 @@ static int dhcp4_request_semi_static_routes(Link *link) { if (r < 0) return r; - r = route_dup(rt, &route); + r = route_dup(rt, NULL, &route); if (r < 0) return r; diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 33d11509d3..74bb549dac 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -218,6 +218,10 @@ static int ndisc_request_route(Route *in, Link *link, sd_ndisc_router *rt) { if (r < 0) return r; + r = route_adjust_nexthops(route, link); + if (r < 0) + return r; + is_new = route_get(NULL, link, route, NULL) < 0; r = link_request_route(link, TAKE_PTR(route), true, &link->ndisc_messages, @@ -340,7 +344,7 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { if (route_gw->nexthop.family != AF_INET6) continue; - r = route_dup(route_gw, &route); + r = route_dup(route_gw, NULL, &route); if (r < 0) return r; diff --git a/src/network/networkd-route-nexthop.c b/src/network/networkd-route-nexthop.c index f58cdb3695..9e14c695be 100644 --- a/src/network/networkd-route-nexthop.c +++ b/src/network/networkd-route-nexthop.c @@ -170,6 +170,147 @@ int route_nexthops_compare_func(const Route *a, const Route *b) { }} } +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_or_null(src->ifindex == 0 ? NULL : src->ifname, &dest->ifname); +} + +int route_nexthops_copy(const Route *src, const RouteNextHop *nh, Route *dest) { + 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); + + /* Currently, this does not copy multipath routes. */ + + 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, Link *link, const RouteNextHop *nh, Link **ret) { assert(manager); assert(nh); @@ -384,9 +525,6 @@ static int netlink_message_append_multipath_route(Link *link, const Route *route assert(route); assert(message); - if (ordered_set_isempty(route->nexthops)) - return 0; - rta = new(struct rtattr, 1); if (!rta) return -ENOMEM; @@ -397,16 +535,23 @@ static int netlink_message_append_multipath_route(Link *link, const Route *route }; offset = (uint8_t *) RTA_DATA(rta) - (uint8_t *) rta; - RouteNextHop *nh; - ORDERED_SET_FOREACH(nh, route->nexthops) { - struct rtnexthop *rtnh; - - r = append_nexthop_one(link, route, nh, &rta, offset); + if (ordered_set_isempty(route->nexthops)) { + r = append_nexthop_one(link, route, &route->nexthop, &rta, offset); if (r < 0) return r; - rtnh = (struct rtnexthop *)((uint8_t *) rta + offset); - offset = (uint8_t *) RTNH_NEXT(rtnh) - (uint8_t *) rta; + } else { + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) { + struct rtnexthop *rtnh; + + r = append_nexthop_one(link, 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)); @@ -425,7 +570,9 @@ int route_nexthops_set_netlink_message(Link *link, const Route *route, sd_netlin if (route_type_is_reject(route)) return 0; - if (ordered_set_isempty(route->nexthops)) { + /* We request IPv6 multipath routes separatedly. 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) diff --git a/src/network/networkd-route-nexthop.h b/src/network/networkd-route-nexthop.h index f06a2c49ff..a63409c9e2 100644 --- a/src/network/networkd-route-nexthop.h +++ b/src/network/networkd-route-nexthop.h @@ -24,6 +24,8 @@ typedef struct RouteNextHop { /* unsupported attributes: RTA_FLOW (IPv4 only), RTA_ENCAP_TYPE, RTA_ENCAP. */ } RouteNextHop; +#define ROUTE_NEXTHOP_NULL ((const RouteNextHop) {}) + RouteNextHop* route_nexthop_free(RouteNextHop *nh); DEFINE_TRIVIAL_CLEANUP_FUNC(RouteNextHop*, route_nexthop_free); @@ -32,6 +34,10 @@ void route_nexthops_done(Route *route); void route_nexthops_hash_func(const Route *route, struct siphash *state); int route_nexthops_compare_func(const Route *a, const Route *b); +int route_nexthops_copy(const Route *src, const RouteNextHop *nh, Route *dest); +bool route_nexthops_needs_adjust(const Route *route); +int route_adjust_nexthops(Route *route, Link *link); + int route_nexthop_get_link(Manager *manager, Link *link, const RouteNextHop *nh, Link **ret); int route_nexthops_is_ready_to_configure(const Route *route, Link *link); diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index 06a114b5a0..f877702efc 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -336,12 +336,33 @@ static int route_get_link(Manager *manager, const Route *route, Link **ret) { return route_nexthop_get_link(manager, NULL, &route->nexthop, ret); } -int route_dup(const Route *src, Route **ret) { +static int route_get_request(Link *link, const Route *route, Request **ret) { + Request *req; + + assert(link); + assert(link->manager); + assert(route); + + req = ordered_set_get(link->manager->request_queue, + &(const Request) { + .link = link, + .type = REQUEST_TYPE_ROUTE, + .userdata = (void*) route, + .hash_func = (hash_func_t) route_hash_func, + .compare_func = (compare_func_t) route_compare_func, + }); + if (!req) + return -ENOENT; + + if (ret) + *ret = req; + return 0; +} + +int route_dup(const Route *src, const RouteNextHop *nh, Route **ret) { _cleanup_(route_freep) Route *dest = NULL; int r; - /* This does not copy mulipath routes. */ - assert(src); assert(ret); @@ -355,10 +376,15 @@ int route_dup(const Route *src, Route **ret) { dest->wireguard = NULL; dest->section = NULL; dest->link = NULL; + dest->nexthop = ROUTE_NEXTHOP_NULL; dest->nexthops = NULL; dest->metric = ROUTE_METRIC_NULL; dest->expire = NULL; + r = route_nexthops_copy(src, nh, dest); + if (r < 0) + return r; + r = route_metric_copy(&src->metric, &dest->metric); if (r < 0) return r; @@ -476,7 +502,7 @@ static int route_convert(Manager *manager, Link *link, const Route *route, Conve if (r < 0) return r; - r = route_dup(route, &c->routes[0]); + r = route_dup(route, NULL, &c->routes[0]); if (r < 0) return r; @@ -499,7 +525,7 @@ static int route_convert(Manager *manager, Link *link, const Route *route, Conve if (r < 0) return r; - r = route_dup(route, &c->routes[i]); + r = route_dup(route, NULL, &c->routes[i]); if (r < 0) return r; @@ -523,7 +549,7 @@ static int route_convert(Manager *manager, Link *link, const Route *route, Conve size_t i = 0; RouteNextHop *nh; ORDERED_SET_FOREACH(nh, route->nexthops) { - r = route_dup(route, &c->routes[i]); + r = route_dup(route, NULL, &c->routes[i]); if (r < 0) return r; @@ -1129,7 +1155,7 @@ static int route_process_request(Request *req, Link *link, Route *route) { if (route_get(link->manager, converted->links[i] ?: link, converted->routes[i], &existing) < 0) { _cleanup_(route_freep) Route *tmp = NULL; - r = route_dup(converted->routes[i], &tmp); + r = route_dup(converted->routes[i], NULL, &tmp); if (r < 0) return log_oom(); @@ -1215,7 +1241,7 @@ int link_request_route( if (consume_object) tmp = route; else { - r = route_dup(route, &tmp); + r = route_dup(route, NULL, &tmp); if (r < 0) return r; } @@ -1345,29 +1371,17 @@ int link_request_static_routes(Link *link, bool only_ipv4) { } void route_cancel_request(Route *route, Link *link) { - Request *req; - assert(route); - link = route->link ?: link; - - assert(link); - assert(link->manager); + link = ASSERT_PTR(route->link ?: link); if (!route_is_requesting(route)) return; - req = ordered_set_get(link->manager->request_queue, - &(Request) { - .link = link, - .type = REQUEST_TYPE_ROUTE, - .userdata = route, - .hash_func = (hash_func_t) route_hash_func, - .compare_func = (compare_func_t) route_compare_func, - }); - - if (req) + Request *req; + if (route_get_request(link, route, &req) >= 0) request_detach(req); + route_cancel_requesting(route); } diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h index 8d55a6ac00..49e78e9777 100644 --- a/src/network/networkd-route.h +++ b/src/network/networkd-route.h @@ -86,7 +86,7 @@ DEFINE_SECTION_CLEANUP_FUNCTIONS(Route, route_free); int route_new(Route **ret); int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret); -int route_dup(const Route *src, Route **ret); +int route_dup(const Route *src, const RouteNextHop *nh, Route **ret); int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, Route *route, const char *error_msg); int route_remove(Route *route);