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