xref: /linux/tools/testing/selftests/net/nl_nlctrl.py (revision c1f9a89b0c901266028e66cd8e6bdf54c8c3042e)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3
4"""
5Tests for the nlctrl genetlink family (family info and policy dumps).
6"""
7
8from lib.py import ksft_run, ksft_exit
9from lib.py import ksft_eq, ksft_ge, ksft_true, ksft_in, ksft_not_in
10from lib.py import NetdevFamily, EthtoolFamily, NlctrlFamily
11
12
13def getfamily_do(ctrl) -> None:
14    """Query a single family by name and validate its ops."""
15    fam = ctrl.getfamily({'family-name': 'netdev'})
16    ksft_eq(fam['family-name'], 'netdev')
17    ksft_true(fam['family-id'] > 0)
18
19    # The format of ops is quite odd, [{$idx: {"id"...}}, {$idx: {"id"...}}]
20    # Discard the indices and re-key by command id.
21    ops_by_id = {v['id']: v for op in fam['ops'] for v in op.values()}
22    ksft_eq(len(ops_by_id), len(fam['ops']))
23
24    # All ops should have a policy (either do or dump has one)
25    for op in ops_by_id.values():
26        ksft_in('cmd-cap-haspol', op['flags'],
27                comment=f"op {op['id']} missing haspol")
28
29    # dev-get (id 1) should support both do and dump
30    ksft_in('cmd-cap-do', ops_by_id[1]['flags'])
31    ksft_in('cmd-cap-dump', ops_by_id[1]['flags'])
32
33    # qstats-get (id 12) is dump-only
34    ksft_not_in('cmd-cap-do', ops_by_id[12]['flags'])
35    ksft_in('cmd-cap-dump', ops_by_id[12]['flags'])
36
37    # napi-set (id 14) is do-only and requires admin
38    ksft_in('cmd-cap-do', ops_by_id[14]['flags'])
39    ksft_not_in('cmd-cap-dump', ops_by_id[14]['flags'])
40    ksft_in('admin-perm', ops_by_id[14]['flags'])
41
42    # Notification-only commands (dev-add/del/change-ntf etc.) must
43    # not appear in the ops list since they have no do/dump handlers.
44    for ntf_id in [2, 3, 4, 6, 7, 8]:
45        ksft_not_in(ntf_id, ops_by_id,
46                    comment=f"ntf-only cmd {ntf_id} should not be in ops")
47
48
49def getfamily_dump(ctrl) -> None:
50    """Dump all families and verify expected entries."""
51    families = ctrl.getfamily({}, dump=True)
52    ksft_ge(len(families), 2)
53
54    names = [f['family-name'] for f in families]
55    ksft_in('nlctrl', names, comment="nlctrl not found in family dump")
56    ksft_in('netdev', names, comment="netdev not found in family dump")
57
58
59def getpolicy_dump(_ctrl) -> None:
60    """Dump policies for ops using get_policy() and validate results.
61
62    Test with netdev (split ops) where do and dump can have different
63    policies, and with ethtool (full ops) where they always share one.
64    """
65    # -- netdev (split ops) --
66    ndev = NetdevFamily()
67
68    # dev-get: do has a real policy with ifindex, dump has no policy
69    # (only the reject-all policy with maxattr=0)
70    pol = ndev.get_policy('dev-get', 'do')
71    ksft_in('ifindex', pol.attrs,
72            comment="dev-get do policy should have ifindex")
73    ksft_eq(pol.attrs.get('ifindex', {}).get('type'), 'u32')
74
75    pol_dump = ndev.get_policy('dev-get', 'dump')
76    ksft_eq(len(pol_dump.attrs), 0,
77            comment="dev-get should not accept any attrs")
78
79    # napi-get: both do and dump have real policies
80    pol_do = ndev.get_policy('napi-get', 'do')
81    ksft_ge(len(pol_do.attrs), 1)
82
83    pol_dump = ndev.get_policy('napi-get', 'dump')
84    ksft_ge(len(pol_dump.attrs), 1)
85
86    # -- ethtool (full ops) --
87    et = EthtoolFamily()
88
89    # strset-get (has both do and dump, full ops share policy)
90    pol_do = et.get_policy('strset-get', 'do')
91    ksft_ge(len(pol_do.attrs), 1, comment="strset-get should have a do policy")
92
93    pol_dump = et.get_policy('strset-get', 'dump')
94    ksft_ge(len(pol_dump.attrs), 1,
95            comment="strset-get should have a dump policy")
96
97    # Same policy means same attribute names
98    ksft_eq(set(pol_do.attrs.keys()), set(pol_dump.attrs.keys()))
99
100    # linkinfo-set is do-only (SET command), no dump
101    pol_do = et.get_policy('linkinfo-set', 'do')
102    ksft_ge(len(pol_do.attrs), 1,
103            comment="linkinfo-set should have a do policy")
104
105    pol_dump = et.get_policy('linkinfo-set', 'dump')
106    ksft_eq(pol_dump, None,
107            comment="linkinfo-set should not have a dump policy")
108
109
110def getpolicy_by_op(_ctrl) -> None:
111    """Query policy for specific ops, check attr names are resolved."""
112    ndev = NetdevFamily()
113
114    # dev-get do policy should have named attributes from the spec
115    pol = ndev.get_policy('dev-get', 'do')
116    ksft_ge(len(pol.attrs), 1)
117    # All attr names should be resolved (no 'attr-N' fallbacks)
118    for name in pol.attrs:
119        ksft_true(not name.startswith('attr-'),
120                  comment=f"unresolved attr name: {name}")
121
122
123def main() -> None:
124    """ Ksft boiler plate main """
125    ctrl = NlctrlFamily()
126    ksft_run([getfamily_do,
127              getfamily_dump,
128              getpolicy_dump,
129              getpolicy_by_op],
130             args=(ctrl, ))
131    ksft_exit()
132
133
134if __name__ == "__main__":
135    main()
136