summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/net/rds/test.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing/selftests/net/rds/test.py')
-rwxr-xr-xtools/testing/selftests/net/rds/test.py713
1 files changed, 502 insertions, 211 deletions
diff --git a/tools/testing/selftests/net/rds/test.py b/tools/testing/selftests/net/rds/test.py
index 4a7178d11193..9e4df01cb0d4 100755
--- a/tools/testing/selftests/net/rds/test.py
+++ b/tools/testing/selftests/net/rds/test.py
@@ -1,77 +1,486 @@
#! /usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
+"""
+This module provides functional testing for the net/rds component.
+"""
import argparse
+import atexit
import ctypes
import errno
import hashlib
import os
import select
+import re
import signal
import socket
import subprocess
import sys
-import atexit
-from pwd import getpwuid
-from os import stat
+import time
# Allow utils module to be imported from different directory
this_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(os.path.join(this_dir, "../"))
-from lib.py.utils import ip
+# pylint: disable-next=wrong-import-position,import-error,no-name-in-module
+from lib.py.utils import ip, cmd # noqa: E402
+# pylint: disable-next=wrong-import-position,import-error,no-name-in-module
+from lib.py.ksft import ksft_pr # noqa: E402
libc = ctypes.cdll.LoadLibrary('libc.so.6')
setns = libc.setns
-net0 = 'net0'
-net1 = 'net1'
+NET0 = 'net0'
+NET1 = 'net1'
+
+VETH0 = 'veth0'
+VETH1 = 'veth1'
+
+tcpdump_procs = []
+tcp_addrs = [
+ # we technically don't need different port numbers, but this will
+ # help identify traffic in the network analyzer
+ ('10.0.0.1', 10000),
+ ('10.0.0.2', 20000),
+]
-veth0 = 'veth0'
-veth1 = 'veth1'
+# RDMA network configs
+RXE_DEV0 = 'rxe0'
+RXE_DEV1 = 'rxe1'
+
+VETH_RDMA0 = 'veth_rdma0'
+VETH_RDMA1 = 'veth_rdma1'
+
+rdma_addrs = [
+ ('10.0.0.3', 30000),
+ ('10.0.0.4', 30000),
+]
+
+# send_packets flag space
+OP_FLAG_TCP = 0x1
+OP_FLAG_RDMA = 0x2
+
+# from include/uapi/linux/rds.h: SO_RDS_TRANSPORT pins a socket to a
+# specific RDS transport so connection setup cannot silently fall back
+# to another (e.g. loopback) transport.
+SOL_RDS = 276
+SO_RDS_TRANSPORT = 8
+RDS_TRANS_TCP = 2
+RDS_TRANS_IB = 0
+
+signal_handler_label = ""
+
+tap_idx = 0
+nr_pass = 0
+nr_fail = 0
# Helper function for creating a socket inside a network namespace.
# We need this because otherwise RDS will detect that the two TCP
# sockets are on the same interface and use the loop transport instead
# of the TCP transport.
-def netns_socket(netns, *args):
+def netns_socket(netns, *sock_args):
+ """
+ Creates sockets inside of network namespace
+
+ :param netns: the name of the network namespace
+ :param sock_args: socket family and type
+ """
u0, u1 = socket.socketpair(socket.AF_UNIX, socket.SOCK_SEQPACKET)
child = os.fork()
if child == 0:
- # change network namespace
- with open(f'/var/run/netns/{netns}') as f:
- try:
- ret = setns(f.fileno(), 0)
- except IOError as e:
- print(e.errno)
- print(e)
-
- # create socket in target namespace
- s = socket.socket(*args)
+ try:
+ # change network namespace
+ with open(f'/var/run/netns/{netns}', encoding='utf-8') as f:
+ setns(f.fileno(), 0)
+ # create socket in target namespace
+ sock = socket.socket(*sock_args)
- # send resulting socket to parent
- socket.send_fds(u0, [], [s.fileno()])
+ # send resulting socket to parent
+ socket.send_fds(u0, [], [sock.fileno()])
- sys.exit(0)
+ os._exit(0)
+ except BaseException:
+ os._exit(1)
# receive socket from child
- _, s, _, _ = socket.recv_fds(u1, 0, 1)
- os.waitpid(child, 0)
+ _, fds, _, _ = socket.recv_fds(u1, 0, 1)
+ _, status = os.waitpid(child, 0)
u0.close()
u1.close()
- return socket.fromfd(s[0], *args)
+ if not os.WIFEXITED(status) or os.WEXITSTATUS(status) != 0:
+ raise RuntimeError(
+ f"netns_socket child failed in netns {netns} (status={status})")
+ return socket.fromfd(fds[0], *sock_args)
-def signal_handler(sig, frame):
- print('Test timed out')
+def send_burst(socks, ip_addrs, snd_hashes, nr_sent, nr_total):
+ """Send until blocked or nr_total reached. Return updated nr_sent."""
+
+ while nr_sent < nr_total:
+ data = hashlib.sha256(
+ f'packet {nr_sent}'.encode('utf-8')).hexdigest().encode('utf-8')
+ # pseudo-random send/receive pattern
+ snd_idx = nr_sent % 2
+ rcv_idx = 1 - (nr_sent % 3) % 2
+
+ snd = socks[snd_idx]
+ rcv = socks[rcv_idx]
+ try:
+ snd.sendto(data, ip_addrs[rcv_idx])
+ except BlockingIOError:
+ return nr_sent
+ except OSError as e:
+ if e.errno in (errno.ENOBUFS, errno.ECONNRESET, errno.EPIPE):
+ return nr_sent
+ raise
+ snd_hashes.setdefault((snd.fileno(), rcv.fileno()),
+ hashlib.sha256()).update(f'<{data}>'.encode('utf-8'))
+ nr_sent += 1
+ return nr_sent
+
+def recv_burst(epoll, socks, ip_addrs, rcv_hashes, nr_rcv):
+ """Drain whatever's readable from epoll. Return updated nr_recv."""
+ for filen, evntmask in epoll.poll():
+ if not evntmask & select.EPOLLRDNORM:
+ continue
+ rcv = next(s for s in socks if s.fileno() == filen)
+ while True:
+ try:
+ data, adr = rcv.recvfrom(1024)
+ except BlockingIOError:
+ break
+ snd_idx = ip_addrs.index(adr)
+ snd = socks[snd_idx]
+ rcv_hashes.setdefault((snd.fileno(), rcv.fileno()),
+ hashlib.sha256()).update(f'<{data}>'.encode('utf-8'))
+ nr_rcv += 1
+ return nr_rcv
+
+def check_info(socks):
+ """
+ Check all rds info pages for errors
+
+ :param socks: list of sockets to check
+ """
+
+ # the Python socket module doesn't know these
+ rds_info_first = 10000
+ rds_info_last = 10017
+
+ nr_success = 0
+ nr_error = 0
+
+ for sock in socks:
+ for optname in range(rds_info_first, rds_info_last + 1):
+ # Sigh, the Python socket module doesn't allow us to pass
+ # buffer lengths greater than 1024 for some reason. RDS
+ # wants multiple pages.
+ try:
+ sock.getsockopt(socket.SOL_RDS, optname, 1024)
+ nr_success = nr_success + 1
+ except OSError as e:
+ nr_error = nr_error + 1
+ if e.errno == errno.ENOSPC:
+ # ignore
+ pass
+
+ ksft_pr(f"getsockopt(): {nr_success}/{nr_error}")
+
+def verify_hashes(snd_hashes, rcv_hashes):
+ """Compare send/recv hashes per (sender, receiver) pair."""
+ for key, snd_hash in snd_hashes.items():
+ rcv_hash = rcv_hashes.get(key)
+ if rcv_hash is None:
+ ksft_pr("FAIL: No data received")
+ return 1
+ if snd_hash.hexdigest() != rcv_hash.hexdigest():
+ ksft_pr("FAIL: Send/recv mismatch")
+ ksft_pr("hash expected:", snd_hash.hexdigest())
+ ksft_pr("hash received:", rcv_hash.hexdigest())
+ return 1
+ ksft_pr(f"{key[0]}/{key[1]}: ok")
+ return 0
+
+def snd_rcv_packets(env):
+ """
+ Send packets on the given network interfaces
+
+ :param env: transport-environment dict for setup_tcp() / setup_rdma().
+ "addrs": list of (ip, port) tuples matching the sockets
+ "netns": list of netns names for TCP or None for RDMA
+ "flags": OP_FLAG_TCP or OP_FLAG_RDMA, selects sockets
+ """
+
+ addrs = env["addrs"]
+ netns_list = env["netns"]
+ flags = env.get("flags", 0)
+
+ if (flags & OP_FLAG_TCP) and (flags & OP_FLAG_RDMA):
+ raise RuntimeError(f"Invalid transport flag sets multiple transports: {flags}")
+
+ if flags & OP_FLAG_TCP:
+ sockets = [
+ netns_socket(netns_list[0], socket.AF_RDS, socket.SOCK_SEQPACKET),
+ netns_socket(netns_list[1], socket.AF_RDS, socket.SOCK_SEQPACKET),
+ ]
+
+ # Pin the sockets to the TCP transport so it doesn't fail over to a
+ # different transport during this test
+ for s in sockets:
+ s.setsockopt(SOL_RDS, SO_RDS_TRANSPORT, RDS_TRANS_TCP)
+ elif flags & OP_FLAG_RDMA:
+ sockets = [
+ socket.socket(socket.AF_RDS, socket.SOCK_SEQPACKET),
+ socket.socket(socket.AF_RDS, socket.SOCK_SEQPACKET),
+ ]
+
+ # Pin the sockets to the RDMA transport so it doesn't fail over to a
+ # different transport during this test
+ for s in sockets:
+ s.setsockopt(SOL_RDS, SO_RDS_TRANSPORT, RDS_TRANS_IB)
+ else:
+ raise RuntimeError(f"Invalid transport flag sets no transports: {flags}")
+
+ for s, addr in zip(sockets, addrs):
+ s.bind(addr)
+ s.setblocking(0)
+
+ send_hashes = {}
+ recv_hashes = {}
+
+ ep = select.epoll()
+
+ for s in sockets:
+ ep.register(s, select.EPOLLRDNORM)
+
+ num_packets = 50000
+ nr_send = 0
+ nr_recv = 0
+
+ while nr_send < num_packets:
+
+ # Send as much as we can without blocking
+ ksft_pr("sending...", nr_send, nr_recv)
+ nr_send = send_burst(sockets, addrs, send_hashes, nr_send, num_packets)
+
+ # Receive as much as we can without blocking
+ ksft_pr("receiving...", nr_send, nr_recv)
+ while nr_recv < nr_send:
+ nr_recv = recv_burst(ep, sockets, addrs, recv_hashes, nr_recv)
+
+ # exercise net/rds/tcp.c:rds_tcp_sysctl_reset()
+ if netns_list:
+ for net in netns_list:
+ ip(f"netns exec {net} /usr/sbin/sysctl net.rds.tcp.rds_tcp_rcvbuf=10000")
+ ip(f"netns exec {net} /usr/sbin/sysctl net.rds.tcp.rds_tcp_sndbuf=10000")
+
+ ksft_pr("done", nr_send, nr_recv)
+
+ check_info(sockets)
+
+ # We're done sending and receiving stuff, now let's check if what
+ # we received is what we sent.
+ rc = verify_hashes(send_hashes, recv_hashes)
+
+ ep.close()
+ for s in sockets:
+ s.close()
+
+ return rc
+
+def stop_pcaps():
+ """Stop tcpdump processes.
+
+ We use pop() here to drain the list in the event that the test
+ completes after the signal handler is fired. List will be empty
+ if logdir is not set
+ """
+
+ if not tcpdump_procs:
+ return
+
+ ksft_pr("Stopping network packet captures")
+ while tcpdump_procs:
+ proc = tcpdump_procs.pop()
+ proc.terminate()
+ try:
+ proc.wait(timeout=5)
+ except subprocess.TimeoutExpired:
+ proc.kill()
+ proc.wait()
+
+def signal_handler(_sig, _frame):
+ """
+ Test timed out signal handler
+ """
+ ksft_pr(f"Test timed out: {signal_handler_label}")
+ print(f"not ok {tap_idx} rds selftest {signal_handler_label}")
sys.exit(1)
+def setup_tcp():
+ """
+ Configure tcp network
+ """
+
+ # clean up any leftovers from a previously interrupted run
+ teardown_tcp()
+
+ ip(f"netns add {NET0}")
+ ip(f"netns add {NET1}")
+ ip("link add type veth")
+
+ # Move TCP interfaces into separate namespaces so they can no longer be
+ # bound directly; this prevents rds from switching over from the tcp
+ # transport to the loop transport.
+ ip(f"link set {VETH0} netns {NET0} up")
+ ip(f"link set {VETH1} netns {NET1} up")
+
+ # add addresses
+ ip(f"-n {NET0} addr add {tcp_addrs[0][0]}/32 dev {VETH0}")
+ ip(f"-n {NET1} addr add {tcp_addrs[1][0]}/32 dev {VETH1}")
+
+ # add routes
+ ip(f"-n {NET0} route add {tcp_addrs[1][0]}/32 dev {VETH0}")
+ ip(f"-n {NET1} route add {tcp_addrs[0][0]}/32 dev {VETH1}")
+
+ # sanity check that our two interfaces/addresses are correctly set up
+ # and communicating by doing a single ping
+ ip(f"netns exec {NET0} ping -c 1 {tcp_addrs[1][0]}")
+
+ # Start a packet capture on each network
+ if logdir is not None:
+ for netn in [NET0, NET1]:
+ pcap = logdir+'/rds-'+netn+'.pcap'
+
+ tcpdump_cmd = ['ip', 'netns', 'exec', netn, '/usr/sbin/tcpdump']
+ sudo_user = os.environ.get('SUDO_USER')
+ if sudo_user:
+ tcpdump_cmd.extend(['-Z', sudo_user])
+ tcpdump_cmd.extend(['-i', 'any', '-w', pcap])
+
+ # pylint: disable-next=consider-using-with
+ p = subprocess.Popen(tcpdump_cmd,
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ tcpdump_procs.append(p)
+
+ # simulate packet loss, duplication and corruption
+ for netn, iface in [(NET0, VETH0), (NET1, VETH1)]:
+ ip(f"netns exec {netn} /usr/sbin/tc qdisc add dev {iface} root netem \
+ corrupt {PACKET_CORRUPTION} loss {PACKET_LOSS} duplicate \
+ {PACKET_DUPLICATE}")
+
+def teardown_tcp():
+ """
+ Tear down the tcp network configured by setup_tcp().
+
+ Removing the namespaces also removes the veth pair, addresses,
+ routes, and netem qdisc that live inside them. fail=False so
+ this is safe to call in error paths after a partial or complete setup.
+ """
+ cmd(f"ip netns del {NET0}", fail=False)
+ cmd(f"ip netns del {NET1}", fail=False)
+
+def get_iface_mac(iface):
+ """Return the MAC address of a local network interface."""
+ out = subprocess.check_output(['ip', 'link', 'show', iface], text=True)
+ mac = re.search(r'link/ether\s+([0-9a-f:]+)', out)
+ if not mac:
+ raise RuntimeError(f"Cannot determine MAC address of {iface}")
+ return mac.group(1)
+
+def setup_rdma():
+ """
+ Configure rdma network
+ """
+
+ # remove links left over by previously interrupted run.
+ teardown_rdma()
+
+ # use call here since modprobe may fail if the rdma_rxe
+ # module is built-in
+ subprocess.call(['modprobe', 'rdma_rxe'],
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+
+ ip(f"link add {VETH_RDMA0} type veth peer name {VETH_RDMA1}")
+
+ ip(f"link set {VETH_RDMA0} up")
+ ip(f"link set {VETH_RDMA1} up")
+
+ # Since both addresses are in the same namespace, the source address
+ # is always local, so enable accept_local
+ cmd(f"/usr/sbin/sysctl -q net.ipv4.conf.{VETH_RDMA0}.accept_local=1")
+ cmd(f"/usr/sbin/sysctl -q net.ipv4.conf.{VETH_RDMA1}.accept_local=1")
+
+ # Reverse path filters must be disabled so that the local routes don't
+ # cause RPF failures.
+ cmd(f"/usr/sbin/sysctl -q net.ipv4.conf.{VETH_RDMA0}.rp_filter=0")
+ cmd(f"/usr/sbin/sysctl -q net.ipv4.conf.{VETH_RDMA1}.rp_filter=0")
+
+ # add addresses
+ ip(f"addr add {rdma_addrs[0][0]}/32 dev {VETH_RDMA0}")
+ ip(f"addr add {rdma_addrs[1][0]}/32 dev {VETH_RDMA1}")
+
+ # add routes
+ ip(f"route add {rdma_addrs[1][0]}/32 dev {VETH_RDMA0}")
+ ip(f"route add {rdma_addrs[0][0]}/32 dev {VETH_RDMA1}")
+
+ # ARP will not resolve neighbor IPs on /32 routes without a subnet.
+ # Avoid this by adding neighbors directly so RDMA CM can populate path
+ # records with correct mac addrs without waiting for the ARP.
+ mac0 = get_iface_mac(VETH_RDMA0)
+ mac1 = get_iface_mac(VETH_RDMA1)
+ ip(f"neigh add {rdma_addrs[1][0]} lladdr {mac1} dev {VETH_RDMA0} nud permanent")
+ ip(f"neigh add {rdma_addrs[0][0]} lladdr {mac0} dev {VETH_RDMA1} nud permanent")
+
+ cmd(f'rdma link add {RXE_DEV0} type rxe netdev {VETH_RDMA0}')
+ cmd(f'rdma link add {RXE_DEV1} type rxe netdev {VETH_RDMA1}')
+
+ time.sleep(1) # allow RXE devices to initialise
+
+ # Start a packet capture on each network
+ if logdir is not None:
+ for iface in [VETH_RDMA0, VETH_RDMA1]:
+ pcap = logdir+'/rds-roce-'+iface+'.pcap'
+
+ tcpdump_cmd = ['/usr/sbin/tcpdump']
+ sudo_user = os.environ.get('SUDO_USER')
+ if sudo_user:
+ tcpdump_cmd.extend(['-Z', sudo_user])
+ tcpdump_cmd.extend(['-i', iface, '-w', pcap])
+
+ # pylint: disable-next=consider-using-with
+ p = subprocess.Popen(tcpdump_cmd,
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ tcpdump_procs.append(p)
+
+ # simulate packet loss, duplication and corruption
+ for iface in [VETH_RDMA0, VETH_RDMA1]:
+ cmd(f"/usr/sbin/tc qdisc add dev {iface} root netem \
+ corrupt {PACKET_CORRUPTION} loss {PACKET_LOSS} duplicate \
+ {PACKET_DUPLICATE}")
+
+def teardown_rdma():
+ """
+ Tear down the rdma network configured by setup_rdma().
+ """
+
+ # remove links left over by previously interrupted run.
+ cmd(f'rdma link del {RXE_DEV0}', fail=False)
+ cmd(f'rdma link del {RXE_DEV1}', fail=False)
+ cmd(f'ip link del {VETH_RDMA0}', fail=False)
+
+
#Parse out command line arguments. We take an optional
# timeout parameter and an optional log output folder
parser = argparse.ArgumentParser(description="init script args",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-d", "--logdir", action="store",
- help="directory to store logs", default="/tmp")
-parser.add_argument('--timeout', help="timeout to terminate hung test",
+ help="directory to store logs", default=None)
+parser.add_argument("-T", "--transport", default="tcp",
+ help="Comma-separated list of transports to test: "
+ "tcp, rdma, or tcp,rdma. Each matching test "
+ "is run once per transport. "
+ "'rdma' requires CONFIG_RDS_RDMA and rdma_rxe.")
+parser.add_argument('-t', '--timeout', help="timeout to terminate hung test",
type=int, default=0)
parser.add_argument('-l', '--loss', help="Simulate tcp packet loss",
type=int, default=0)
@@ -81,185 +490,67 @@ parser.add_argument('-u', '--duplicate', help="Simulate tcp packet duplication",
type=int, default=0)
args = parser.parse_args()
logdir=args.logdir
-packet_loss=str(args.loss)+'%'
-packet_corruption=str(args.corruption)+'%'
-packet_duplicate=str(args.duplicate)+'%'
-
-ip(f"netns add {net0}")
-ip(f"netns add {net1}")
-ip(f"link add type veth")
-
-addrs = [
- # we technically don't need different port numbers, but this will
- # help identify traffic in the network analyzer
- ('10.0.0.1', 10000),
- ('10.0.0.2', 20000),
-]
-
-# move interfaces to separate namespaces so they can no longer be
-# bound directly; this prevents rds from switching over from the tcp
-# transport to the loop transport.
-ip(f"link set {veth0} netns {net0} up")
-ip(f"link set {veth1} netns {net1} up")
-
-
-
-# add addresses
-ip(f"-n {net0} addr add {addrs[0][0]}/32 dev {veth0}")
-ip(f"-n {net1} addr add {addrs[1][0]}/32 dev {veth1}")
-
-# add routes
-ip(f"-n {net0} route add {addrs[1][0]}/32 dev {veth0}")
-ip(f"-n {net1} route add {addrs[0][0]}/32 dev {veth1}")
-
-# sanity check that our two interfaces/addresses are correctly set up
-# and communicating by doing a single ping
-ip(f"netns exec {net0} ping -c 1 {addrs[1][0]}")
-
-# Start a packet capture on each network
-for net in [net0, net1]:
- tcpdump_pid = os.fork()
- if tcpdump_pid == 0:
- pcap = logdir+'/'+net+'.pcap'
- subprocess.check_call(['touch', pcap])
- user = getpwuid(stat(pcap).st_uid).pw_name
- ip(f"netns exec {net} /usr/sbin/tcpdump -Z {user} -i any -w {pcap}")
- sys.exit(0)
-
-# simulate packet loss, duplication and corruption
-for net, iface in [(net0, veth0), (net1, veth1)]:
- ip(f"netns exec {net} /usr/sbin/tc qdisc add dev {iface} root netem \
- corrupt {packet_corruption} loss {packet_loss} duplicate \
- {packet_duplicate}")
-
-# add a timeout
-if args.timeout > 0:
- signal.alarm(args.timeout)
- signal.signal(signal.SIGALRM, signal_handler)
-
-sockets = [
- netns_socket(net0, socket.AF_RDS, socket.SOCK_SEQPACKET),
- netns_socket(net1, socket.AF_RDS, socket.SOCK_SEQPACKET),
-]
-
-for s, addr in zip(sockets, addrs):
- s.bind(addr)
- s.setblocking(0)
-
-fileno_to_socket = {
- s.fileno(): s for s in sockets
-}
-
-addr_to_socket = {
- addr: s for addr, s in zip(addrs, sockets)
-}
-
-socket_to_addr = {
- s: addr for addr, s in zip(addrs, sockets)
-}
-
-send_hashes = {}
-recv_hashes = {}
-
-ep = select.epoll()
-
-for s in sockets:
- ep.register(s, select.EPOLLRDNORM)
-
-n = 50000
-nr_send = 0
-nr_recv = 0
-
-while nr_send < n:
- # Send as much as we can without blocking
- print("sending...", nr_send, nr_recv)
- while nr_send < n:
- send_data = hashlib.sha256(
- f'packet {nr_send}'.encode('utf-8')).hexdigest().encode('utf-8')
-
- # pseudo-random send/receive pattern
- sender = sockets[nr_send % 2]
- receiver = sockets[1 - (nr_send % 3) % 2]
-
- try:
- sender.sendto(send_data, socket_to_addr[receiver])
- send_hashes.setdefault((sender.fileno(), receiver.fileno()),
- hashlib.sha256()).update(f'<{send_data}>'.encode('utf-8'))
- nr_send = nr_send + 1
- except BlockingIOError as e:
- break
- except OSError as e:
- if e.errno in [errno.ENOBUFS, errno.ECONNRESET, errno.EPIPE]:
- break
- raise
-
- # Receive as much as we can without blocking
- print("receiving...", nr_send, nr_recv)
- while nr_recv < nr_send:
- for fileno, eventmask in ep.poll():
- receiver = fileno_to_socket[fileno]
-
- if eventmask & select.EPOLLRDNORM:
- while True:
- try:
- recv_data, address = receiver.recvfrom(1024)
- sender = addr_to_socket[address]
- recv_hashes.setdefault((sender.fileno(),
- receiver.fileno()), hashlib.sha256()).update(
- f'<{recv_data}>'.encode('utf-8'))
- nr_recv = nr_recv + 1
- except BlockingIOError as e:
- break
-
- # exercise net/rds/tcp.c:rds_tcp_sysctl_reset()
- for net in [net0, net1]:
- ip(f"netns exec {net} /usr/sbin/sysctl net.rds.tcp.rds_tcp_rcvbuf=10000")
- ip(f"netns exec {net} /usr/sbin/sysctl net.rds.tcp.rds_tcp_sndbuf=10000")
-
-print("done", nr_send, nr_recv)
-
-# the Python socket module doesn't know these
-RDS_INFO_FIRST = 10000
-RDS_INFO_LAST = 10017
-
-nr_success = 0
-nr_error = 0
-
-for s in sockets:
- for optname in range(RDS_INFO_FIRST, RDS_INFO_LAST + 1):
- # Sigh, the Python socket module doesn't allow us to pass
- # buffer lengths greater than 1024 for some reason. RDS
- # wants multiple pages.
- try:
- s.getsockopt(socket.SOL_RDS, optname, 1024)
- nr_success = nr_success + 1
- except OSError as e:
- nr_error = nr_error + 1
- if e.errno == errno.ENOSPC:
- # ignore
- pass
-
-print(f"getsockopt(): {nr_success}/{nr_error}")
-
-print("Stopping network packet captures")
-subprocess.check_call(['killall', '-q', 'tcpdump'])
-
-# We're done sending and receiving stuff, now let's check if what
-# we received is what we sent.
-for (sender, receiver), send_hash in send_hashes.items():
- recv_hash = recv_hashes.get((sender, receiver))
-
- if recv_hash is None:
- print("FAIL: No data received")
- sys.exit(1)
-
- if send_hash.hexdigest() != recv_hash.hexdigest():
- print("FAIL: Send/recv mismatch")
- print("hash expected:", send_hash.hexdigest())
- print("hash received:", recv_hash.hexdigest())
- sys.exit(1)
-
- print(f"{sender}/{receiver}: ok")
-
-print("Success")
-sys.exit(0)
+PACKET_LOSS=str(args.loss)+'%'
+PACKET_CORRUPTION=str(args.corruption)+'%'
+PACKET_DUPLICATE=str(args.duplicate)+'%'
+
+# check transport is either tcp or rdma
+transports = [t.strip() for t in args.transport.split(',')]
+for t in transports:
+ if t not in ('tcp', 'rdma'):
+ raise SystemExit(f"test.py: unknown transport: {t!r}")
+
+# Register stop_pcaps before any network setups so that any partially setup
+# tcpdumps are still cleaned up on error
+atexit.register(stop_pcaps)
+
+# Set up all requested transports upfront so network plumbing is
+# ready before any test runs.
+transport_envs = {}
+FLAGS = 0
+if 'tcp' in transports:
+ # Register cleanups before setups to handle partial setups that error'd out
+ atexit.register(teardown_tcp)
+ setup_tcp()
+ transport_envs['tcp'] = {
+ 'addrs': tcp_addrs,
+ 'netns': [NET0, NET1],
+ 'flags': FLAGS | OP_FLAG_TCP,
+ }
+
+if 'rdma' in transports:
+ atexit.register(teardown_rdma)
+ setup_rdma()
+ transport_envs['rdma'] = {
+ 'addrs': rdma_addrs,
+ 'netns': None,
+ 'flags': FLAGS | OP_FLAG_RDMA,
+ }
+
+print("TAP version 13")
+print(f"1..{len(transport_envs)}")
+
+for transport, tenv in transport_envs.items():
+ tap_idx += 1
+
+ # add a timeout
+ if args.timeout > 0:
+ signal_handler_label = transport
+ signal.alarm(args.timeout)
+ signal.signal(signal.SIGALRM, signal_handler)
+
+ ret = snd_rcv_packets(tenv)
+
+ # cancel timeout
+ signal.alarm(0)
+
+ if ret == 0:
+ ksft_pr("Success")
+ print(f"ok {tap_idx} rds selftest {transport}")
+ nr_pass += 1
+ else:
+ print(f"not ok {tap_idx} rds selftest {transport}")
+ nr_fail += 1
+
+ksft_pr(f"Totals: pass:{nr_pass} fail:{nr_fail} skip:0")
+sys.exit(1 if nr_fail else 0)