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 163 164 165 166 167
|
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
"""
Tests for RSS hashing on IPv6 Flow Label.
"""
import glob
import os
import socket
from lib.py import CmdExitFailure
from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ge, ksft_in, \
ksft_not_in, ksft_raises, KsftSkipEx
from lib.py import bkg, cmd, defer, fd_read_timeout, rand_port
from lib.py import NetDrvEpEnv
def _check_system(cfg):
if not hasattr(socket, "SO_INCOMING_CPU"):
raise KsftSkipEx("socket.SO_INCOMING_CPU was added in Python 3.11")
qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*"))
if qcnt < 2:
raise KsftSkipEx(f"Local has only {qcnt} queues")
for f in [f"/sys/class/net/{cfg.ifname}/queues/rx-0/rps_flow_cnt",
f"/sys/class/net/{cfg.ifname}/queues/rx-0/rps_cpus"]:
try:
with open(f, 'r') as fp:
setting = fp.read().strip()
# CPU mask will be zeros and commas
if setting.replace("0", "").replace(",", ""):
raise KsftSkipEx(f"RPS/RFS is configured: {f}: {setting}")
except FileNotFoundError:
pass
# 1 is the default, if someone changed it we probably shouldn"t mess with it
af = cmd("cat /proc/sys/net/ipv6/auto_flowlabels", host=cfg.remote).stdout
if af.strip() != "1":
raise KsftSkipEx("Remote does not have auto_flowlabels enabled")
def _ethtool_get_cfg(cfg, fl_type):
descr = cmd(f"ethtool -n {cfg.ifname} rx-flow-hash {fl_type}").stdout
converter = {
"IP SA": "s",
"IP DA": "d",
"L3 proto": "t",
"L4 bytes 0 & 1 [TCP/UDP src port]": "f",
"L4 bytes 2 & 3 [TCP/UDP dst port]": "n",
"IPv6 Flow Label": "l",
}
ret = ""
for line in descr.split("\n")[1:-2]:
# if this raises we probably need to add more keys to converter above
ret += converter[line]
return ret
def _traffic(cfg, one_sock, one_cpu):
local_port = rand_port(socket.SOCK_DGRAM)
remote_port = rand_port(socket.SOCK_DGRAM)
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
sock.bind(("", local_port))
sock.connect((cfg.remote_addr_v["6"], 0))
if one_sock:
send = f"exec 5<>/dev/udp/{cfg.addr_v['6']}/{local_port}; " \
"for i in `seq 20`; do echo a >&5; sleep 0.02; done; exec 5>&-"
else:
send = "for i in `seq 20`; do echo a | socat -t0.02 - UDP6:" \
f"[{cfg.addr_v['6']}]:{local_port},sourceport={remote_port}; done"
cpus = set()
with bkg(send, shell=True, host=cfg.remote, exit_wait=True):
for _ in range(20):
fd_read_timeout(sock.fileno(), 1)
cpu = sock.getsockopt(socket.SOL_SOCKET, socket.SO_INCOMING_CPU)
cpus.add(cpu)
if one_cpu:
ksft_eq(len(cpus), 1,
f"{one_sock=} - expected one CPU, got traffic on: {cpus=}")
else:
ksft_ge(len(cpus), 2,
f"{one_sock=} - expected many CPUs, got traffic on: {cpus=}")
def test_rss_flow_label(cfg):
"""
Test hashing on IPv6 flow label. Send traffic over a single socket
and over multiple sockets. Depend on the remote having auto-label
enabled so that it randomizes the label per socket.
"""
cfg.require_ipver("6")
cfg.require_cmd("socat", remote=True)
_check_system(cfg)
# Enable flow label hashing for UDP6
initial = _ethtool_get_cfg(cfg, "udp6")
no_lbl = initial.replace("l", "")
if "l" not in initial:
try:
cmd(f"ethtool -N {cfg.ifname} rx-flow-hash udp6 l{no_lbl}")
except CmdExitFailure as exc:
raise KsftSkipEx("Device doesn't support Flow Label for UDP6") from exc
defer(cmd, f"ethtool -N {cfg.ifname} rx-flow-hash udp6 {initial}")
_traffic(cfg, one_sock=True, one_cpu=True)
_traffic(cfg, one_sock=False, one_cpu=False)
# Disable it, we should see no hashing (reset was already defer()ed)
cmd(f"ethtool -N {cfg.ifname} rx-flow-hash udp6 {no_lbl}")
_traffic(cfg, one_sock=False, one_cpu=True)
def _check_v4_flow_types(cfg):
for fl_type in ["tcp4", "udp4", "ah4", "esp4", "sctp4"]:
try:
cur = cmd(f"ethtool -n {cfg.ifname} rx-flow-hash {fl_type}").stdout
ksft_not_in("Flow Label", cur,
comment=f"{fl_type=} has Flow Label:" + cur)
except CmdExitFailure:
# Probably does not support this flow type
pass
def test_rss_flow_label_6only(cfg):
"""
Test interactions with IPv4 flow types. It should not be possible to set
IPv6 Flow Label hashing for an IPv4 flow type. The Flow Label should also
not appear in the IPv4 "current config".
"""
with ksft_raises(CmdExitFailure) as cm:
cmd(f"ethtool -N {cfg.ifname} rx-flow-hash tcp4 sdfnl")
ksft_in("Invalid argument", cm.exception.cmd.stderr)
_check_v4_flow_types(cfg)
# Try to enable Flow Labels and check again, in case it leaks thru
initial = _ethtool_get_cfg(cfg, "udp6")
changed = initial.replace("l", "") if "l" in initial else initial + "l"
cmd(f"ethtool -N {cfg.ifname} rx-flow-hash udp6 {changed}")
restore = defer(cmd, f"ethtool -N {cfg.ifname} rx-flow-hash udp6 {initial}")
_check_v4_flow_types(cfg)
restore.exec()
_check_v4_flow_types(cfg)
def main() -> None:
with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
ksft_run([test_rss_flow_label,
test_rss_flow_label_6only],
args=(cfg, ))
ksft_exit()
if __name__ == "__main__":
main()
|