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