[RFC net-next 5/6] selftests: drv-net: psp_steer: test where PSP steering sits in the Rx pipeline

Jakub Kicinski <[email protected]>
Newsgroups org.kernel.vger.netdev
Message-ID <[email protected]>
Cover the steering priority / Rx pipeline ordering.

Signed-off-by: Jakub Kicinski <[email protected]>
---
 .../selftests/drivers/net/psp_steer.py        | 171 +++++++++++++++++-
 1 file changed, 164 insertions(+), 7 deletions(-)

diff --git a/tools/testing/selftests/drivers/net/psp_steer.py b/tools/testing/selftests/drivers/net/psp_steer.py
index 0401a7c359e7..fac2d7532d61 100644
--- a/tools/testing/selftests/drivers/net/psp_steer.py
+++ b/tools/testing/selftests/drivers/net/psp_steer.py
@@ -10,17 +10,35 @@ import socket
 from lib.py import defer
 from lib.py import ksft_run, ksft_exit
 from lib.py import ksft_eq, ksft_ge, ksft_in, ksft_ne, ksft_raises
-from lib.py import KsftSkipEx
+from lib.py import CmdExitFailure, KsftSkipEx
 from lib.py import NetDrvEpEnv
 from lib.py import NetdevFamily, NlError, PSPFamily
+from lib.py import ethtool
 
-from psp_lib import close_conn, init_psp_dev, make_psp_conn, psp_txrx, \
-    remote_conn_steer, remote_dev_steer, spi_xchg
+from psp_lib import close_conn, init_psp_dev, make_clr_conn, make_psp_conn, \
+    psp_txrx, remote_conn_steer, remote_dev_steer, spi_xchg
 from psp_lib import responder as psp_responder
 
 # Not exposed by the socket module
 _SO_INCOMING_NAPI_ID = 56
 
+
+# Mirrors the helpers of the same name in hw/rss_ctx.py, which lives in a
+# directory this test cannot import from.
+def ethtool_create(cfg, act, opts):
+    output = ethtool(f"{act} {cfg.ifname} {opts}").stdout
+    # "New RSS context is 1" / "Added rule with ID 7", we want the integer
+    return int(output.split()[-1])
+
+
+def require_ntuple(cfg):
+    features = ethtool(f"-k {cfg.ifname}", json=True)[0]
+    if not features["ntuple-filters"]["active"]:
+        if features["ntuple-filters"]["fixed"]:
+            raise KsftSkipEx("Device does not support ntuple-filters")
+        ethtool(f"-K {cfg.ifname} ntuple-filters on")
+        defer(ethtool, f"-K {cfg.ifname} ntuple-filters off")
+
 _VC_TX = 1 << 0
 _VC_RX = 1 << 1
 _VC_BOTH = _VC_TX | _VC_RX
@@ -92,6 +110,14 @@ _VC_SIZE = 8
     return s
 
 
