diff options
Diffstat (limited to 'tools/testing/selftests/net/nl_netdev.py')
| -rwxr-xr-x | tools/testing/selftests/net/nl_netdev.py | 284 |
1 files changed, 279 insertions, 5 deletions
diff --git a/tools/testing/selftests/net/nl_netdev.py b/tools/testing/selftests/net/nl_netdev.py index 93e8cb671c3d..ceb44c8e1fec 100755 --- a/tools/testing/selftests/net/nl_netdev.py +++ b/tools/testing/selftests/net/nl_netdev.py @@ -1,10 +1,15 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0 -import time -from lib.py import ksft_run, ksft_exit, ksft_pr -from lib.py import ksft_eq, ksft_ge, ksft_busy_wait -from lib.py import NetdevFamily, NetdevSimDev, ip +""" +Tests for the netdev netlink family. +""" + +import errno +from os import system +from lib.py import ksft_run, ksft_exit +from lib.py import ksft_eq, ksft_ge, ksft_ne, ksft_raises, ksft_busy_wait +from lib.py import NetdevFamily, NetdevSimDev, NlError, defer, ip def empty_check(nf) -> None: @@ -18,6 +23,15 @@ def lo_check(nf) -> None: ksft_eq(len(lo_info['xdp-rx-metadata-features']), 0) +def dev_dump_reject_attr(nf) -> None: + """Test that dev-get dump rejects attributes (no dump request policy).""" + with ksft_raises(NlError) as cm: + nf.dev_get({'ifindex': 1}, dump=True) + ksft_eq(cm.exception.nl_msg.error, -errno.EINVAL) + ksft_eq(cm.exception.nl_msg.extack['msg'], 'Unknown attribute type') + ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex') + + def napi_list_check(nf) -> None: with NetdevSimDev(queue_count=100) as nsimdev: nsim = nsimdev.nsims[0] @@ -34,6 +48,143 @@ def napi_list_check(nf) -> None: ksft_eq(len(napis), 100, comment=f"queue count after reset queue {q} mode {i}") +def napi_set_threaded(nf) -> None: + """ + Test that verifies various cases of napi threaded + set and unset at napi and device level. + """ + with NetdevSimDev(queue_count=2) as nsimdev: + nsim = nsimdev.nsims[0] + + ip(f"link set dev {nsim.ifname} up") + + napis = nf.napi_get({'ifindex': nsim.ifindex}, dump=True) + ksft_eq(len(napis), 2) + + napi0_id = napis[0]['id'] + napi1_id = napis[1]['id'] + + # set napi threaded and verify + nf.napi_set({'id': napi0_id, 'threaded': "enabled"}) + napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], "enabled") + ksft_ne(napi0.get('pid'), None) + + # check it is not set for napi1 + napi1 = nf.napi_get({'id': napi1_id}) + ksft_eq(napi1['threaded'], "disabled") + ksft_eq(napi1.get('pid'), None) + + ip(f"link set dev {nsim.ifname} down") + ip(f"link set dev {nsim.ifname} up") + + # verify if napi threaded is still set + napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], "enabled") + ksft_ne(napi0.get('pid'), None) + + # check it is still not set for napi1 + napi1 = nf.napi_get({'id': napi1_id}) + ksft_eq(napi1['threaded'], "disabled") + ksft_eq(napi1.get('pid'), None) + + # unset napi threaded and verify + nf.napi_set({'id': napi0_id, 'threaded': "disabled"}) + napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], "disabled") + ksft_eq(napi0.get('pid'), None) + + # set threaded at device level + system(f"echo 1 > /sys/class/net/{nsim.ifname}/threaded") + + # check napi threaded is set for both napis + napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], "enabled") + ksft_ne(napi0.get('pid'), None) + napi1 = nf.napi_get({'id': napi1_id}) + ksft_eq(napi1['threaded'], "enabled") + ksft_ne(napi1.get('pid'), None) + + # unset threaded at device level + system(f"echo 0 > /sys/class/net/{nsim.ifname}/threaded") + + # check napi threaded is unset for both napis + napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], "disabled") + ksft_eq(napi0.get('pid'), None) + napi1 = nf.napi_get({'id': napi1_id}) + ksft_eq(napi1['threaded'], "disabled") + ksft_eq(napi1.get('pid'), None) + + # set napi threaded for napi0 + nf.napi_set({'id': napi0_id, 'threaded': 1}) + napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], "enabled") + ksft_ne(napi0.get('pid'), None) + + # unset threaded at device level + system(f"echo 0 > /sys/class/net/{nsim.ifname}/threaded") + + # check napi threaded is unset for both napis + napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], "disabled") + ksft_eq(napi0.get('pid'), None) + napi1 = nf.napi_get({'id': napi1_id}) + ksft_eq(napi1['threaded'], "disabled") + ksft_eq(napi1.get('pid'), None) + +def dev_set_threaded(nf) -> None: + """ + Test that verifies various cases of napi threaded + set and unset at device level using sysfs. + """ + with NetdevSimDev(queue_count=2) as nsimdev: + nsim = nsimdev.nsims[0] + + ip(f"link set dev {nsim.ifname} up") + + napis = nf.napi_get({'ifindex': nsim.ifindex}, dump=True) + ksft_eq(len(napis), 2) + + napi0_id = napis[0]['id'] + napi1_id = napis[1]['id'] + + # set threaded + system(f"echo 1 > /sys/class/net/{nsim.ifname}/threaded") + + # check napi threaded is set for both napis + napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], "enabled") + ksft_ne(napi0.get('pid'), None) + napi1 = nf.napi_get({'id': napi1_id}) + ksft_eq(napi1['threaded'], "enabled") + ksft_ne(napi1.get('pid'), None) + + # unset threaded + system(f"echo 0 > /sys/class/net/{nsim.ifname}/threaded") + + # check napi threaded is unset for both napis + napi0 = nf.napi_get({'id': napi0_id}) + ksft_eq(napi0['threaded'], "disabled") + ksft_eq(napi0.get('pid'), None) + napi1 = nf.napi_get({'id': napi1_id}) + ksft_eq(napi1['threaded'], "disabled") + ksft_eq(napi1.get('pid'), None) + +def nsim_rxq_reset_down(nf) -> None: + """ + Test that the queue API supports resetting a queue + while the interface is down. We should convert this + test to testing real HW once more devices support + queue API. + """ + with NetdevSimDev(queue_count=4) as nsimdev: + nsim = nsimdev.nsims[0] + + ip(f"link set dev {nsim.ifname} down") + for i in [0, 2, 3]: + nsim.dfs_write("queue_reset", f"1 {i}") + def page_pool_check(nf) -> None: with NetdevSimDev() as nsimdev: @@ -104,9 +255,132 @@ def page_pool_check(nf) -> None: nsim.dfs_write("pp_hold", "y") +def page_pool_dump_ifindex(nf) -> None: + """Test page pool dump filtering by ifindex.""" + nsimdev1 = NetdevSimDev(queue_count=3) + rm_nsim1 = defer(nsimdev1.remove) + nsimdev2 = NetdevSimDev(queue_count=5) + defer(nsimdev2.remove) + + nsim1 = nsimdev1.nsims[0] + nsim2 = nsimdev2.nsims[0] + + ip(f"link set dev {nsim1.ifname} up") + ip(f"link set dev {nsim2.ifname} up") + + # Unfiltered dump should have pools from both devices + all_pp = nf.page_pool_get({}, dump=True) + pp1_all = [pp for pp in all_pp + if pp.get("ifindex") == nsim1.ifindex] + pp2_all = [pp for pp in all_pp + if pp.get("ifindex") == nsim2.ifindex] + ksft_ge(len(pp1_all), 1) + ksft_ge(len(pp2_all), 1) + + # Filtered dump should only return pools for that device + pp1_flt = nf.page_pool_get({'ifindex': nsim1.ifindex}, dump=True) + ksft_eq(pp1_flt, pp1_all) + + pp2_flt = nf.page_pool_get({'ifindex': nsim2.ifindex}, dump=True) + ksft_eq(pp2_flt, pp2_all) + + # Non-existent ifindex should return empty dump + pp_none = nf.page_pool_get({'ifindex': 12345678}, dump=True) + ksft_eq(len(pp_none), 0) + + # Device down - no pools for that ifindex + ip(f"link set dev {nsim1.ifname} down") + pp1_down = nf.page_pool_get({'ifindex': nsim1.ifindex}, dump=True) + ksft_eq(len(pp1_down), 0) + + # Remove device, dump by its old ifindex should return empty + old_ifindex = nsim1.ifindex + rm_nsim1.exec() + pp1_gone = nf.page_pool_get({'ifindex': old_ifindex}, dump=True) + ksft_eq(len(pp1_gone), 0) + + +def page_pool_ifindex_leak_check(nf) -> None: + """Test that zombie page pools don't show up under the original ifindex.""" + nsimdev = NetdevSimDev() + rm_nsim = defer(nsimdev.remove) + nsim = nsimdev.nsims[0] + + ip(f"link set dev {nsim.ifname} up") + nsim.dfs_write("pp_hold", "y") + + pp_up = nf.page_pool_get({'ifindex': nsim.ifindex}, dump=True) + ksft_ge(len(pp_up), 1) + + # Remove device with leaked page - pool becomes zombie (orphaned to lo) + old_ifindex = nsim.ifindex + rm_nsim.exec() + + # Zombie pool should NOT appear under the original device + pp_down = nf.page_pool_get({'ifindex': old_ifindex}, dump=True) + ksft_eq(len(pp_down), 0) + + # But it should appear in an unfiltered dump (under loopback) + pp_all = nf.page_pool_get({}, dump=True) + orphans = [pp for pp in pp_all + if "detach-time" in pp and "ifindex" not in pp] + ksft_ge(len(orphans), 1) + + +def page_pool_stats_ifindex_check(nf) -> None: + """Test page pool stats dump filtering by ifindex.""" + nsimdev1 = NetdevSimDev(queue_count=3) + defer(nsimdev1.remove) + nsimdev2 = NetdevSimDev(queue_count=5) + defer(nsimdev2.remove) + + nsim1 = nsimdev1.nsims[0] + nsim2 = nsimdev2.nsims[0] + + ip(f"link set dev {nsim1.ifname} up") + ip(f"link set dev {nsim2.ifname} up") + + # Unfiltered stats dump + all_stats = nf.page_pool_stats_get({}, dump=True) + s1_all = [s for s in all_stats + if s.get("info", {}).get("ifindex") == nsim1.ifindex] + s2_all = [s for s in all_stats + if s.get("info", {}).get("ifindex") == nsim2.ifindex] + ksft_ge(len(s1_all), 1) + ksft_ge(len(s2_all), 1) + + # Filtered stats dump + s1_flt = nf.page_pool_stats_get({'info': {'ifindex': nsim1.ifindex}}, + dump=True) + ksft_eq(s1_flt, s1_all) + + # Non-existent ifindex should return empty + s_none = nf.page_pool_stats_get({'info': {'ifindex': 12345678}}, dump=True) + ksft_eq(len(s_none), 0) + + # info.id should be rejected for stats dump + with ksft_raises(NlError) as cm: + nf.page_pool_stats_get({'info': {'id': s1_all[0]['info']['id']}}, + dump=True) + ksft_eq(cm.exception.nl_msg.error, -errno.EINVAL) + ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.info.id') + + def main() -> None: + """ Ksft boiler plate main """ nf = NetdevFamily() - ksft_run([empty_check, lo_check, page_pool_check, napi_list_check], + ksft_run([empty_check, + lo_check, + dev_dump_reject_attr, + napi_list_check, + napi_set_threaded, + dev_set_threaded, + nsim_rxq_reset_down, + page_pool_check, + page_pool_dump_ifindex, + page_pool_ifindex_leak_check, + page_pool_stats_ifindex_check + ], args=(nf, )) ksft_exit() |
