summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/net/nl_netdev.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing/selftests/net/nl_netdev.py')
-rwxr-xr-xtools/testing/selftests/net/nl_netdev.py284
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()