xref: /linux/tools/testing/selftests/net/nl_netdev.py (revision d603517771d8e08a2d8fc9e1f7682ce393d3973a)
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()
390