Merge pull request #33450 from yuwata/network-ndisc-do-not-override-static-routes

network/NDisc: do not override static routes
This commit is contained in:
Luca Boccassi
2024-06-25 14:40:06 +02:00
committed by GitHub
3 changed files with 92 additions and 15 deletions

View File

@@ -222,20 +222,33 @@ static int ndisc_request_route(Route *route, Link *link) {
/* 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. */
/* First, check if a conflicting route is already requested. If there is an existing route,
* and also an existing pending request, then the source may be updated by the request. So,
* we first need to check the source of the requested route. */
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 an existing route that conflicts with new route based on a received RA, removing.");
if (existing->source == NETWORK_CONFIG_SOURCE_STATIC) {
log_link_debug(link, "Found a pending route request that conflicts with new request based on a received RA, ignoring request.");
return 0;
}
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;
}
}
if (route_get_request(link->manager, route, &req) >= 0) {
existing = ASSERT_PTR(req->userdata);
/* Then, check if a conflicting route exists. */
if (route_get(link->manager, route, &existing) >= 0) {
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.");
if (existing->source == NETWORK_CONFIG_SOURCE_STATIC) {
log_link_debug(link, "Found an existing route that conflicts with new route based on a received RA, ignoring request.");
return 0;
}
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;
@@ -291,18 +304,44 @@ static int ndisc_remove_route(Route *route, Link *link) {
if (r < 0)
return r;
if (route->pref_set) {
ndisc_set_route_priority(link, route);
return route_remove_and_cancel(route, link->manager);
}
uint8_t pref;
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);
r = route_remove_and_cancel(route, link->manager);
if (r < 0)
return r;
/* Unfortunately, we cannot directly pass 'route' to route_remove_and_cancel() here, as the
* same or similar route may be configured or requested statically. */
/* First, check if the route is already requested. If there is an existing route, and also an
* existing pending request, then the source may be updated by the request. So, we first need
* to check the source of the requested route. */
if (route_get_request(link->manager, route, &req) >= 0) {
existing = ASSERT_PTR(req->userdata);
if (existing->source == NETWORK_CONFIG_SOURCE_STATIC)
continue;
r = route_remove_and_cancel(existing, link->manager);
if (r < 0)
return r;
}
/* Then, check if the route exists. */
if (route_get(link->manager, route, &existing) >= 0) {
if (existing->source == NETWORK_CONFIG_SOURCE_STATIC)
continue;
r = route_remove_and_cancel(existing, link->manager);
if (r < 0)
return r;
}
}
return 0;

View File

@@ -0,0 +1,14 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=veth99
[Network]
IPv6AcceptRA=true
[Route]
Gateway=fe80::1034:56ff:fe78:9abd
GatewayOnLink=no
Metric=256
[IPv6AcceptRA]
RouteMetric=256

View File

@@ -5824,6 +5824,30 @@ class NetworkdRATests(unittest.TestCase, Utilities):
self.assertIn('pref high', output)
self.assertNotIn('pref low', output)
def test_ndisc_vs_static_route(self):
copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-static-route.network')
start_networkd()
self.wait_online('veth99:routable', 'veth-peer:degraded')
output = check_output('ip -6 route show dev veth99 table all')
print(output)
# If a conflicting static route is already configured, do not override the static route.
output = check_output('ip -6 route show dev veth99 default via fe80::1034:56ff:fe78:9abd')
print(output)
self.assertIn('default proto static metric 256 pref medium', output)
self.assertNotIn('proto ra', output)
if not os.path.exists(test_ndisc_send):
self.skipTest(f"{test_ndisc_send} does not exist.")
# Also check if the static route is protected from RA with zero lifetime
check_output(f'{test_ndisc_send} --interface veth-peer --type router-advertisement --lifetime 0')
time.sleep(2)
output = check_output('ip -6 route show dev veth99 default via fe80::1034:56ff:fe78:9abd')
print(output)
self.assertIn('default proto static metric 256 pref medium', output)
# radvd supports captive portal since v2.20.
# https://github.com/radvd-project/radvd/commit/791179a7f730decbddb2290ef0e34aa85d71b1bc
@unittest.skipUnless(radvd_check_config('captive-portal.conf'), "Installed radvd doesn't support captive portals")