Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/testing/selftests/drivers/net/hw/ntuple.py
171056 views
1
#!/usr/bin/env python3
2
# SPDX-License-Identifier: GPL-2.0
3
"""Test ethtool NFC (ntuple) flow steering rules."""
4
5
import random
6
from enum import Enum, auto
7
from lib.py import ksft_run, ksft_exit
8
from lib.py import ksft_eq, ksft_ge
9
from lib.py import ksft_variants, KsftNamedVariant
10
from lib.py import EthtoolFamily, NetDrvEpEnv, NetdevFamily
11
from lib.py import KsftSkipEx
12
from lib.py import cmd, ethtool, defer, rand_ports, bkg, wait_port_listen
13
14
15
class NtupleField(Enum):
16
SRC_IP = auto()
17
DST_IP = auto()
18
SRC_PORT = auto()
19
DST_PORT = auto()
20
21
22
def _require_ntuple(cfg):
23
features = ethtool(f"-k {cfg.ifname}", json=True)[0]
24
if not features["ntuple-filters"]["active"]:
25
raise KsftSkipEx("Ntuple filters not enabled on the device: " + str(features["ntuple-filters"]))
26
27
28
def _get_rx_cnts(cfg, prev=None):
29
"""Get Rx packet counts for all queues, as a simple list of integers
30
if @prev is specified the prev counts will be subtracted"""
31
cfg.wait_hw_stats_settle()
32
data = cfg.netdevnl.qstats_get({"ifindex": cfg.ifindex, "scope": ["queue"]}, dump=True)
33
data = [x for x in data if x['queue-type'] == "rx"]
34
max_q = max([x["queue-id"] for x in data])
35
queue_stats = [0] * (max_q + 1)
36
for q in data:
37
queue_stats[q["queue-id"]] = q["rx-packets"]
38
if prev and q["queue-id"] < len(prev):
39
queue_stats[q["queue-id"]] -= prev[q["queue-id"]]
40
return queue_stats
41
42
43
def _ntuple_rule_add(cfg, flow_spec):
44
"""Install an NFC rule via ethtool."""
45
46
output = ethtool(f"-N {cfg.ifname} {flow_spec}").stdout
47
rule_id = int(output.split()[-1])
48
defer(ethtool, f"-N {cfg.ifname} delete {rule_id}")
49
50
51
def _setup_isolated_queue(cfg):
52
"""Default all traffic to queue 0, and pick a random queue to
53
steer NFC traffic to."""
54
55
channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
56
ch_max = channels['combined-max']
57
qcnt = channels['combined-count']
58
59
if ch_max < 2:
60
raise KsftSkipEx(f"Need at least 2 combined channels, max is {ch_max}")
61
62
desired_queues = min(ch_max, 4)
63
if qcnt >= desired_queues:
64
desired_queues = qcnt
65
else:
66
ethtool(f"-L {cfg.ifname} combined {desired_queues}")
67
defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
68
69
ethtool(f"-X {cfg.ifname} equal 1")
70
defer(ethtool, f"-X {cfg.ifname} default")
71
72
return random.randint(1, desired_queues - 1)
73
74
75
def _send_traffic(cfg, ipver, proto, dst_port, src_port, pkt_cnt=40):
76
"""Generate traffic with the desired flow signature."""
77
78
cfg.require_cmd("socat", remote=True)
79
80
socat_proto = proto.upper()
81
dst_addr = f"[{cfg.addr_v['6']}]" if ipver == '6' else cfg.addr_v['4']
82
83
extra_opts = ",nodelay" if proto == "tcp" else ",shut-null"
84
85
listen_cmd = (f"socat -{ipver} -t 2 -u "
86
f"{socat_proto}-LISTEN:{dst_port},reuseport /dev/null")
87
with bkg(listen_cmd, exit_wait=True):
88
wait_port_listen(dst_port, proto=proto)
89
send_cmd = f"""
90
bash -c 'for i in $(seq {pkt_cnt}); do echo msg; sleep 0.02; done' |
91
socat -{ipver} -u - \
92
{socat_proto}:{dst_addr}:{dst_port},sourceport={src_port},reuseaddr{extra_opts}
93
"""
94
cmd(send_cmd, shell=True, host=cfg.remote)
95
96
97
def _add_ntuple_rule_and_send_traffic(cfg, ipver, proto, fields, test_queue):
98
ports = rand_ports(2)
99
src_port = ports[0]
100
dst_port = ports[1]
101
flow_parts = [f"flow-type {proto}{ipver}"]
102
103
for field in fields:
104
if field == NtupleField.SRC_IP:
105
flow_parts.append(f"src-ip {cfg.remote_addr_v[ipver]}")
106
elif field == NtupleField.DST_IP:
107
flow_parts.append(f"dst-ip {cfg.addr_v[ipver]}")
108
elif field == NtupleField.SRC_PORT:
109
flow_parts.append(f"src-port {src_port}")
110
elif field == NtupleField.DST_PORT:
111
flow_parts.append(f"dst-port {dst_port}")
112
113
flow_parts.append(f"action {test_queue}")
114
_ntuple_rule_add(cfg, " ".join(flow_parts))
115
_send_traffic(cfg, ipver, proto, dst_port=dst_port, src_port=src_port)
116
117
118
def _ntuple_variants():
119
for ipver in ["4", "6"]:
120
for proto in ["tcp", "udp"]:
121
for fields in [[NtupleField.SRC_IP],
122
[NtupleField.DST_IP],
123
[NtupleField.SRC_PORT],
124
[NtupleField.DST_PORT],
125
[NtupleField.SRC_IP, NtupleField.DST_IP],
126
[NtupleField.SRC_IP, NtupleField.DST_IP,
127
NtupleField.SRC_PORT, NtupleField.DST_PORT]]:
128
name = ".".join(f.name.lower() for f in fields)
129
yield KsftNamedVariant(f"{proto}{ipver}.{name}",
130
ipver, proto, fields)
131
132
133
@ksft_variants(_ntuple_variants())
134
def queue(cfg, ipver, proto, fields):
135
"""Test that an NFC rule steers traffic to the correct queue."""
136
137
cfg.require_ipver(ipver)
138
_require_ntuple(cfg)
139
140
test_queue = _setup_isolated_queue(cfg)
141
142
cnts = _get_rx_cnts(cfg)
143
_add_ntuple_rule_and_send_traffic(cfg, ipver, proto, fields, test_queue)
144
cnts = _get_rx_cnts(cfg, prev=cnts)
145
146
ksft_ge(cnts[test_queue], 40, f"Traffic on test queue {test_queue}: {cnts}")
147
sum_idle = sum(cnts) - cnts[0] - cnts[test_queue]
148
ksft_eq(sum_idle, 0, f"Traffic on idle queues: {cnts}")
149
150
151
def main() -> None:
152
"""Ksft boilerplate main."""
153
154
with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
155
cfg.ethnl = EthtoolFamily()
156
cfg.netdevnl = NetdevFamily()
157
ksft_run([queue], args=(cfg,))
158
ksft_exit()
159
160
161
if __name__ == "__main__":
162
main()
163
164