diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 5f93f45b3ce..e813f8c98b7 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -205,7 +205,6 @@ static int ndisc_request_route(Route *route, Link *link, sd_ndisc_router *rt) { route->provider.in6 = router; if (!route->table_set) route->table = link_get_ipv6_accept_ra_route_table(link); - ndisc_set_route_priority(link, route); if (!route->protocol_set) route->protocol = RTPROT_RA; r = route_metric_set(&route->metric, RTAX_MTU, mtu); @@ -222,6 +221,47 @@ static int ndisc_request_route(Route *route, Link *link, sd_ndisc_router *rt) { if (r < 0) return r; + uint8_t pref, pref_original = route->pref; + FOREACH_ARGUMENT(pref, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH) { + Route *existing; + Request *req; + + /* If the preference is specified by the user config (that is, for semi-static routes), + * rather than RA, then only search conflicting routes that have the same preference. */ + if (route->pref_set && pref != pref_original) + continue; + + route->pref = pref; + ndisc_set_route_priority(link, route); + + /* Note, here do not call route_remove_and_cancel() with 'route' directly, otherwise + * existing route(s) may be removed needlessly. */ + + if (route_get(link->manager, route, &existing) >= 0) { + /* Found an existing route that may conflict with this route. */ + if (!route_can_update(existing, route)) { + log_link_debug(link, "Found an existing route that conflicts with new route based on a received RA, removing."); + r = route_remove_and_cancel(existing, link->manager); + if (r < 0) + return r; + } + } + + if (route_get_request(link->manager, route, &req) >= 0) { + existing = ASSERT_PTR(req->userdata); + if (!route_can_update(existing, route)) { + log_link_debug(link, "Found a pending route request that conflicts with new request based on a received RA, cancelling."); + r = route_remove_and_cancel(existing, link->manager); + if (r < 0) + return r; + } + } + } + + /* The preference (and priority) may be changed in the above loop. Restore it. */ + route->pref = pref_original; + ndisc_set_route_priority(link, route); + is_new = route_get(link->manager, route, NULL) < 0; r = link_request_route(link, route, &link->ndisc_messages, ndisc_route_handler); diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index 743b4f55200..ee12f1664ab 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -352,7 +352,7 @@ static int route_get_link(Manager *manager, const Route *route, Link **ret) { return route_nexthop_get_link(manager, &route->nexthop, ret); } -static int route_get_request(Manager *manager, const Route *route, Request **ret) { +int route_get_request(Manager *manager, const Route *route, Request **ret) { Request *req; assert(manager); diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h index d292716b1d6..912c6e54a9f 100644 --- a/src/network/networkd-route.h +++ b/src/network/networkd-route.h @@ -96,6 +96,7 @@ int route_remove(Route *route, Manager *manager); int route_remove_and_cancel(Route *route, Manager *manager); int route_get(Manager *manager, const Route *route, Route **ret); +int route_get_request(Manager *manager, const Route *route, Request **ret); bool route_can_update(const Route *existing, const Route *requesting);