projects/linux·blob master · refs

213 lines · 7.7 KiB

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# Families we can expect to always be around, and which between them
13# cover ops with a do, with a dump, and with both.
14FAMILIES = ('nlctrl', 'netdev')
15
16
17def _get_ops(ctrl, name):
18 """Get the ops of a family, keyed by command id."""
19 fam = ctrl.getfamily({'family-name': name})
20 ksft_eq(fam['family-name'], name)
21 ksft_true(fam['family-id'] > 0)
22
23 # The format of ops is quite odd, [{$idx: {"id"...}}, {$idx: {"id"...}}]
24 # Discard the indices and re-key by command id.
25 ops_by_id = {v['id']: v for op in fam['ops'] for v in op.values()}
26 ksft_eq(len(ops_by_id), len(fam['ops']),
27 comment=f"{name} lists a command twice")
28 return ops_by_id
29
30
31def _get_policy_map(ctrl, req):
32 """
33 The policy map in the Netlink replies looks like this:
34
35 [{'family-id': 16, 'op-policy': {'do': 0, 'dump': 0, 'op-id': 3}},
36 {'family-id': 16, 'op-policy': {'dump': 1, 'op-id': 4}}, ...]
37
38 Return the mapping:
39
40 {3:{'do','dump'}, 4:{'dump'}}
41
42 The policy itself is discarded here, only return which command has policy.
43 """
44 pol_map = {}
45 for msg in ctrl.getpolicy(req, dump=True):
46 if 'op-policy' not in msg:
47 continue
48 modes = dict(msg['op-policy'])
49 cmd = modes.pop('op-id')
50 ksft_not_in(cmd, pol_map, comment=f"command {cmd} reported twice")
51 pol_map[cmd] = set(modes.keys())
52 return pol_map
53
54
55def getfamily_do(ctrl) -> None:
56 """Query single families by name and validate their ops."""
57 ops = {name: _get_ops(ctrl, name) for name in FAMILIES}
58
59 for name, ops_by_id in ops.items():
60 for op in ops_by_id.values():
61 # All ops in nlctrl and netdev have a policy
62 ksft_in('cmd-cap-haspol', op['flags'],
63 comment=f"{name} op {op['id']} missing haspol")
64 ksft_true(op['flags'] & {'cmd-cap-do', 'cmd-cap-dump'},
65 comment=f"{name} op {op['id']} has no handler")
66
67 # nlctrl getfamily (id 3) does both, getpolicy (id 10) is dump-only
68 ksft_in('cmd-cap-do', ops['nlctrl'][3]['flags'])
69 ksft_in('cmd-cap-dump', ops['nlctrl'][3]['flags'])
70 ksft_not_in('cmd-cap-do', ops['nlctrl'][10]['flags'])
71 ksft_in('cmd-cap-dump', ops['nlctrl'][10]['flags'])
72
73 netdev = ops['netdev']
74
75 # dev-get (id 1) should support both do and dump
76 ksft_in('cmd-cap-do', netdev[1]['flags'])
77 ksft_in('cmd-cap-dump', netdev[1]['flags'])
78
79 # qstats-get (id 12) is dump-only
80 ksft_not_in('cmd-cap-do', netdev[12]['flags'])
81 ksft_in('cmd-cap-dump', netdev[12]['flags'])
82
83 # napi-set (id 14) is do-only and requires admin
84 ksft_in('cmd-cap-do', netdev[14]['flags'])
85 ksft_not_in('cmd-cap-dump', netdev[14]['flags'])
86 ksft_in('admin-perm', netdev[14]['flags'])
87
88 # Notification-only commands (dev-add/del/change-ntf etc.) must
89 # not appear in the ops list since they have no do/dump handlers.
90 for ntf_id in [2, 3, 4, 6, 7, 8]:
91 ksft_not_in(ntf_id, netdev,
92 comment=f"ntf-only cmd {ntf_id} should not be in ops")
93
94
95def getfamily_dump(ctrl) -> None:
96 """Dump all families and verify expected entries."""
97 families = ctrl.getfamily({}, dump=True)
98 ksft_ge(len(families), 2)
99
100 names = [f['family-name'] for f in families]
101 ksft_in('nlctrl', names, comment="nlctrl not found in family dump")
102 ksft_in('netdev', names, comment="netdev not found in family dump")
103
104
105def getpolicy_dump(_ctrl) -> None:
106 """Dump policies for ops using get_policy() and validate results.
107
108 Test with netdev (split ops) where do and dump can have different
109 policies, and with ethtool (full ops) where they always share one.
110 """
111 # -- netdev (split ops) --
112 ndev = NetdevFamily()
113
114 # dev-get: do has a real policy with ifindex, dump has no policy
115 # (only the reject-all policy with maxattr=0)
116 pol = ndev.get_policy('dev-get', 'do')
117 ksft_in('ifindex', pol, comment="dev-get do policy should have ifindex")
118 ksft_eq(pol['ifindex'].type, 'u32')
119
120 pol_dump = ndev.get_policy('dev-get', 'dump')
121 ksft_eq(len(pol_dump), 0, comment="dev-get should not accept any attrs")
122
123 # napi-get: both do and dump have real policies
124 pol_do = ndev.get_policy('napi-get', 'do')
125 ksft_ge(len(pol_do), 1)
126
127 pol_dump = ndev.get_policy('napi-get', 'dump')
128 ksft_ge(len(pol_dump), 1)
129
130 # -- ethtool (full ops) --
131 et = EthtoolFamily()
132
133 # strset-get (has both do and dump, full ops share policy)
134 pol_do = et.get_policy('strset-get', 'do')
135 ksft_ge(len(pol_do), 1, comment="strset-get should have a do policy")
136
137 pol_dump = et.get_policy('strset-get', 'dump')
138 ksft_ge(len(pol_dump), 1, comment="strset-get should have a dump policy")
139
140 # Same policy means same attribute names
141 ksft_eq(set(pol_do.keys()), set(pol_dump.keys()))
142
143 # linkinfo-set is do-only (SET command), no dump
144 pol_do = et.get_policy('linkinfo-set', 'do')
145 ksft_ge(len(pol_do), 1, comment="linkinfo-set should have a do policy")
146
147 pol_dump = et.get_policy('linkinfo-set', 'dump')
148 ksft_eq(pol_dump, None,
149 comment="linkinfo-set should not have a dump policy")
150
151
152def getpolicy_op_map(ctrl) -> None:
153 """Check the op-to-policy map consistency. Each op with 'haspol' flag
154 has to have a policy. The policy back-references must name only
155 real ops that exist, have given modes (do vs dump) and have 'haspol'.
156 """
157 for name in FAMILIES:
158 ops_by_id = _get_ops(ctrl, name)
159 haspol = {cmd for cmd, op in ops_by_id.items()
160 if 'cmd-cap-haspol' in op['flags']}
161
162 pol_map = _get_policy_map(ctrl, {'family-name': name})
163 ksft_eq(set(pol_map), haspol,
164 comment=f"{name} policy map does not match the op list")
165
166 # Walk the op list rather than the map, the map may be missing
167 # the very op we are after. Asking for a command the family does
168 # not have is an error, so it must not come from the map either.
169 for cmd in sorted(haspol):
170 modes = pol_map.get(cmd, set())
171
172 # The kernel only reports a mode the op actually has.
173 if 'do' in modes:
174 ksft_in('cmd-cap-do', ops_by_id[cmd]['flags'],
175 comment=f"{name} cmd {cmd} has no do")
176 if 'dump' in modes:
177 ksft_in('cmd-cap-dump', ops_by_id[cmd]['flags'],
178 comment=f"{name} cmd {cmd} has no dump")
179
180 # Asking for one op builds the map in a different place in
181 # the kernel, it has to report what the full dump did.
182 single = _get_policy_map(ctrl, {'family-name': name, 'op': cmd})
183 ksft_eq(single, {cmd: modes},
184 comment=f"{name} cmd {cmd} policy differs from the dump")
185
186
187def getpolicy_by_op(_ctrl) -> None:
188 """Query policy for specific ops, check attr names are resolved."""
189 ndev = NetdevFamily()
190
191 # dev-get do policy should have named attributes from the spec
192 pol = ndev.get_policy('dev-get', 'do')
193 ksft_ge(len(pol), 1)
194 # All attr names should be resolved (no 'attr-N' fallbacks)
195 for name in pol:
196 ksft_true(not name.startswith('attr-'),
197 comment=f"unresolved attr name: {name}")
198
199
200def main() -> None:
201 """ Ksft boiler plate main """
202 ctrl = NlctrlFamily()
203 ksft_run([getfamily_do,
204 getfamily_dump,
205 getpolicy_dump,
206 getpolicy_op_map,
207 getpolicy_by_op],
208 args=(ctrl, ))
209 ksft_exit()
210
211
212if __name__ == "__main__":
213 main()