#!/usr/bin/env python3# SPDX-License-Identifier: GPL-2.0 """Tests for the netdev netlink family.""" import errnofrom os import systemfrom lib.py import ksft_run, ksft_exitfrom lib.py import ksft_eq, ksft_ge, ksft_ne, ksft_raises, ksft_busy_waitfrom lib.py import NetdevFamily, NetdevSimDev, NlError, defer, ip def empty_check(nf) -> None: devs = nf.dev_get({}, dump=True) ksft_ge(len(devs), 1) def lo_check(nf) -> None: lo_info = nf.dev_get({"ifindex": 1}) ksft_eq(len(lo_info['xdp-features']), 0) 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] ip(f"link set dev {nsim.ifname} up") napis = nf.napi_get({'ifindex': nsim.ifindex}, dump=True) ksft_eq(len(napis), 100) for q in [50, 0, 99]: for i in range(4): nsim.dfs_write("queue_reset", f"{q} {i}") napis = nf.napi_get({'ifindex': nsim.ifindex}, dump=True) 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: nsim = nsimdev.nsims[0] def up(): ip(f"link set dev {nsim.ifname} up") def down(): ip(f"link set dev {nsim.ifname} down") def get_pp(): pp_list = nf.page_pool_get({}, dump=True) return [pp for pp in pp_list if pp.get("ifindex") == nsim.ifindex] # No page pools when down down() ksft_eq(len(get_pp()), 0) # Up, empty page pool appears up() pp_list = get_pp() ksft_ge(len(pp_list), 0) refs = sum([pp["inflight"] for pp in pp_list]) ksft_eq(refs, 0) # Down, it disappears, again down() pp_list = get_pp() ksft_eq(len(pp_list), 0) # Up, allocate a page up() nsim.dfs_write("pp_hold", "y") pp_list = nf.page_pool_get({}, dump=True) refs = sum([pp["inflight"] for pp in pp_list if pp.get("ifindex") == nsim.ifindex]) ksft_ge(refs, 1) # Now let's leak a page down() pp_list = get_pp() ksft_eq(len(pp_list), 1) refs = sum([pp["inflight"] for pp in pp_list]) ksft_eq(refs, 1) attached = [pp for pp in pp_list if "detach-time" not in pp] ksft_eq(len(attached), 0) # New pp can get created, and we'll have two up() pp_list = get_pp() attached = [pp for pp in pp_list if "detach-time" not in pp] detached = [pp for pp in pp_list if "detach-time" in pp] ksft_eq(len(attached), 1) ksft_eq(len(detached), 1) # Free the old page and the old pp is gone nsim.dfs_write("pp_hold", "n") # Freeing check is once a second so we may need to retry ksft_busy_wait(lambda: len(get_pp()) == 1, deadline=2) # And down... down() ksft_eq(len(get_pp()), 0) # Last, leave the page hanging for destroy, nothing to check # we're trying to exercise the orphaning path in the kernel up() 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, 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() if __name__ == "__main__": main()
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
"""
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:
devs = nf.dev_get({}, dump=True)
ksft_ge(len(devs), 1)
def lo_check(nf) -> None:
lo_info = nf.dev_get({"ifindex": 1})
ksft_eq(len(lo_info['xdp-features']), 0)
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]
ip(f"link set dev {nsim.ifname} up")
napis = nf.napi_get({'ifindex': nsim.ifindex}, dump=True)
ksft_eq(len(napis), 100)
for q in [50, 0, 99]:
for i in range(4):
nsim.dfs_write("queue_reset", f"{q} {i}")
napis = nf.napi_get({'ifindex': nsim.ifindex}, dump=True)
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:
nsim = nsimdev.nsims[0]
def up():
ip(f"link set dev {nsim.ifname} up")
def down():
ip(f"link set dev {nsim.ifname} down")
def get_pp():
pp_list = nf.page_pool_get({}, dump=True)
return [pp for pp in pp_list if pp.get("ifindex") == nsim.ifindex]
# No page pools when down
down()
ksft_eq(len(get_pp()), 0)
# Up, empty page pool appears
up()
pp_list = get_pp()
ksft_ge(len(pp_list), 0)
refs = sum([pp["inflight"] for pp in pp_list])
ksft_eq(refs, 0)
# Down, it disappears, again
down()
pp_list = get_pp()
ksft_eq(len(pp_list), 0)
# Up, allocate a page
up()
nsim.dfs_write("pp_hold", "y")
pp_list = nf.page_pool_get({}, dump=True)
refs = sum([pp["inflight"] for pp in pp_list if pp.get("ifindex") == nsim.ifindex])
ksft_ge(refs, 1)
# Now let's leak a page
down()
pp_list = get_pp()
ksft_eq(len(pp_list), 1)
refs = sum([pp["inflight"] for pp in pp_list])
ksft_eq(refs, 1)
attached = [pp for pp in pp_list if "detach-time" not in pp]
ksft_eq(len(attached), 0)
# New pp can get created, and we'll have two
up()
pp_list = get_pp()
attached = [pp for pp in pp_list if "detach-time" not in pp]
detached = [pp for pp in pp_list if "detach-time" in pp]
ksft_eq(len(attached), 1)
ksft_eq(len(detached), 1)
# Free the old page and the old pp is gone
nsim.dfs_write("pp_hold", "n")
# Freeing check is once a second so we may need to retry
ksft_busy_wait(lambda: len(get_pp()) == 1, deadline=2)
# And down...
down()
ksft_eq(len(get_pp()), 0)
# Last, leave the page hanging for destroy, nothing to check
# we're trying to exercise the orphaning path in the kernel
up()
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,
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()
if __name__ == "__main__":
main()