mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-05-05 09:57:21 +02:00
17a55ddb19
Donald points out that the current naive implementation using dicts breaks if policy is recursive (child nest uses policy idx already used by its parent). Lean more into the NlPolicy class. This lets us "render" the policy on demand, when user accesses it. If someone wants to do an infinite walk that's on them :) Show policy info as attributes of the class and use dict format to descend into sub-policies for extra neatness. Reviewed-by: Donald Hunter <donald.hunter@gmail.com> Link: https://patch.msgid.link/20260313232047.2068518-1-kuba@kernel.org Signed-off-by: Jakub Kicinski <kuba@kernel.org>
132 lines
4.5 KiB
Python
Executable File
132 lines
4.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-2.0
|
|
|
|
"""
|
|
Tests for the nlctrl genetlink family (family info and policy dumps).
|
|
"""
|
|
|
|
from lib.py import ksft_run, ksft_exit
|
|
from lib.py import ksft_eq, ksft_ge, ksft_true, ksft_in, ksft_not_in
|
|
from lib.py import NetdevFamily, EthtoolFamily, NlctrlFamily
|
|
|
|
|
|
def getfamily_do(ctrl) -> None:
|
|
"""Query a single family by name and validate its ops."""
|
|
fam = ctrl.getfamily({'family-name': 'netdev'})
|
|
ksft_eq(fam['family-name'], 'netdev')
|
|
ksft_true(fam['family-id'] > 0)
|
|
|
|
# The format of ops is quite odd, [{$idx: {"id"...}}, {$idx: {"id"...}}]
|
|
# Discard the indices and re-key by command id.
|
|
ops_by_id = {v['id']: v for op in fam['ops'] for v in op.values()}
|
|
ksft_eq(len(ops_by_id), len(fam['ops']))
|
|
|
|
# All ops should have a policy (either do or dump has one)
|
|
for op in ops_by_id.values():
|
|
ksft_in('cmd-cap-haspol', op['flags'],
|
|
comment=f"op {op['id']} missing haspol")
|
|
|
|
# dev-get (id 1) should support both do and dump
|
|
ksft_in('cmd-cap-do', ops_by_id[1]['flags'])
|
|
ksft_in('cmd-cap-dump', ops_by_id[1]['flags'])
|
|
|
|
# qstats-get (id 12) is dump-only
|
|
ksft_not_in('cmd-cap-do', ops_by_id[12]['flags'])
|
|
ksft_in('cmd-cap-dump', ops_by_id[12]['flags'])
|
|
|
|
# napi-set (id 14) is do-only and requires admin
|
|
ksft_in('cmd-cap-do', ops_by_id[14]['flags'])
|
|
ksft_not_in('cmd-cap-dump', ops_by_id[14]['flags'])
|
|
ksft_in('admin-perm', ops_by_id[14]['flags'])
|
|
|
|
# Notification-only commands (dev-add/del/change-ntf etc.) must
|
|
# not appear in the ops list since they have no do/dump handlers.
|
|
for ntf_id in [2, 3, 4, 6, 7, 8]:
|
|
ksft_not_in(ntf_id, ops_by_id,
|
|
comment=f"ntf-only cmd {ntf_id} should not be in ops")
|
|
|
|
|
|
def getfamily_dump(ctrl) -> None:
|
|
"""Dump all families and verify expected entries."""
|
|
families = ctrl.getfamily({}, dump=True)
|
|
ksft_ge(len(families), 2)
|
|
|
|
names = [f['family-name'] for f in families]
|
|
ksft_in('nlctrl', names, comment="nlctrl not found in family dump")
|
|
ksft_in('netdev', names, comment="netdev not found in family dump")
|
|
|
|
|
|
def getpolicy_dump(_ctrl) -> None:
|
|
"""Dump policies for ops using get_policy() and validate results.
|
|
|
|
Test with netdev (split ops) where do and dump can have different
|
|
policies, and with ethtool (full ops) where they always share one.
|
|
"""
|
|
# -- netdev (split ops) --
|
|
ndev = NetdevFamily()
|
|
|
|
# dev-get: do has a real policy with ifindex, dump has no policy
|
|
# (only the reject-all policy with maxattr=0)
|
|
pol = ndev.get_policy('dev-get', 'do')
|
|
ksft_in('ifindex', pol, comment="dev-get do policy should have ifindex")
|
|
ksft_eq(pol['ifindex'].type, 'u32')
|
|
|
|
pol_dump = ndev.get_policy('dev-get', 'dump')
|
|
ksft_eq(len(pol_dump), 0, comment="dev-get should not accept any attrs")
|
|
|
|
# napi-get: both do and dump have real policies
|
|
pol_do = ndev.get_policy('napi-get', 'do')
|
|
ksft_ge(len(pol_do), 1)
|
|
|
|
pol_dump = ndev.get_policy('napi-get', 'dump')
|
|
ksft_ge(len(pol_dump), 1)
|
|
|
|
# -- ethtool (full ops) --
|
|
et = EthtoolFamily()
|
|
|
|
# strset-get (has both do and dump, full ops share policy)
|
|
pol_do = et.get_policy('strset-get', 'do')
|
|
ksft_ge(len(pol_do), 1, comment="strset-get should have a do policy")
|
|
|
|
pol_dump = et.get_policy('strset-get', 'dump')
|
|
ksft_ge(len(pol_dump), 1, comment="strset-get should have a dump policy")
|
|
|
|
# Same policy means same attribute names
|
|
ksft_eq(set(pol_do.keys()), set(pol_dump.keys()))
|
|
|
|
# linkinfo-set is do-only (SET command), no dump
|
|
pol_do = et.get_policy('linkinfo-set', 'do')
|
|
ksft_ge(len(pol_do), 1, comment="linkinfo-set should have a do policy")
|
|
|
|
pol_dump = et.get_policy('linkinfo-set', 'dump')
|
|
ksft_eq(pol_dump, None,
|
|
comment="linkinfo-set should not have a dump policy")
|
|
|
|
|
|
def getpolicy_by_op(_ctrl) -> None:
|
|
"""Query policy for specific ops, check attr names are resolved."""
|
|
ndev = NetdevFamily()
|
|
|
|
# dev-get do policy should have named attributes from the spec
|
|
pol = ndev.get_policy('dev-get', 'do')
|
|
ksft_ge(len(pol), 1)
|
|
# All attr names should be resolved (no 'attr-N' fallbacks)
|
|
for name in pol:
|
|
ksft_true(not name.startswith('attr-'),
|
|
comment=f"unresolved attr name: {name}")
|
|
|
|
|
|
def main() -> None:
|
|
""" Ksft boiler plate main """
|
|
ctrl = NlctrlFamily()
|
|
ksft_run([getfamily_do,
|
|
getfamily_dump,
|
|
getpolicy_dump,
|
|
getpolicy_by_op],
|
|
args=(ctrl, ))
|
|
ksft_exit()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|