+def _rx_queue(cfg, s):
+    """Rx queue the socket's last packet arrived on"""
+    napi_id = s.getsockopt(socket.SOL_SOCKET, _SO_INCOMING_NAPI_ID)
+    ksft_ne(napi_id, 0, comment="socket saw no traffic?")
+    ksft_in(napi_id, cfg.napi2queue, comment="unknown NAPI id")
+    return cfg.napi2queue[napi_id]
+
+
 def _settled_rx_queue(cfg, s, sent):
     """Run traffic until the peer picked our request up, report the queue
 
@@ -101,10 +127,52 @@ _VC_SIZE = 8
     sent = psp_txrx(cfg, s, 1, sent)
     sent = psp_txrx(cfg, s, 1, sent)
 
-    napi_id = s.getsockopt(socket.SOL_SOCKET, _SO_INCOMING_NAPI_ID)
-    ksft_ne(napi_id, 0, comment="socket saw no traffic?")
-    ksft_in(napi_id, cfg.napi2queue, comment="unknown NAPI id")
-    return cfg.napi2queue[napi_id], sent
+    return _rx_queue(cfg, s), sent
+
+
+def _rss_pin(cfg, qid, context=None):
+    """Point an RSS indirection table at a single queue"""
+    ctx = f"context {context} " if context is not None else ""
+    weights = " ".join("1" if i == qid else "0"
+                       for i in range(cfg.rx_queue_cnt))
+    ethtool(f"-X {cfg.ifname} {ctx}weight {weights}")
+
+
+def _require_rss_steering(cfg):
+    """Skip unless the Rx queue actually follows the RSS table
+
+    Steering can only be shown to outrank RSS on a device where RSS has
+    a say in the first place - netdevsim, for one, ignores the table.
+    """
+    probe = cfg.rx_queue_cnt - 1
+
+    try:
+        _rss_pin(cfg, probe)
+    except CmdExitFailure as exc:
+        raise KsftSkipEx("Device does not support RSS table updates") from exc
+    defer(ethtool, f"-X {cfg.ifname} default")
+
+    with make_clr_conn(cfg) as s:
+        psp_txrx(cfg, s, 1)
+        landed = _rx_queue(cfg, s)
+        close_conn(cfg, s)
+
+    if landed != probe:
+        raise KsftSkipEx("Rx queue does not follow the RSS table")
+
+
+def _ntuple_l3_rule(cfg, target):
+    """Steer this host's traffic with an L3 only rule, and clean it up
+
+    L3 only on purpose: whether the classifier sees the inner TCP ports
+    of a PSP packet or just the outer UDP encapsulation is up to the
+    device, the addresses are there either way.
+    """
+    flow = (f"flow-type ip{cfg.addr_ipver} "
+            f"src-ip {cfg.remote_addr} dst-ip {cfg.addr} {target}")
+    rule = ethtool_create(cfg, "-N", flow)
+    defer(ethtool, f"-N {cfg.ifname} delete {rule}")
+    return rule
 
 
 #
@@ -221,6 +289,95 @@ _VC_SIZE = 8
         close_conn(cfg, s)
 
 
+def data_steer_no_grant(cfg):
+    """ We ask and nobody grants: nothing is steered """
+    _require_steer(cfg)
+    _require_queues(cfg, 3)
+    _require_rss_steering(cfg)
+    _enable_steer(cfg, local=_VC_RX, remote=0)
+
+    # RSS says 2, we would be asking for 1 if anyone were listening
+    _rss_pin(cfg, 2)
+    defer(_force_tx_queue, cfg, -1)
+    _force_tx_queue(cfg, 1)
+
+    with _psp_conn(cfg) as s:
+        qid, _ = _settled_rx_queue(cfg, s, 0)
+        ksft_eq(qid, 2, comment="steered without the peer granting anything")
+        close_conn(cfg, s)
+
+
+def data_steer_beats_rss(cfg):
+    """ Steering has to win over the RSS table """
+    _require_steer(cfg)
+    _require_queues(cfg, 3)
+    _enable_steer(cfg)
+    _require_rss_steering(cfg)
+
+    # RSS says 2, steering is going to ask for 1
+    _rss_pin(cfg, 2)
+    defer(_force_tx_queue, cfg, -1)
+    _force_tx_queue(cfg, 1)
+
+    with _psp_conn(cfg) as s:
+        qid, _ = _settled_rx_queue(cfg, s, 0)
+        ksft_eq(qid, 1)
+        close_conn(cfg, s)
+
+
+def data_steer_beats_rss_ctx(cfg):
+    """ Steering has to win over an additional RSS context as well """
+    _require_steer(cfg)
+    _require_queues(cfg, 3)
+    _enable_steer(cfg)
+    _require_rss_steering(cfg)
+    require_ntuple(cfg)
+
+    # Three distinct answers: default RSS says 0, the context says 2,
+    # and steering is going to ask for 1.
+    _rss_pin(cfg, 0)
+    ctx = ethtool_create(cfg, "-X", "context new")
+    defer(ethtool, f"-X {cfg.ifname} context {ctx} delete")
+    _rss_pin(cfg, 2, context=ctx)
+    _ntuple_l3_rule(cfg, f"context {ctx}")
+
+    # Without a cookie the context has to be the one deciding, otherwise
+    # the check below would pass without steering doing anything.
+    with make_clr_conn(cfg) as s:
+        psp_txrx(cfg, s, 1)
+        ksft_eq(_rx_queue(cfg, s), 2, comment="RSS context not in use")
+        close_conn(cfg, s)
+
+    defer(_force_tx_queue, cfg, -1)
+    _force_tx_queue(cfg, 1)
+
+    with _psp_conn(cfg) as s:
+        qid, _ = _settled_rx_queue(cfg, s, 0)
+        ksft_eq(qid, 1, comment="steering did not escape the RSS context")
+        close_conn(cfg, s)
+
+
+def data_ntuple_beats_steer(cfg):
+    """ An ntuple rule naming a queue outranks steering """
+    _require_steer(cfg)
+    _require_queues(cfg, 3)
+    _enable_steer(cfg)
+    _require_rss_steering(cfg)
+    require_ntuple(cfg)
+
+    # RSS says 0, the rule says 2, steering is going to ask for 1
+    _rss_pin(cfg, 0)
+    _ntuple_l3_rule(cfg, "action 2")
+
+    defer(_force_tx_queue, cfg, -1)
+    _force_tx_queue(cfg, 1)
+
+    with _psp_conn(cfg) as s:
+        qid, _ = _settled_rx_queue(cfg, s, 0)
+        ksft_eq(qid, 2, comment="steering overrode an explicit ntuple rule")
+        close_conn(cfg, s)
+
+
 def _queue_info(cfg):
     """Map NAPI ids to Rx queue ids, and count the queues"""
     netnl = NetdevFamily()
-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.