xref: /linux/tools/testing/selftests/drivers/net/hw/ntuple.py (revision 91a4855d6c03e770e42f17c798a36a3c46e63de2)
118589df9SDimitri Daskalakis#!/usr/bin/env python3
218589df9SDimitri Daskalakis# SPDX-License-Identifier: GPL-2.0
318589df9SDimitri Daskalakis"""Test ethtool NFC (ntuple) flow steering rules."""
418589df9SDimitri Daskalakis
518589df9SDimitri Daskalakisimport random
618589df9SDimitri Daskalakisfrom enum import Enum, auto
718589df9SDimitri Daskalakisfrom lib.py import ksft_run, ksft_exit
818589df9SDimitri Daskalakisfrom lib.py import ksft_eq, ksft_ge
918589df9SDimitri Daskalakisfrom lib.py import ksft_variants, KsftNamedVariant
1018589df9SDimitri Daskalakisfrom lib.py import EthtoolFamily, NetDrvEpEnv, NetdevFamily
1118589df9SDimitri Daskalakisfrom lib.py import KsftSkipEx
12*a66374a3SDimitri Daskalakisfrom lib.py import cmd, ethtool, defer, rand_ports, bkg, wait_port_listen
1318589df9SDimitri Daskalakis
1418589df9SDimitri Daskalakis
1518589df9SDimitri Daskalakisclass NtupleField(Enum):
1618589df9SDimitri Daskalakis    SRC_IP = auto()
17*a66374a3SDimitri Daskalakis    DST_IP = auto()
18*a66374a3SDimitri Daskalakis    SRC_PORT = auto()
19*a66374a3SDimitri Daskalakis    DST_PORT = auto()
2018589df9SDimitri Daskalakis
2118589df9SDimitri Daskalakis
2218589df9SDimitri Daskalakisdef _require_ntuple(cfg):
2318589df9SDimitri Daskalakis    features = ethtool(f"-k {cfg.ifname}", json=True)[0]
2418589df9SDimitri Daskalakis    if not features["ntuple-filters"]["active"]:
2518589df9SDimitri Daskalakis        raise KsftSkipEx("Ntuple filters not enabled on the device: " + str(features["ntuple-filters"]))
2618589df9SDimitri Daskalakis
2718589df9SDimitri Daskalakis
2818589df9SDimitri Daskalakisdef _get_rx_cnts(cfg, prev=None):
2918589df9SDimitri Daskalakis    """Get Rx packet counts for all queues, as a simple list of integers
3018589df9SDimitri Daskalakis       if @prev is specified the prev counts will be subtracted"""
3118589df9SDimitri Daskalakis    cfg.wait_hw_stats_settle()
3218589df9SDimitri Daskalakis    data = cfg.netdevnl.qstats_get({"ifindex": cfg.ifindex, "scope": ["queue"]}, dump=True)
3318589df9SDimitri Daskalakis    data = [x for x in data if x['queue-type'] == "rx"]
3418589df9SDimitri Daskalakis    max_q = max([x["queue-id"] for x in data])
3518589df9SDimitri Daskalakis    queue_stats = [0] * (max_q + 1)
3618589df9SDimitri Daskalakis    for q in data:
3718589df9SDimitri Daskalakis        queue_stats[q["queue-id"]] = q["rx-packets"]
3818589df9SDimitri Daskalakis        if prev and q["queue-id"] < len(prev):
3918589df9SDimitri Daskalakis            queue_stats[q["queue-id"]] -= prev[q["queue-id"]]
4018589df9SDimitri Daskalakis    return queue_stats
4118589df9SDimitri Daskalakis
4218589df9SDimitri Daskalakis
4318589df9SDimitri Daskalakisdef _ntuple_rule_add(cfg, flow_spec):
4418589df9SDimitri Daskalakis    """Install an NFC rule via ethtool."""
4518589df9SDimitri Daskalakis
4618589df9SDimitri Daskalakis    output = ethtool(f"-N {cfg.ifname} {flow_spec}").stdout
4718589df9SDimitri Daskalakis    rule_id = int(output.split()[-1])
4818589df9SDimitri Daskalakis    defer(ethtool, f"-N {cfg.ifname} delete {rule_id}")
4918589df9SDimitri Daskalakis
5018589df9SDimitri Daskalakis
5118589df9SDimitri Daskalakisdef _setup_isolated_queue(cfg):
5218589df9SDimitri Daskalakis    """Default all traffic to queue 0, and pick a random queue to
5318589df9SDimitri Daskalakis       steer NFC traffic to."""
5418589df9SDimitri Daskalakis
5518589df9SDimitri Daskalakis    channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
5618589df9SDimitri Daskalakis    ch_max = channels['combined-max']
5718589df9SDimitri Daskalakis    qcnt = channels['combined-count']
5818589df9SDimitri Daskalakis
5918589df9SDimitri Daskalakis    if ch_max < 2:
6018589df9SDimitri Daskalakis        raise KsftSkipEx(f"Need at least 2 combined channels, max is {ch_max}")
6118589df9SDimitri Daskalakis
6218589df9SDimitri Daskalakis    desired_queues = min(ch_max, 4)
6318589df9SDimitri Daskalakis    if qcnt >= desired_queues:
6418589df9SDimitri Daskalakis        desired_queues = qcnt
6518589df9SDimitri Daskalakis    else:
6618589df9SDimitri Daskalakis        ethtool(f"-L {cfg.ifname} combined {desired_queues}")
6718589df9SDimitri Daskalakis        defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
6818589df9SDimitri Daskalakis
6918589df9SDimitri Daskalakis    ethtool(f"-X {cfg.ifname} equal 1")
7018589df9SDimitri Daskalakis    defer(ethtool, f"-X {cfg.ifname} default")
7118589df9SDimitri Daskalakis
7218589df9SDimitri Daskalakis    return random.randint(1, desired_queues - 1)
7318589df9SDimitri Daskalakis
7418589df9SDimitri Daskalakis
75*a66374a3SDimitri Daskalakisdef _send_traffic(cfg, ipver, proto, dst_port, src_port, pkt_cnt=40):
7618589df9SDimitri Daskalakis    """Generate traffic with the desired flow signature."""
7718589df9SDimitri Daskalakis
7818589df9SDimitri Daskalakis    cfg.require_cmd("socat", remote=True)
7918589df9SDimitri Daskalakis
8018589df9SDimitri Daskalakis    socat_proto = proto.upper()
8118589df9SDimitri Daskalakis    dst_addr = f"[{cfg.addr_v['6']}]" if ipver == '6' else cfg.addr_v['4']
8218589df9SDimitri Daskalakis
8318589df9SDimitri Daskalakis    extra_opts = ",nodelay" if proto == "tcp" else ",shut-null"
8418589df9SDimitri Daskalakis
8518589df9SDimitri Daskalakis    listen_cmd = (f"socat -{ipver} -t 2 -u "
8618589df9SDimitri Daskalakis                  f"{socat_proto}-LISTEN:{dst_port},reuseport /dev/null")
8718589df9SDimitri Daskalakis    with bkg(listen_cmd, exit_wait=True):
8818589df9SDimitri Daskalakis        wait_port_listen(dst_port, proto=proto)
8918589df9SDimitri Daskalakis        send_cmd = f"""
9018589df9SDimitri Daskalakis        bash -c 'for i in $(seq {pkt_cnt}); do echo msg; sleep 0.02; done' |
9118589df9SDimitri Daskalakis        socat -{ipver} -u - \
92*a66374a3SDimitri Daskalakis            {socat_proto}:{dst_addr}:{dst_port},sourceport={src_port},reuseaddr{extra_opts}
9318589df9SDimitri Daskalakis        """
9418589df9SDimitri Daskalakis        cmd(send_cmd, shell=True, host=cfg.remote)
9518589df9SDimitri Daskalakis
9618589df9SDimitri Daskalakis
9718589df9SDimitri Daskalakisdef _add_ntuple_rule_and_send_traffic(cfg, ipver, proto, fields, test_queue):
98*a66374a3SDimitri Daskalakis    ports = rand_ports(2)
99*a66374a3SDimitri Daskalakis    src_port = ports[0]
100*a66374a3SDimitri Daskalakis    dst_port = ports[1]
10118589df9SDimitri Daskalakis    flow_parts = [f"flow-type {proto}{ipver}"]
10218589df9SDimitri Daskalakis
10318589df9SDimitri Daskalakis    for field in fields:
10418589df9SDimitri Daskalakis        if field == NtupleField.SRC_IP:
10518589df9SDimitri Daskalakis            flow_parts.append(f"src-ip {cfg.remote_addr_v[ipver]}")
106*a66374a3SDimitri Daskalakis        elif field == NtupleField.DST_IP:
107*a66374a3SDimitri Daskalakis            flow_parts.append(f"dst-ip {cfg.addr_v[ipver]}")
108*a66374a3SDimitri Daskalakis        elif field == NtupleField.SRC_PORT:
109*a66374a3SDimitri Daskalakis            flow_parts.append(f"src-port {src_port}")
110*a66374a3SDimitri Daskalakis        elif field == NtupleField.DST_PORT:
111*a66374a3SDimitri Daskalakis            flow_parts.append(f"dst-port {dst_port}")
11218589df9SDimitri Daskalakis
11318589df9SDimitri Daskalakis    flow_parts.append(f"action {test_queue}")
11418589df9SDimitri Daskalakis    _ntuple_rule_add(cfg, " ".join(flow_parts))
115*a66374a3SDimitri Daskalakis    _send_traffic(cfg, ipver, proto, dst_port=dst_port, src_port=src_port)
11618589df9SDimitri Daskalakis
11718589df9SDimitri Daskalakis
11818589df9SDimitri Daskalakisdef _ntuple_variants():
11918589df9SDimitri Daskalakis    for ipver in ["4", "6"]:
12018589df9SDimitri Daskalakis        for proto in ["tcp", "udp"]:
121*a66374a3SDimitri Daskalakis            for fields in [[NtupleField.SRC_IP],
122*a66374a3SDimitri Daskalakis                           [NtupleField.DST_IP],
123*a66374a3SDimitri Daskalakis                           [NtupleField.SRC_PORT],
124*a66374a3SDimitri Daskalakis                           [NtupleField.DST_PORT],
125*a66374a3SDimitri Daskalakis                           [NtupleField.SRC_IP, NtupleField.DST_IP],
126*a66374a3SDimitri Daskalakis                           [NtupleField.SRC_IP, NtupleField.DST_IP,
127*a66374a3SDimitri Daskalakis                            NtupleField.SRC_PORT, NtupleField.DST_PORT]]:
12818589df9SDimitri Daskalakis                name = ".".join(f.name.lower() for f in fields)
12918589df9SDimitri Daskalakis                yield KsftNamedVariant(f"{proto}{ipver}.{name}",
13018589df9SDimitri Daskalakis                                      ipver, proto, fields)
13118589df9SDimitri Daskalakis
13218589df9SDimitri Daskalakis
13318589df9SDimitri Daskalakis@ksft_variants(_ntuple_variants())
13418589df9SDimitri Daskalakisdef queue(cfg, ipver, proto, fields):
13518589df9SDimitri Daskalakis    """Test that an NFC rule steers traffic to the correct queue."""
13618589df9SDimitri Daskalakis
13718589df9SDimitri Daskalakis    cfg.require_ipver(ipver)
13818589df9SDimitri Daskalakis    _require_ntuple(cfg)
13918589df9SDimitri Daskalakis
14018589df9SDimitri Daskalakis    test_queue = _setup_isolated_queue(cfg)
14118589df9SDimitri Daskalakis
14218589df9SDimitri Daskalakis    cnts = _get_rx_cnts(cfg)
14318589df9SDimitri Daskalakis    _add_ntuple_rule_and_send_traffic(cfg, ipver, proto, fields, test_queue)
14418589df9SDimitri Daskalakis    cnts = _get_rx_cnts(cfg, prev=cnts)
14518589df9SDimitri Daskalakis
14618589df9SDimitri Daskalakis    ksft_ge(cnts[test_queue], 40, f"Traffic on test queue {test_queue}: {cnts}")
14718589df9SDimitri Daskalakis    sum_idle = sum(cnts) - cnts[0] - cnts[test_queue]
14818589df9SDimitri Daskalakis    ksft_eq(sum_idle, 0, f"Traffic on idle queues: {cnts}")
14918589df9SDimitri Daskalakis
15018589df9SDimitri Daskalakis
15118589df9SDimitri Daskalakisdef main() -> None:
15218589df9SDimitri Daskalakis    """Ksft boilerplate main."""
15318589df9SDimitri Daskalakis
15418589df9SDimitri Daskalakis    with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
15518589df9SDimitri Daskalakis        cfg.ethnl = EthtoolFamily()
15618589df9SDimitri Daskalakis        cfg.netdevnl = NetdevFamily()
15718589df9SDimitri Daskalakis        ksft_run([queue], args=(cfg,))
15818589df9SDimitri Daskalakis    ksft_exit()
15918589df9SDimitri Daskalakis
16018589df9SDimitri Daskalakis
16118589df9SDimitri Daskalakisif __name__ == "__main__":
16218589df9SDimitri Daskalakis    main()
163