projects/linux·blob master · refs

389 lines · 12.5 KiB

1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3
4"""
5Tests for the netdev netlink family.
6"""
7
8import errno
9from os import system
10from lib.py import ksft_run, ksft_exit
11from lib.py import ksft_eq, ksft_ge, ksft_ne, ksft_raises, ksft_busy_wait
12from lib.py import NetdevFamily, NetdevSimDev, NlError, defer, ip
13
14
15def empty_check(nf) -> None:
16 devs = nf.dev_get({}, dump=True)
17 ksft_ge(len(devs), 1)
18
19
20def lo_check(nf) -> None:
21 lo_info = nf.dev_get({"ifindex": 1})
22 ksft_eq(len(lo_info['xdp-features']), 0)
23 ksft_eq(len(lo_info['xdp-rx-metadata-features']), 0)
24
25
26def dev_dump_reject_attr(nf) -> None:
27 """Test that dev-get dump rejects attributes (no dump request policy)."""
28 with ksft_raises(NlError) as cm:
29 nf.dev_get({'ifindex': 1}, dump=True)
30 ksft_eq(cm.exception.nl_msg.error, -errno.EINVAL)
31 ksft_eq(cm.exception.nl_msg.extack['msg'], 'Unknown attribute type')
32 ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')
33
34
35def napi_list_check(nf) -> None:
36 with NetdevSimDev(queue_count=100) as nsimdev:
37 nsim = nsimdev.nsims[0]
38
39 ip(f"link set dev {nsim.ifname} up")
40
41 napis = nf.napi_get({'ifindex': nsim.ifindex}, dump=True)
42 ksft_eq(len(napis), 100)
43
44 for q in [50, 0, 99]:
45 for i in range(4):
46 nsim.dfs_write("queue_reset", f"{q} {i}")
47 napis = nf.napi_get({'ifindex': nsim.ifindex}, dump=True)
48 ksft_eq(len(napis), 100,
49 comment=f"queue count after reset queue {q} mode {i}")
50
51def napi_set_threaded(nf) -> None:
52 """
53 Test that verifies various cases of napi threaded
54 set and unset at napi and device level.
55 """
56 with NetdevSimDev(queue_count=2) as nsimdev:
57 nsim = nsimdev.nsims[0]
58
59 ip(f"link set dev {nsim.ifname} up")
60
61 napis = nf.napi_get({'ifindex': nsim.ifindex}, dump=True)
62 ksft_eq(len(napis), 2)
63
64 napi0_id = napis[0]['id']
65 napi1_id = napis[1]['id']
66
67 # set napi threaded and verify
68 nf.napi_set({'id': napi0_id, 'threaded': "enabled"})
69 napi0 = nf.napi_get({'id': napi0_id})
70 ksft_eq(napi0['threaded'], "enabled")
71 ksft_ne(napi0.get('pid'), None)
72
73 # check it is not set for napi1
74 napi1 = nf.napi_get({'id': napi1_id})
75 ksft_eq(napi1['threaded'], "disabled")
76 ksft_eq(napi1.get('pid'), None)
77
78 ip(f"link set dev {nsim.ifname} down")
79 ip(f"link set dev {nsim.ifname} up")
80
81 # verify if napi threaded is still set
82 napi0 = nf.napi_get({'id': napi0_id})
83 ksft_eq(napi0['threaded'], "enabled")
84 ksft_ne(napi0.get('pid'), None)
85
86 # check it is still not set for napi1
87 napi1 = nf.napi_get({'id': napi1_id})
88 ksft_eq(napi1['threaded'], "disabled")
89 ksft_eq(napi1.get('pid'), None)
90
91 # unset napi threaded and verify
92 nf.napi_set({'id': napi0_id, 'threaded': "disabled"})
93 napi0 = nf.napi_get({'id': napi0_id})
94 ksft_eq(napi0['threaded'], "disabled")
95 ksft_eq(napi0.get('pid'), None)
96
97 # set threaded at device level
98 system(f"echo 1 > /sys/class/net/{nsim.ifname}/threaded")
99
100 # check napi threaded is set for both napis
101 napi0 = nf.napi_get({'id': napi0_id})
102 ksft_eq(napi0['threaded'], "enabled")
103 ksft_ne(napi0.get('pid'), None)
104 napi1 = nf.napi_get({'id': napi1_id})
105 ksft_eq(napi1['threaded'], "enabled")
106 ksft_ne(napi1.get('pid'), None)
107
108 # unset threaded at device level
109 system(f"echo 0 > /sys/class/net/{nsim.ifname}/threaded")
110
111 # check napi threaded is unset for both napis
112 napi0 = nf.napi_get({'id': napi0_id})
113 ksft_eq(napi0['threaded'], "disabled")
114 ksft_eq(napi0.get('pid'), None)
115 napi1 = nf.napi_get({'id': napi1_id})
116 ksft_eq(napi1['threaded'], "disabled")
117 ksft_eq(napi1.get('pid'), None)
118
119 # set napi threaded for napi0
120 nf.napi_set({'id': napi0_id, 'threaded': 1})
121 napi0 = nf.napi_get({'id': napi0_id})
122 ksft_eq(napi0['threaded'], "enabled")
123 ksft_ne(napi0.get('pid'), None)
124
125 # unset threaded at device level
126 system(f"echo 0 > /sys/class/net/{nsim.ifname}/threaded")
127
128 # check napi threaded is unset for both napis
129 napi0 = nf.napi_get({'id': napi0_id})
130 ksft_eq(napi0['threaded'], "disabled")
131 ksft_eq(napi0.get('pid'), None)
132 napi1 = nf.napi_get({'id': napi1_id})
133 ksft_eq(napi1['threaded'], "disabled")
134 ksft_eq(napi1.get('pid'), None)
135
136def dev_set_threaded(nf) -> None:
137 """
138 Test that verifies various cases of napi threaded
139 set and unset at device level using sysfs.
140 """
141 with NetdevSimDev(queue_count=2) as nsimdev:
142 nsim = nsimdev.nsims[0]
143
144 ip(f"link set dev {nsim.ifname} up")
145
146 napis = nf.napi_get({'ifindex': nsim.ifindex}, dump=True)
147 ksft_eq(len(napis), 2)
148
149 napi0_id = napis[0]['id']
150 napi1_id = napis[1]['id']
151
152 # set threaded
153 system(f"echo 1 > /sys/class/net/{nsim.ifname}/threaded")
154
155 # check napi threaded is set for both napis
156 napi0 = nf.napi_get({'id': napi0_id})
157 ksft_eq(napi0['threaded'], "enabled")
158 ksft_ne(napi0.get('pid'), None)
159 napi1 = nf.napi_get({'id': napi1_id})
160 ksft_eq(napi1['threaded'], "enabled")
161 ksft_ne(napi1.get('pid'), None)
162
163 # unset threaded
164 system(f"echo 0 > /sys/class/net/{nsim.ifname}/threaded")
165
166 # check napi threaded is unset for both napis
167 napi0 = nf.napi_get({'id': napi0_id})
168 ksft_eq(napi0['threaded'], "disabled")
169 ksft_eq(napi0.get('pid'), None)
170 napi1 = nf.napi_get({'id': napi1_id})
171 ksft_eq(napi1['threaded'], "disabled")
172 ksft_eq(napi1.get('pid'), None)
173
174def nsim_rxq_reset_down(nf) -> None:
175 """
176 Test that the queue API supports resetting a queue
177 while the interface is down. We should convert this
178 test to testing real HW once more devices support
179 queue API.
180 """
181 with NetdevSimDev(queue_count=4) as nsimdev:
182 nsim = nsimdev.nsims[0]
183
184 ip(f"link set dev {nsim.ifname} down")
185 for i in [0, 2, 3]:
186 nsim.dfs_write("queue_reset", f"1 {i}")
187
188
189def page_pool_check(nf) -> None:
190 with NetdevSimDev() as nsimdev:
191 nsim = nsimdev.nsims[0]
192
193 def up():
194 ip(f"link set dev {nsim.ifname} up")
195
196 def down():
197 ip(f"link set dev {nsim.ifname} down")
198
199 def get_pp():
200 pp_list = nf.page_pool_get({}, dump=True)
201 return [pp for pp in pp_list if pp.get("ifindex") == nsim.ifindex]
202
203 # No page pools when down
204 down()
205 ksft_eq(len(get_pp()), 0)
206
207 # Up, empty page pool appears
208 up()
209 pp_list = get_pp()
210 ksft_ge(len(pp_list), 0)
211 refs = sum([pp["inflight"] for pp in pp_list])
212 ksft_eq(refs, 0)
213
214 # Down, it disappears, again
215 down()
216 pp_list = get_pp()
217 ksft_eq(len(pp_list), 0)
218
219 # Up, allocate a page
220 up()
221 nsim.dfs_write("pp_hold", "y")
222 pp_list = nf.page_pool_get({}, dump=True)
223 refs = sum([pp["inflight"] for pp in pp_list if pp.get("ifindex") == nsim.ifindex])
224 ksft_ge(refs, 1)
225
226 # Now let's leak a page
227 down()
228 pp_list = get_pp()
229 ksft_eq(len(pp_list), 1)
230 refs = sum([pp["inflight"] for pp in pp_list])
231 ksft_eq(refs, 1)
232 attached = [pp for pp in pp_list if "detach-time" not in pp]
233 ksft_eq(len(attached), 0)
234
235 # New pp can get created, and we'll have two
236 up()
237 pp_list = get_pp()
238 attached = [pp for pp in pp_list if "detach-time" not in pp]
239 detached = [pp for pp in pp_list if "detach-time" in pp]
240 ksft_eq(len(attached), 1)
241 ksft_eq(len(detached), 1)
242
243 # Free the old page and the old pp is gone
244 nsim.dfs_write("pp_hold", "n")
245 # Freeing check is once a second so we may need to retry
246 ksft_busy_wait(lambda: len(get_pp()) == 1, deadline=2)
247
248 # And down...
249 down()
250 ksft_eq(len(get_pp()), 0)
251
252 # Last, leave the page hanging for destroy, nothing to check
253 # we're trying to exercise the orphaning path in the kernel
254 up()
255 nsim.dfs_write("pp_hold", "y")
256
257
258def page_pool_dump_ifindex(nf) -> None:
259 """Test page pool dump filtering by ifindex."""
260 nsimdev1 = NetdevSimDev(queue_count=3)
261 rm_nsim1 = defer(nsimdev1.remove)
262 nsimdev2 = NetdevSimDev(queue_count=5)
263 defer(nsimdev2.remove)
264
265 nsim1 = nsimdev1.nsims[0]
266 nsim2 = nsimdev2.nsims[0]
267
268 ip(f"link set dev {nsim1.ifname} up")
269 ip(f"link set dev {nsim2.ifname} up")
270
271 # Unfiltered dump should have pools from both devices
272 all_pp = nf.page_pool_get({}, dump=True)
273 pp1_all = [pp for pp in all_pp
274 if pp.get("ifindex") == nsim1.ifindex]
275 pp2_all = [pp for pp in all_pp
276 if pp.get("ifindex") == nsim2.ifindex]
277 ksft_ge(len(pp1_all), 1)
278 ksft_ge(len(pp2_all), 1)
279
280 # Filtered dump should only return pools for that device
281 pp1_flt = nf.page_pool_get({'ifindex': nsim1.ifindex}, dump=True)
282 ksft_eq(pp1_flt, pp1_all)
283
284 pp2_flt = nf.page_pool_get({'ifindex': nsim2.ifindex}, dump=True)
285 ksft_eq(pp2_flt, pp2_all)
286
287 # Non-existent ifindex should return empty dump
288 pp_none = nf.page_pool_get({'ifindex': 12345678}, dump=True)
289 ksft_eq(len(pp_none), 0)
290
291 # Device down - no pools for that ifindex
292 ip(f"link set dev {nsim1.ifname} down")
293 pp1_down = nf.page_pool_get({'ifindex': nsim1.ifindex}, dump=True)
294 ksft_eq(len(pp1_down), 0)
295
296 # Remove device, dump by its old ifindex should return empty
297 old_ifindex = nsim1.ifindex
298 rm_nsim1.exec()
299 pp1_gone = nf.page_pool_get({'ifindex': old_ifindex}, dump=True)
300 ksft_eq(len(pp1_gone), 0)
301
302
303def page_pool_ifindex_leak_check(nf) -> None:
304 """Test that zombie page pools don't show up under the original ifindex."""
305 nsimdev = NetdevSimDev()
306 rm_nsim = defer(nsimdev.remove)
307 nsim = nsimdev.nsims[0]
308
309 ip(f"link set dev {nsim.ifname} up")
310 nsim.dfs_write("pp_hold", "y")
311
312 pp_up = nf.page_pool_get({'ifindex': nsim.ifindex}, dump=True)
313 ksft_ge(len(pp_up), 1)
314
315 # Remove device with leaked page - pool becomes zombie (orphaned to lo)
316 old_ifindex = nsim.ifindex
317 rm_nsim.exec()
318
319 # Zombie pool should NOT appear under the original device
320 pp_down = nf.page_pool_get({'ifindex': old_ifindex}, dump=True)
321 ksft_eq(len(pp_down), 0)
322
323 # But it should appear in an unfiltered dump (under loopback)
324 pp_all = nf.page_pool_get({}, dump=True)
325 orphans = [pp for pp in pp_all
326 if "detach-time" in pp and "ifindex" not in pp]
327 ksft_ge(len(orphans), 1)
328
329
330def page_pool_stats_ifindex_check(nf) -> None:
331 """Test page pool stats dump filtering by ifindex."""
332 nsimdev1 = NetdevSimDev(queue_count=3)
333 defer(nsimdev1.remove)
334 nsimdev2 = NetdevSimDev(queue_count=5)
335 defer(nsimdev2.remove)
336
337 nsim1 = nsimdev1.nsims[0]
338 nsim2 = nsimdev2.nsims[0]
339
340 ip(f"link set dev {nsim1.ifname} up")
341 ip(f"link set dev {nsim2.ifname} up")
342
343 # Unfiltered stats dump
344 all_stats = nf.page_pool_stats_get({}, dump=True)
345 s1_all = [s for s in all_stats
346 if s.get("info", {}).get("ifindex") == nsim1.ifindex]
347 s2_all = [s for s in all_stats
348 if s.get("info", {}).get("ifindex") == nsim2.ifindex]
349 ksft_ge(len(s1_all), 1)
350 ksft_ge(len(s2_all), 1)
351
352 # Filtered stats dump
353 s1_flt = nf.page_pool_stats_get({'info': {'ifindex': nsim1.ifindex}},
354 dump=True)
355 ksft_eq(s1_flt, s1_all)
356
357 # Non-existent ifindex should return empty
358 s_none = nf.page_pool_stats_get({'info': {'ifindex': 12345678}}, dump=True)
359 ksft_eq(len(s_none), 0)
360
361 # info.id should be rejected for stats dump
362 with ksft_raises(NlError) as cm:
363 nf.page_pool_stats_get({'info': {'id': s1_all[0]['info']['id']}},
364 dump=True)
365 ksft_eq(cm.exception.nl_msg.error, -errno.EINVAL)
366 ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.info.id')
367
368
369def main() -> None:
370 """ Ksft boiler plate main """
371 nf = NetdevFamily()
372 ksft_run([empty_check,
373 lo_check,
374 dev_dump_reject_attr,
375 napi_list_check,
376 napi_set_threaded,
377 dev_set_threaded,
378 nsim_rxq_reset_down,
379 page_pool_check,
380 page_pool_dump_ifindex,
381 page_pool_ifindex_leak_check,
382 page_pool_stats_ifindex_check
383 ],
384 args=(nf, ))
385 ksft_exit()
386
387
388if __name__ == "__main__":
389 main()