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