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