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:
parent
965b18779b
commit
fd21172d50
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user