network/ndisc: dynamically configure nexthops when routes with gateway are requested
Previously, when multiple routers send RAs with the same preference,
then the kernel merges routes with the same gateway address:
===
default proto ra metric 1024 expires 595sec pref medium
nexthop via fe80::200:10ff:fe10:1060 dev enp0s9 weight 1
nexthop via fe80::200:10ff:fe10:1061 dev enp0s9 weight 1
===
This causes IPv6 Conformance Test v6LC.2.2.11 failure, as reported in #33470.
To avoid the coalescing issue, we can use nexthop, as suggested by Ido Schimmel:
https://lore.kernel.org/netdev/ZytjEINNRmtpadr_@shredder/
> BTW, you can avoid the coalescing problem by using the nexthop API.
> # ip nexthop add id 1 via fe80::200:10ff:fe10:1060 dev enp0s9
> # ip -6 route add default nhid 1 expires 600 proto ra
> # ip nexthop add id 2 via fe80::200:10ff:fe10:1061 dev enp0s9
> # ip -6 route append default nhid 2 expires 600 proto ra
> # ip -6 route
> fe80::/64 dev enp0s9 proto kernel metric 256 pref medium
> default nhid 1 via fe80::200:10ff:fe10:1060 dev enp0s9 proto ra metric 1024 expires 563sec pref medium
> default nhid 2 via fe80::200:10ff:fe10:1061 dev enp0s9 proto ra metric 1024 expires 594sec pref medium
Fixes #33470.
Suggested-by: Ido Schimmel <idosch@idosch.org>
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
#include "networkd-dhcp6.h"
|
||||
#include "networkd-manager.h"
|
||||
#include "networkd-ndisc.h"
|
||||
#include "networkd-nexthop.h"
|
||||
#include "networkd-queue.h"
|
||||
#include "networkd-route.h"
|
||||
#include "networkd-state-file.h"
|
||||
@@ -149,6 +150,290 @@ static int ndisc_check_ready(Link *link) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ndisc_remove_unused_nexthop(Link *link, NextHop *nexthop) {
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
assert(link->manager);
|
||||
assert(link->ifindex > 0);
|
||||
assert(nexthop);
|
||||
|
||||
if (nexthop->source != NETWORK_CONFIG_SOURCE_NDISC)
|
||||
return 0;
|
||||
|
||||
if (nexthop->ifindex != link->ifindex)
|
||||
return 0;
|
||||
|
||||
Route *route;
|
||||
SET_FOREACH(route, nexthop->routes)
|
||||
if (route_exists(route) || route_is_requesting(route))
|
||||
return 0;
|
||||
|
||||
Request *req;
|
||||
ORDERED_SET_FOREACH(req, link->manager->request_queue) {
|
||||
if (req->type != REQUEST_TYPE_ROUTE)
|
||||
continue;
|
||||
|
||||
route = ASSERT_PTR(req->userdata);
|
||||
if (route->nexthop_id == nexthop->id)
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = nexthop_remove_and_cancel(nexthop, link->manager);
|
||||
if (r < 0)
|
||||
return log_link_debug_errno(link, r, "Failed to remove unused nexthop: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ndisc_remove_unused_nexthop_by_id(Link *link, uint32_t id) {
|
||||
assert(link);
|
||||
assert(link->manager);
|
||||
|
||||
if (id == 0)
|
||||
return 0;
|
||||
|
||||
NextHop *nexthop;
|
||||
if (nexthop_get_by_id(link->manager, id, &nexthop) < 0)
|
||||
return 0;
|
||||
|
||||
return ndisc_remove_unused_nexthop(link, nexthop);
|
||||
}
|
||||
|
||||
static int ndisc_remove_unused_nexthops(Link *link) {
|
||||
int ret = 0;
|
||||
|
||||
assert(link);
|
||||
assert(link->manager);
|
||||
|
||||
NextHop *nexthop;
|
||||
HASHMAP_FOREACH(nexthop, link->manager->nexthops_by_id)
|
||||
RET_GATHER(ret, ndisc_remove_unused_nexthop(link, nexthop));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define NDISC_NEXTHOP_APP_ID SD_ID128_MAKE(76,d2,0f,1f,76,1e,44,d1,97,3a,52,5c,05,68,b5,0d)
|
||||
|
||||
static uint32_t ndisc_generate_nexthop_id(NextHop *nexthop, Link *link, sd_id128_t app_id, uint64_t trial) {
|
||||
assert(nexthop);
|
||||
assert(link);
|
||||
|
||||
struct siphash state;
|
||||
siphash24_init(&state, app_id.bytes);
|
||||
siphash24_compress_typesafe(nexthop->protocol, &state);
|
||||
siphash24_compress_string(link->ifname, &state);
|
||||
siphash24_compress_typesafe(nexthop->gw.address.in6, &state);
|
||||
siphash24_compress_typesafe(nexthop->provider.in6, &state);
|
||||
uint64_t n = htole64(trial);
|
||||
siphash24_compress_typesafe(n, &state);
|
||||
|
||||
uint64_t result = htole64(siphash24_finalize(&state));
|
||||
return (uint32_t) ((result & 0xffffffff) ^ (result >> 32));
|
||||
}
|
||||
|
||||
static bool ndisc_nexthop_equal(NextHop *a, NextHop *b) {
|
||||
assert(a);
|
||||
assert(b);
|
||||
|
||||
if (a->source != b->source)
|
||||
return false;
|
||||
if (a->protocol != b->protocol)
|
||||
return false;
|
||||
if (a->ifindex != b->ifindex)
|
||||
return false;
|
||||
if (!in6_addr_equal(&a->provider.in6, &b->provider.in6))
|
||||
return false;
|
||||
if (!in6_addr_equal(&a->gw.address.in6, &b->gw.address.in6))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ndisc_take_nexthop_id(NextHop *nexthop, NextHop *existing, Manager *manager) {
|
||||
assert(nexthop);
|
||||
assert(existing);
|
||||
assert(manager);
|
||||
|
||||
if (!ndisc_nexthop_equal(nexthop, existing))
|
||||
return false;
|
||||
|
||||
log_nexthop_debug(existing, "Found matching", manager);
|
||||
nexthop->id = existing->id;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int ndisc_nexthop_find_id(NextHop *nexthop, Link *link) {
|
||||
NextHop *n;
|
||||
Request *req;
|
||||
int r;
|
||||
|
||||
assert(nexthop);
|
||||
assert(link);
|
||||
assert(link->manager);
|
||||
|
||||
sd_id128_t app_id;
|
||||
r = sd_id128_get_machine_app_specific(NDISC_NEXTHOP_APP_ID, &app_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
uint32_t id = ndisc_generate_nexthop_id(nexthop, link, app_id, 0);
|
||||
if (nexthop_get_by_id(link->manager, id, &n) >= 0 &&
|
||||
ndisc_take_nexthop_id(nexthop, n, link->manager))
|
||||
return true;
|
||||
if (nexthop_get_request_by_id(link->manager, id, &req) >= 0 &&
|
||||
ndisc_take_nexthop_id(nexthop, req->userdata, link->manager))
|
||||
return true;
|
||||
|
||||
HASHMAP_FOREACH(n, link->manager->nexthops_by_id)
|
||||
if (ndisc_take_nexthop_id(nexthop, n, link->manager))
|
||||
return true;
|
||||
|
||||
ORDERED_SET_FOREACH(req, link->manager->request_queue) {
|
||||
if (req->type != REQUEST_TYPE_NEXTHOP)
|
||||
continue;
|
||||
|
||||
if (ndisc_take_nexthop_id(nexthop, req->userdata, link->manager))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int ndisc_nexthop_new(Route *route, Link *link, NextHop **ret) {
|
||||
_cleanup_(nexthop_unrefp) NextHop *nexthop = NULL;
|
||||
int r;
|
||||
|
||||
assert(route);
|
||||
assert(link);
|
||||
assert(ret);
|
||||
|
||||
r = nexthop_new(&nexthop);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
nexthop->source = NETWORK_CONFIG_SOURCE_NDISC;
|
||||
nexthop->provider = route->provider;
|
||||
nexthop->protocol = route->protocol == RTPROT_REDIRECT ? RTPROT_REDIRECT : RTPROT_RA;
|
||||
nexthop->family = AF_INET6;
|
||||
nexthop->gw.address = route->nexthop.gw;
|
||||
nexthop->ifindex = link->ifindex;
|
||||
|
||||
r = ndisc_nexthop_find_id(nexthop, link);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = TAKE_PTR(nexthop);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ndisc_nexthop_acquire_id(NextHop *nexthop, Link *link) {
|
||||
int r;
|
||||
|
||||
assert(nexthop);
|
||||
assert(nexthop->id == 0);
|
||||
assert(link);
|
||||
assert(link->manager);
|
||||
|
||||
sd_id128_t app_id;
|
||||
r = sd_id128_get_machine_app_specific(NDISC_NEXTHOP_APP_ID, &app_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
for (uint64_t trial = 0; trial < 100; trial++) {
|
||||
uint32_t id = ndisc_generate_nexthop_id(nexthop, link, app_id, trial);
|
||||
if (id == 0)
|
||||
continue;
|
||||
|
||||
if (set_contains(link->manager->nexthop_ids, UINT32_TO_PTR(id)))
|
||||
continue; /* The ID is already used in a .network file. */
|
||||
|
||||
if (nexthop_get_by_id(link->manager, id, NULL) >= 0)
|
||||
continue; /* The ID is already used by an existing nexthop. */
|
||||
|
||||
if (nexthop_get_request_by_id(link->manager, id, NULL) >= 0)
|
||||
continue; /* The ID is already used by a nexthop being requested. */
|
||||
|
||||
log_link_debug(link, "Generated new ndisc nexthop ID for %s with trial %"PRIu64": %"PRIu32,
|
||||
IN6_ADDR_TO_STRING(&nexthop->gw.address.in6), trial, id);
|
||||
nexthop->id = id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return log_link_debug_errno(link, SYNTHETIC_ERRNO(EBUSY), "Cannot find free nexthop ID for %s.",
|
||||
IN6_ADDR_TO_STRING(&nexthop->gw.address.in6));
|
||||
}
|
||||
|
||||
static int ndisc_nexthop_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, NextHop *nexthop) {
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
|
||||
r = nexthop_configure_handler_internal(m, link, "Could not set NDisc route");
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
r = ndisc_check_ready(link);
|
||||
if (r < 0)
|
||||
link_enter_failed(link);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ndisc_request_nexthop(NextHop *nexthop, Link *link) {
|
||||
int r;
|
||||
|
||||
assert(nexthop);
|
||||
assert(link);
|
||||
|
||||
if (nexthop->id > 0)
|
||||
return 0;
|
||||
|
||||
r = ndisc_nexthop_acquire_id(nexthop, link);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = link_request_nexthop(link, nexthop, &link->ndisc_messages, ndisc_nexthop_handler);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
link->ndisc_configured = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ndisc_set_route_nexthop(Route *route, Link *link, bool request) {
|
||||
_cleanup_(nexthop_unrefp) NextHop *nexthop = NULL;
|
||||
int r;
|
||||
|
||||
assert(route);
|
||||
assert(link);
|
||||
assert(link->manager);
|
||||
|
||||
if (!link->manager->manage_foreign_nexthops)
|
||||
goto finalize;
|
||||
|
||||
if (route->nexthop.family != AF_INET6 || in6_addr_is_null(&route->nexthop.gw.in6))
|
||||
goto finalize;
|
||||
|
||||
r = ndisc_nexthop_new(route, link, &nexthop);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (nexthop->id == 0 && !request)
|
||||
goto finalize;
|
||||
|
||||
r = ndisc_request_nexthop(nexthop, link);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
route->nexthop = (RouteNextHop) {};
|
||||
route->nexthop_id = nexthop->id;
|
||||
|
||||
finalize:
|
||||
return route_adjust_nexthops(route, link);
|
||||
}
|
||||
|
||||
static int ndisc_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, Route *route) {
|
||||
int r;
|
||||
|
||||
@@ -200,7 +485,7 @@ static int ndisc_request_route(Route *route, Link *link) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = route_adjust_nexthops(route, link);
|
||||
r = ndisc_set_route_nexthop(route, link, /* request = */ true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -322,7 +607,7 @@ static int ndisc_remove_route(Route *route, Link *link) {
|
||||
assert(link);
|
||||
assert(link->manager);
|
||||
|
||||
r = route_adjust_nexthops(route, link);
|
||||
r = ndisc_set_route_nexthop(route, link, /* request = */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -362,7 +647,7 @@ static int ndisc_remove_route(Route *route, Link *link) {
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
return RET_GATHER(ret, ndisc_remove_unused_nexthop_by_id(link, route->nexthop_id));
|
||||
}
|
||||
|
||||
static int ndisc_remove_router_route(Route *route, Link *link, sd_ndisc_router *rt) {
|
||||
@@ -2112,6 +2397,8 @@ static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t
|
||||
RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to remove outdated SLAAC route, ignoring: %m"));
|
||||
}
|
||||
|
||||
RET_GATHER(ret, ndisc_remove_unused_nexthops(link));
|
||||
|
||||
SET_FOREACH(address, link->addresses) {
|
||||
if (address->source != NETWORK_CONFIG_SOURCE_NDISC)
|
||||
continue;
|
||||
@@ -2772,6 +3059,8 @@ int link_drop_ndisc_config(Link *link, Network *network) {
|
||||
if (r < 0)
|
||||
RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to remove SLAAC route, ignoring: %m"));
|
||||
}
|
||||
|
||||
RET_GATHER(ret, ndisc_remove_unused_nexthops(link));
|
||||
}
|
||||
|
||||
/* If SLAAC address is disabled, drop all addresses. */
|
||||
|
||||
Reference in New Issue
Block a user