Path: blob/master/tools/testing/selftests/drivers/net/hw/rss_api.py
26295 views
#!/usr/bin/env python31# SPDX-License-Identifier: GPL-2.023"""4API level tests for RSS (mostly Netlink vs IOCTL).5"""67import errno8import glob9import random10from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_is, ksft_ne, ksft_raises11from lib.py import KsftSkipEx, KsftFailEx12from lib.py import defer, ethtool, CmdExitFailure13from lib.py import EthtoolFamily, NlError14from lib.py import NetDrvEnv151617def _require_2qs(cfg):18qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*"))19if qcnt < 2:20raise KsftSkipEx(f"Local has only {qcnt} queues")21return qcnt222324def _ethtool_create(cfg, act, opts):25output = ethtool(f"{act} {cfg.ifname} {opts}").stdout26# Output will be something like: "New RSS context is 1" or27# "Added rule with ID 7", we want the integer from the end28return int(output.split()[-1])293031def _ethtool_get_cfg(cfg, fl_type, to_nl=False):32descr = ethtool(f"-n {cfg.ifname} rx-flow-hash {fl_type}").stdout3334if to_nl:35converter = {36"IP SA": "ip-src",37"IP DA": "ip-dst",38"L4 bytes 0 & 1 [TCP/UDP src port]": "l4-b-0-1",39"L4 bytes 2 & 3 [TCP/UDP dst port]": "l4-b-2-3",40}4142ret = set()43else:44converter = {45"IP SA": "s",46"IP DA": "d",47"L3 proto": "t",48"L4 bytes 0 & 1 [TCP/UDP src port]": "f",49"L4 bytes 2 & 3 [TCP/UDP dst port]": "n",50}5152ret = ""5354for line in descr.split("\n")[1:-2]:55# if this raises we probably need to add more keys to converter above56if to_nl:57ret.add(converter[line])58else:59ret += converter[line]60return ret616263def test_rxfh_nl_set_fail(cfg):64"""65Test error path of Netlink SET.66"""67_require_2qs(cfg)6869ethnl = EthtoolFamily()70ethnl.ntf_subscribe("monitor")7172with ksft_raises(NlError):73ethnl.rss_set({"header": {"dev-name": "lo"},74"indir": None})7576with ksft_raises(NlError):77ethnl.rss_set({"header": {"dev-index": cfg.ifindex},78"indir": [100000]})79ntf = next(ethnl.poll_ntf(duration=0.2), None)80ksft_is(ntf, None)818283def test_rxfh_nl_set_indir(cfg):84"""85Test setting indirection table via Netlink.86"""87qcnt = _require_2qs(cfg)8889# Test some SETs with a value90reset = defer(cfg.ethnl.rss_set,91{"header": {"dev-index": cfg.ifindex}, "indir": None})92cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},93"indir": [1]})94rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})95ksft_eq(set(rss.get("indir", [-1])), {1})9697cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},98"indir": [0, 1]})99rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})100ksft_eq(set(rss.get("indir", [-1])), {0, 1})101102# Make sure we can't set the queue count below max queue used103with ksft_raises(CmdExitFailure):104ethtool(f"-L {cfg.ifname} combined 0 rx 1")105with ksft_raises(CmdExitFailure):106ethtool(f"-L {cfg.ifname} combined 1 rx 0")107108# Test reset back to default109reset.exec()110rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})111ksft_eq(set(rss.get("indir", [-1])), set(range(qcnt)))112113114def test_rxfh_nl_set_indir_ctx(cfg):115"""116Test setting indirection table for a custom context via Netlink.117"""118_require_2qs(cfg)119120# Get setting for ctx 0, we'll make sure they don't get clobbered121dflt = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})122123# Create context124ctx_id = _ethtool_create(cfg, "-X", "context new")125defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")126127cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},128"context": ctx_id, "indir": [1]})129rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex},130"context": ctx_id})131ksft_eq(set(rss.get("indir", [-1])), {1})132133ctx0 = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})134ksft_eq(ctx0, dflt)135136cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},137"context": ctx_id, "indir": [0, 1]})138rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex},139"context": ctx_id})140ksft_eq(set(rss.get("indir", [-1])), {0, 1})141142ctx0 = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})143ksft_eq(ctx0, dflt)144145# Make sure we can't set the queue count below max queue used146with ksft_raises(CmdExitFailure):147ethtool(f"-L {cfg.ifname} combined 0 rx 1")148with ksft_raises(CmdExitFailure):149ethtool(f"-L {cfg.ifname} combined 1 rx 0")150151152def test_rxfh_indir_ntf(cfg):153"""154Check that Netlink notifications are generated when RSS indirection155table was modified.156"""157_require_2qs(cfg)158159ethnl = EthtoolFamily()160ethnl.ntf_subscribe("monitor")161162ethtool(f"--disable-netlink -X {cfg.ifname} weight 0 1")163reset = defer(ethtool, f"-X {cfg.ifname} default")164165ntf = next(ethnl.poll_ntf(duration=0.2), None)166if ntf is None:167raise KsftFailEx("No notification received")168ksft_eq(ntf["name"], "rss-ntf")169ksft_eq(set(ntf["msg"]["indir"]), {1})170171reset.exec()172ntf = next(ethnl.poll_ntf(duration=0.2), None)173if ntf is None:174raise KsftFailEx("No notification received after reset")175ksft_eq(ntf["name"], "rss-ntf")176ksft_is(ntf["msg"].get("context"), None)177ksft_ne(set(ntf["msg"]["indir"]), {1})178179180def test_rxfh_indir_ctx_ntf(cfg):181"""182Check that Netlink notifications are generated when RSS indirection183table was modified on an additional RSS context.184"""185_require_2qs(cfg)186187ctx_id = _ethtool_create(cfg, "-X", "context new")188defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")189190ethnl = EthtoolFamily()191ethnl.ntf_subscribe("monitor")192193ethtool(f"--disable-netlink -X {cfg.ifname} context {ctx_id} weight 0 1")194195ntf = next(ethnl.poll_ntf(duration=0.2), None)196if ntf is None:197raise KsftFailEx("No notification received")198ksft_eq(ntf["name"], "rss-ntf")199ksft_eq(ntf["msg"].get("context"), ctx_id)200ksft_eq(set(ntf["msg"]["indir"]), {1})201202203def test_rxfh_nl_set_key(cfg):204"""205Test setting hashing key via Netlink.206"""207208dflt = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})209defer(cfg.ethnl.rss_set,210{"header": {"dev-index": cfg.ifindex},211"hkey": dflt["hkey"], "indir": None})212213# Empty key should error out214with ksft_raises(NlError) as cm:215cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},216"hkey": None})217ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.hkey')218219# Set key to random220mod = random.randbytes(len(dflt["hkey"]))221cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},222"hkey": mod})223rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})224ksft_eq(rss.get("hkey", [-1]), mod)225226# Set key to random and indir tbl to something at once227mod = random.randbytes(len(dflt["hkey"]))228cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},229"indir": [0, 1], "hkey": mod})230rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})231ksft_eq(rss.get("hkey", [-1]), mod)232ksft_eq(set(rss.get("indir", [-1])), {0, 1})233234235def test_rxfh_fields(cfg):236"""237Test reading Rx Flow Hash over Netlink.238"""239240flow_types = ["tcp4", "tcp6", "udp4", "udp6"]241ethnl = EthtoolFamily()242243cfg_nl = ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})244for fl_type in flow_types:245one = _ethtool_get_cfg(cfg, fl_type, to_nl=True)246ksft_eq(one, cfg_nl["flow-hash"][fl_type],247comment="Config for " + fl_type)248249250def test_rxfh_fields_set(cfg):251""" Test configuring Rx Flow Hash over Netlink. """252253flow_types = ["tcp4", "tcp6", "udp4", "udp6"]254255# Collect current settings256cfg_old = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})257# symmetric hashing is config-order-sensitive make sure we leave258# symmetric mode, or make the flow-hash sym-compatible first259changes = [{"flow-hash": cfg_old["flow-hash"],},260{"input-xfrm": cfg_old.get("input-xfrm", {}),}]261if cfg_old.get("input-xfrm"):262changes = list(reversed(changes))263for old in changes:264defer(cfg.ethnl.rss_set, {"header": {"dev-index": cfg.ifindex},} | old)265266# symmetric hashing prevents some of the configs below267if cfg_old.get("input-xfrm"):268cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},269"input-xfrm": {}})270271for fl_type in flow_types:272cur = _ethtool_get_cfg(cfg, fl_type)273if cur == "sdfn":274change_nl = {"ip-src", "ip-dst"}275change_ic = "sd"276else:277change_nl = {"l4-b-0-1", "l4-b-2-3", "ip-src", "ip-dst"}278change_ic = "sdfn"279280cfg.ethnl.rss_set({281"header": {"dev-index": cfg.ifindex},282"flow-hash": {fl_type: change_nl}283})284reset = defer(ethtool, f"--disable-netlink -N {cfg.ifname} "285f"rx-flow-hash {fl_type} {cur}")286287cfg_nl = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})288ksft_eq(change_nl, cfg_nl["flow-hash"][fl_type],289comment=f"Config for {fl_type} over Netlink")290cfg_ic = _ethtool_get_cfg(cfg, fl_type)291ksft_eq(change_ic, cfg_ic,292comment=f"Config for {fl_type} over IOCTL")293294reset.exec()295cfg_nl = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})296ksft_eq(cfg_old["flow-hash"][fl_type], cfg_nl["flow-hash"][fl_type],297comment=f"Un-config for {fl_type} over Netlink")298cfg_ic = _ethtool_get_cfg(cfg, fl_type)299ksft_eq(cur, cfg_ic, comment=f"Un-config for {fl_type} over IOCTL")300301# Try to set multiple at once, the defer was already installed at the start302change = {"ip-src"}303if change == cfg_old["flow-hash"]["tcp4"]:304change = {"ip-dst"}305cfg.ethnl.rss_set({306"header": {"dev-index": cfg.ifindex},307"flow-hash": {x: change for x in flow_types}308})309310cfg_nl = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})311for fl_type in flow_types:312ksft_eq(change, cfg_nl["flow-hash"][fl_type],313comment=f"multi-config for {fl_type} over Netlink")314315316def test_rxfh_fields_set_xfrm(cfg):317""" Test changing Rx Flow Hash vs xfrm_input at once. """318319def set_rss(cfg, xfrm, fh):320cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},321"input-xfrm": xfrm, "flow-hash": fh})322323# Install the reset handler324cfg_old = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})325# symmetric hashing is config-order-sensitive make sure we leave326# symmetric mode, or make the flow-hash sym-compatible first327changes = [{"flow-hash": cfg_old["flow-hash"],},328{"input-xfrm": cfg_old.get("input-xfrm", {}),}]329if cfg_old.get("input-xfrm"):330changes = list(reversed(changes))331for old in changes:332defer(cfg.ethnl.rss_set, {"header": {"dev-index": cfg.ifindex},} | old)333334# Make sure we start with input-xfrm off, and tcp4 config non-sym335set_rss(cfg, {}, {})336set_rss(cfg, {}, {"tcp4": {"ip-src"}})337338# Setting sym and fixing tcp4 config not expected to pass right now339with ksft_raises(NlError):340set_rss(cfg, {"sym-xor"}, {"tcp4": {"ip-src", "ip-dst"}})341# One at a time should work, hopefully342set_rss(cfg, 0, {"tcp4": {"ip-src", "ip-dst"}})343no_support = False344try:345set_rss(cfg, {"sym-xor"}, {})346except NlError:347try:348set_rss(cfg, {"sym-or-xor"}, {})349except NlError:350no_support = True351if no_support:352raise KsftSkipEx("no input-xfrm supported")353# Disabling two at once should not work either without kernel changes354with ksft_raises(NlError):355set_rss(cfg, {}, {"tcp4": {"ip-src"}})356357358def test_rxfh_fields_ntf(cfg):359""" Test Rx Flow Hash notifications. """360361cur = _ethtool_get_cfg(cfg, "tcp4")362if cur == "sdfn":363change = {"ip-src", "ip-dst"}364else:365change = {"l4-b-0-1", "l4-b-2-3", "ip-src", "ip-dst"}366367ethnl = EthtoolFamily()368ethnl.ntf_subscribe("monitor")369370ethnl.rss_set({371"header": {"dev-index": cfg.ifindex},372"flow-hash": {"tcp4": change}373})374reset = defer(ethtool,375f"--disable-netlink -N {cfg.ifname} rx-flow-hash tcp4 {cur}")376377ntf = next(ethnl.poll_ntf(duration=0.2), None)378if ntf is None:379raise KsftFailEx("No notification received after IOCTL change")380ksft_eq(ntf["name"], "rss-ntf")381ksft_eq(ntf["msg"]["flow-hash"]["tcp4"], change)382ksft_eq(next(ethnl.poll_ntf(duration=0.01), None), None)383384reset.exec()385ntf = next(ethnl.poll_ntf(duration=0.2), None)386if ntf is None:387raise KsftFailEx("No notification received after Netlink change")388ksft_eq(ntf["name"], "rss-ntf")389ksft_ne(ntf["msg"]["flow-hash"]["tcp4"], change)390ksft_eq(next(ethnl.poll_ntf(duration=0.01), None), None)391392393def test_rss_ctx_add(cfg):394""" Test creating an additional RSS context via Netlink """395396_require_2qs(cfg)397398# Test basic creation399ctx = cfg.ethnl.rss_create_act({"header": {"dev-index": cfg.ifindex}})400d = defer(ethtool, f"-X {cfg.ifname} context {ctx.get('context')} delete")401ksft_ne(ctx.get("context", 0), 0)402ksft_ne(set(ctx.get("indir", [0])), {0},403comment="Driver should init the indirection table")404405# Try requesting the ID we just got allocated406with ksft_raises(NlError) as cm:407ctx = cfg.ethnl.rss_create_act({408"header": {"dev-index": cfg.ifindex},409"context": ctx.get("context"),410})411ethtool(f"-X {cfg.ifname} context {ctx.get('context')} delete")412d.exec()413ksft_eq(cm.exception.nl_msg.error, -errno.EBUSY)414415# Test creating with a specified RSS table, and context ID416ctx_id = ctx.get("context")417ctx = cfg.ethnl.rss_create_act({418"header": {"dev-index": cfg.ifindex},419"context": ctx_id,420"indir": [1],421})422ethtool(f"-X {cfg.ifname} context {ctx.get('context')} delete")423ksft_eq(ctx.get("context"), ctx_id)424ksft_eq(set(ctx.get("indir", [0])), {1})425426427def test_rss_ctx_ntf(cfg):428""" Test notifications for creating additional RSS contexts """429430ethnl = EthtoolFamily()431ethnl.ntf_subscribe("monitor")432433# Create / delete via Netlink434ctx = cfg.ethnl.rss_create_act({"header": {"dev-index": cfg.ifindex}})435cfg.ethnl.rss_delete_act({436"header": {"dev-index": cfg.ifindex},437"context": ctx["context"],438})439440ntf = next(ethnl.poll_ntf(duration=0.2), None)441if ntf is None:442raise KsftFailEx("[NL] No notification after context creation")443ksft_eq(ntf["name"], "rss-create-ntf")444ksft_eq(ctx, ntf["msg"])445446ntf = next(ethnl.poll_ntf(duration=0.2), None)447if ntf is None:448raise KsftFailEx("[NL] No notification after context deletion")449ksft_eq(ntf["name"], "rss-delete-ntf")450451# Create / deleve via IOCTL452ctx_id = _ethtool_create(cfg, "--disable-netlink -X", "context new")453ethtool(f"--disable-netlink -X {cfg.ifname} context {ctx_id} delete")454ntf = next(ethnl.poll_ntf(duration=0.2), None)455if ntf is None:456raise KsftFailEx("[IOCTL] No notification after context creation")457ksft_eq(ntf["name"], "rss-create-ntf")458459ntf = next(ethnl.poll_ntf(duration=0.2), None)460if ntf is None:461raise KsftFailEx("[IOCTL] No notification after context deletion")462ksft_eq(ntf["name"], "rss-delete-ntf")463464465def main() -> None:466""" Ksft boiler plate main """467468with NetDrvEnv(__file__, nsim_test=False) as cfg:469cfg.ethnl = EthtoolFamily()470ksft_run(globs=globals(), case_pfx={"test_"}, args=(cfg, ))471ksft_exit()472473474if __name__ == "__main__":475main()476477478