diff --git a/tests/atf_python/sys/net/tools.py b/tests/atf_python/sys/net/tools.py --- a/tests/atf_python/sys/net/tools.py +++ b/tests/atf_python/sys/net/tools.py @@ -41,7 +41,7 @@ def get_routes(cls, family: str, fibnum: int = 0): family_key = {"inet": "-4", "inet6": "-6"}.get(family) out = cls.get_output( - "{} {} -rn -F {} --libxo json".format(cls.NETSTAT_PATH, family_key, fibnum) + "{} {} -rnW -F {} --libxo json".format(cls.NETSTAT_PATH, family_key, fibnum) ) js = json.loads(out) js = js["statistics"]["route-information"]["route-table"]["rt-family"] @@ -50,6 +50,19 @@ else: return [] + @classmethod + def get_nhops(cls, family: str, fibnum: int = 0): + family_key = {"inet": "-4", "inet6": "-6"}.get(family) + out = cls.get_output( + "{} {} -onW -F {} --libxo json".format(cls.NETSTAT_PATH, family_key, fibnum) + ) + js = json.loads(out) + js = js["statistics"]["route-nhop-information"]["nhop-table"]["rt-family"] + if js: + return js[0]["nh-entry"] + else: + return [] + @classmethod def get_linklocals(cls): ret = {} diff --git a/tests/atf_python/sys/net/vnet.py b/tests/atf_python/sys/net/vnet.py --- a/tests/atf_python/sys/net/vnet.py +++ b/tests/atf_python/sys/net/vnet.py @@ -101,11 +101,15 @@ addr = ipaddress.ip_interface(_addr) if addr.version == 6: family = "inet6" + cmd = "/sbin/ifconfig {} {} {}".format(self.name, family, addr) else: family = "inet" - cmd = "/sbin/ifconfig {} {} {}".format(self.name, family, addr) + if self.addr_map[family]: + cmd = "/sbin/ifconfig {} alias {}".format(self.name, addr) + else: + cmd = "/sbin/ifconfig {} {} {}".format(self.name, family, addr) self.run_cmd(cmd) - self.addr_map[family][str(addr)] = addr + self.addr_map[family][str(addr.ip)] = addr def delete_addr(self, _addr: str): addr = ipaddress.ip_address(_addr) diff --git a/tests/sys/net/routing/Makefile b/tests/sys/net/routing/Makefile --- a/tests/sys/net/routing/Makefile +++ b/tests/sys/net/routing/Makefile @@ -7,6 +7,7 @@ ATF_TESTS_C += test_rtsock_l3 ATF_TESTS_C += test_rtsock_lladdr +ATF_TESTS_PYTEST += test_routing_l3.py ATF_TESTS_PYTEST += test_rtsock_multipath.py ${PACKAGE}FILES+= generic_cleanup.sh diff --git a/tests/sys/net/routing/test_routing_l3.py b/tests/sys/net/routing/test_routing_l3.py new file mode 100755 --- /dev/null +++ b/tests/sys/net/routing/test_routing_l3.py @@ -0,0 +1,81 @@ +import ipaddress + +import pytest +from atf_python.sys.net.tools import ToolsHelper +from atf_python.sys.net.vnet import VnetTestTemplate + + +class TestIfOps(VnetTestTemplate): + TOPOLOGY = { + "vnet1": {"ifaces": ["if1", "if2"]}, + "if1": {"prefixes4": [], "prefixes6": []}, + "if2": {"prefixes4": [], "prefixes6": []}, + } + + @pytest.mark.parametrize("family", ["inet", "inet6"]) + @pytest.mark.require_user("root") + def test_change_prefix_route(self, family): + """Tests that prefix route changes to the new one upon addr deletion""" + vnet = self.vnet_map["vnet1"] + first_iface = vnet.iface_alias_map["if1"] + second_iface = vnet.iface_alias_map["if2"] + if family == "inet": + first_addr = ipaddress.ip_interface("192.0.2.1/24") + second_addr = ipaddress.ip_interface("192.0.2.2/24") + else: + first_addr = ipaddress.ip_interface("2001:db8::1/64") + second_addr = ipaddress.ip_interface("2001:db8::2/64") + + first_iface.setup_addr(str(first_addr)) + second_iface.setup_addr(str(second_addr)) + + # At this time prefix should be pointing to the first interface + routes = ToolsHelper.get_routes(family) + px = [r for r in routes if r["destination"] == str(first_addr.network)][0] + assert px["interface-name"] == first_iface.name + + # Now delete address from the first interface and verify switchover + first_iface.delete_addr(first_addr.ip) + + routes = ToolsHelper.get_routes(family) + px = [r for r in routes if r["destination"] == str(first_addr.network)][0] + assert px["interface-name"] == second_iface.name + + @pytest.mark.parametrize( + "family", + [ + "inet", + pytest.param("inet6", marks=pytest.mark.xfail(reason="currently fails")), + ], + ) + @pytest.mark.require_user("root") + def test_change_prefix_route_same_iface(self, family): + """Tests that prefix route changes to the new ifa upon addr deletion""" + vnet = self.vnet_map["vnet1"] + first_iface = vnet.iface_alias_map["if1"] + + if family == "inet": + first_addr = ipaddress.ip_interface("192.0.2.1/24") + second_addr = ipaddress.ip_interface("192.0.2.2/24") + else: + first_addr = ipaddress.ip_interface("2001:db8::1/64") + second_addr = ipaddress.ip_interface("2001:db8::2/64") + + first_iface.setup_addr(str(first_addr)) + first_iface.setup_addr(str(second_addr)) + + # At this time prefix should be pointing to the first interface + routes = ToolsHelper.get_routes(family) + px = [r for r in routes if r["destination"] == str(first_addr.network)][0] + assert px["interface-name"] == first_iface.name + + # Now delete address from the first interface and verify switchover + first_iface.delete_addr(str(first_addr.ip)) + + routes = ToolsHelper.get_routes(family) + px = [r for r in routes if r["destination"] == str(first_addr.network)][0] + nhop_kidx = px["nhop"] + assert px["interface-name"] == first_iface.name + nhops = ToolsHelper.get_nhops(family) + nh = [nh for nh in nhops if nh["index"] == nhop_kidx][0] + assert nh["ifa"] == str(second_addr.ip)