route: workaround for removing IPv6 default gateway

Due to NM bug [1], Nmstate is not removing IPv6 default gateway. In
order to workaround this, Nmstate is deactivating first the interface if
an IPv6 route has been removed.

Integration test added.

[1] https://bugzilla.redhat.com/1837254

Signed-off-by: Fernando Fernandez Mancera <ffmancera@riseup.net>
This commit is contained in:
Fernando Fernandez Mancera 2021-02-09 12:49:55 +01:00 committed by Gris Ge
parent 965b18779b
commit fd21172d50
3 changed files with 62 additions and 2 deletions

View File

@ -49,6 +49,7 @@ from .translator import Api2Nm
IMPORT_NM_DEV_TIMEOUT = 5
IMPORT_NM_DEV_RETRY_INTERNAL = 0.5
FALLBACK_CHECKER_INTERNAL = 15
IPV6_ROUTE_REMOVED = "_ipv6_route_removed"
class NmProfile:
@ -162,6 +163,11 @@ class NmProfile:
elif self._iface.is_virtual and self._nm_dev:
self._add_action(NmProfile.ACTION_DELETE_DEVICE)
if self._iface.raw.get(IPV6_ROUTE_REMOVED):
# This is a workaround for NM bug:
# https://bugzilla.redhat.com/1837254
self._add_action(NmProfile.ACTION_DEACTIVATE_FIRST)
if self._iface.is_controller and self._iface.is_up:
if self._iface.controller:
self._add_action(NmProfile.ACTION_OTHER_MASTER)

View File

@ -33,6 +33,9 @@ from .state import StateEntry
from .state import state_match
IPV6_ROUTE_REMOVED = "_ipv6_route_removed"
class RouteEntry(StateEntry):
IPV4_DEFAULT_GATEWAY_DESTINATION = "0.0.0.0/0"
IPV6_DEFAULT_GATEWAY_DESTINATION = "::/0"
@ -211,6 +214,16 @@ class RouteState:
for route in route_set:
if not rt.match(route):
new_routes.add(route)
if route.is_ipv6:
# The routes match and therefore it is being removed.
# Nmstate will check if it is an IPv6 route and if so,
# marking the interface as deactivate first.
#
# This is a workaround for NM bug:
# https://bugzilla.redhat.com/1837254
ifaces.all_kernel_ifaces[iface_name].raw[
IPV6_ROUTE_REMOVED
] = True
if new_routes != route_set:
self._routes[iface_name] = new_routes

View File

@ -31,6 +31,7 @@ from libnmstate.schema import InterfaceType
from libnmstate.schema import Route
from libnmstate.schema import RouteRule
from .testlib import cmdlib
from .testlib import iprule
IPV4_ADDRESS1 = "192.0.2.251"
@ -45,6 +46,8 @@ IPV4_DNS_NAMESERVER = "8.8.8.8"
IPV6_DNS_NAMESERVER = "2001:4860:4860::8888"
DNS_SEARCHES = ["example.org", "example.com"]
IPV6_GATEWAY1 = "2001:db8:1::f"
IPV6_GATEWAY2 = "2001:db8:1::e"
ETH1_INTERFACE_STATE = {
Interface.NAME: "eth1",
@ -290,14 +293,14 @@ def _get_ipv6_gateways():
{
Route.DESTINATION: "::/0",
Route.METRIC: 103,
Route.NEXT_HOP_ADDRESS: "2001:db8:1::f",
Route.NEXT_HOP_ADDRESS: IPV6_GATEWAY1,
Route.NEXT_HOP_INTERFACE: "eth1",
Route.TABLE_ID: 254,
},
{
Route.DESTINATION: "::/0",
Route.METRIC: 101,
Route.NEXT_HOP_ADDRESS: "2001:db8:1::e",
Route.NEXT_HOP_ADDRESS: IPV6_GATEWAY2,
Route.NEXT_HOP_INTERFACE: "eth1",
Route.TABLE_ID: 254,
},
@ -502,6 +505,44 @@ def test_apply_empty_state_preserve_routes(eth1_static_gateway_dns):
assert current_state[DNS.KEY][DNS.CONFIG] == state[DNS.KEY][DNS.CONFIG]
def _get_routes_from_iproute():
_, out, _ = cmdlib.exec_cmd("ip -6 route".split(), check=True)
return out
def test_remove_default_ipv6_gateway_and_revert():
gateway1 = {
Route.DESTINATION: "::/0",
Route.METRIC: -1,
Route.NEXT_HOP_ADDRESS: IPV6_GATEWAY1,
Route.NEXT_HOP_INTERFACE: "eth1",
Route.TABLE_ID: 0,
}
gateway2 = {
Route.DESTINATION: "::/0",
Route.METRIC: -1,
Route.NEXT_HOP_ADDRESS: IPV6_GATEWAY2,
Route.NEXT_HOP_INTERFACE: "eth1",
Route.TABLE_ID: 0,
}
eth1 = copy.deepcopy(ETH1_INTERFACE_STATE)
d_state = {Interface.KEY: [eth1], Route.KEY: {Route.CONFIG: [gateway1]}}
libnmstate.apply(d_state)
gateway1[Route.STATE] = Route.STATE_ABSENT
d_state[Route.KEY][Route.CONFIG] = [gateway1, gateway2]
libnmstate.apply(d_state)
gateway1.pop(Route.STATE)
gateway2[Route.STATE] = Route.STATE_ABSENT
libnmstate.apply(d_state)
routes_output = _get_routes_from_iproute()
assert IPV6_GATEWAY1 in routes_output
assert IPV6_GATEWAY2 not in routes_output
@pytest.fixture(scope="function")
def route_rule_test_env(eth1_static_gateway_dns):
yield eth1_static_gateway_dns