Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1#! /usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3
4import argparse
5import ctypes
6import errno
7import hashlib
8import os
9import select
10import signal
11import socket
12import subprocess
13import sys
14import tempfile
15import shutil
16
17# Allow utils module to be imported from different directory
18this_dir = os.path.dirname(os.path.realpath(__file__))
19sys.path.append(os.path.join(this_dir, "../"))
20from lib.py.utils import ip
21
22libc = ctypes.cdll.LoadLibrary('libc.so.6')
23setns = libc.setns
24
25NET0 = 'net0'
26NET1 = 'net1'
27
28VETH0 = 'veth0'
29VETH1 = 'veth1'
30
31# Helper function for creating a socket inside a network namespace.
32# We need this because otherwise RDS will detect that the two TCP
33# sockets are on the same interface and use the loop transport instead
34# of the TCP transport.
35def netns_socket(netns, *sock_args):
36 """
37 Creates sockets inside of network namespace
38
39 :param netns: the name of the network namespace
40 :param sock_args: socket family and type
41 """
42 u0, u1 = socket.socketpair(socket.AF_UNIX, socket.SOCK_SEQPACKET)
43
44 child = os.fork()
45 if child == 0:
46 # change network namespace
47 with open(f'/var/run/netns/{netns}', encoding='utf-8') as f:
48 try:
49 setns(f.fileno(), 0)
50 except IOError as e:
51 print(e.errno)
52 print(e)
53
54 # create socket in target namespace
55 sock = socket.socket(*sock_args)
56
57 # send resulting socket to parent
58 socket.send_fds(u0, [], [sock.fileno()])
59
60 sys.exit(0)
61
62 # receive socket from child
63 _, fds, _, _ = socket.recv_fds(u1, 0, 1)
64 os.waitpid(child, 0)
65 u0.close()
66 u1.close()
67 return socket.fromfd(fds[0], *sock_args)
68
69def signal_handler(_sig, _frame):
70 """
71 Test timed out signal handler
72 """
73 print('Test timed out')
74 sys.exit(1)
75
76#Parse out command line arguments. We take an optional
77# timeout parameter and an optional log output folder
78parser = argparse.ArgumentParser(description="init script args",
79 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
80parser.add_argument("-d", "--logdir", action="store",
81 help="directory to store logs", default="/tmp")
82parser.add_argument('--timeout', help="timeout to terminate hung test",
83 type=int, default=0)
84parser.add_argument('-l', '--loss', help="Simulate tcp packet loss",
85 type=int, default=0)
86parser.add_argument('-c', '--corruption', help="Simulate tcp packet corruption",
87 type=int, default=0)
88parser.add_argument('-u', '--duplicate', help="Simulate tcp packet duplication",
89 type=int, default=0)
90args = parser.parse_args()
91logdir=args.logdir
92PACKET_LOSS=str(args.loss)+'%'
93PACKET_CORRUPTION=str(args.corruption)+'%'
94PACKET_DUPLICATE=str(args.duplicate)+'%'
95
96ip(f"netns add {NET0}")
97ip(f"netns add {NET1}")
98ip("link add type veth")
99
100addrs = [
101 # we technically don't need different port numbers, but this will
102 # help identify traffic in the network analyzer
103 ('10.0.0.1', 10000),
104 ('10.0.0.2', 20000),
105]
106
107# move interfaces to separate namespaces so they can no longer be
108# bound directly; this prevents rds from switching over from the tcp
109# transport to the loop transport.
110ip(f"link set {VETH0} netns {NET0} up")
111ip(f"link set {VETH1} netns {NET1} up")
112
113
114
115# add addresses
116ip(f"-n {NET0} addr add {addrs[0][0]}/32 dev {VETH0}")
117ip(f"-n {NET1} addr add {addrs[1][0]}/32 dev {VETH1}")
118
119# add routes
120ip(f"-n {NET0} route add {addrs[1][0]}/32 dev {VETH0}")
121ip(f"-n {NET1} route add {addrs[0][0]}/32 dev {VETH1}")
122
123# sanity check that our two interfaces/addresses are correctly set up
124# and communicating by doing a single ping
125ip(f"netns exec {NET0} ping -c 1 {addrs[1][0]}")
126
127# Start a packet capture on each network
128tcpdump_procs = []
129for net in [NET0, NET1]:
130 pcap = logdir+'/'+net+'.pcap'
131 fd, pcap_tmp = tempfile.mkstemp(suffix=".pcap", prefix=f"{net}-", dir="/tmp")
132 p = subprocess.Popen(
133 ['ip', 'netns', 'exec', net,
134 '/usr/sbin/tcpdump', '-i', 'any', '-w', pcap_tmp])
135 tcpdump_procs.append((p, pcap_tmp, pcap, fd))
136
137# simulate packet loss, duplication and corruption
138for net, iface in [(NET0, VETH0), (NET1, VETH1)]:
139 ip(f"netns exec {net} /usr/sbin/tc qdisc add dev {iface} root netem \
140 corrupt {PACKET_CORRUPTION} loss {PACKET_LOSS} duplicate \
141 {PACKET_DUPLICATE}")
142
143# add a timeout
144if args.timeout > 0:
145 signal.alarm(args.timeout)
146 signal.signal(signal.SIGALRM, signal_handler)
147
148sockets = [
149 netns_socket(NET0, socket.AF_RDS, socket.SOCK_SEQPACKET),
150 netns_socket(NET1, socket.AF_RDS, socket.SOCK_SEQPACKET),
151]
152
153for s, addr in zip(sockets, addrs):
154 s.bind(addr)
155 s.setblocking(0)
156
157fileno_to_socket = {
158 s.fileno(): s for s in sockets
159}
160
161addr_to_socket = dict(zip(addrs, sockets))
162
163socket_to_addr = {
164 s: addr for addr, s in zip(addrs, sockets)
165}
166
167send_hashes = {}
168recv_hashes = {}
169
170ep = select.epoll()
171
172for s in sockets:
173 ep.register(s, select.EPOLLRDNORM)
174
175NUM_PACKETS = 50000
176nr_send = 0
177nr_recv = 0
178
179while nr_send < NUM_PACKETS:
180 # Send as much as we can without blocking
181 print("sending...", nr_send, nr_recv)
182 while nr_send < NUM_PACKETS:
183 send_data = hashlib.sha256(
184 f'packet {nr_send}'.encode('utf-8')).hexdigest().encode('utf-8')
185
186 # pseudo-random send/receive pattern
187 sender = sockets[nr_send % 2]
188 receiver = sockets[1 - (nr_send % 3) % 2]
189
190 try:
191 sender.sendto(send_data, socket_to_addr[receiver])
192 send_hashes.setdefault((sender.fileno(), receiver.fileno()),
193 hashlib.sha256()).update(f'<{send_data}>'.encode('utf-8'))
194 nr_send = nr_send + 1
195 except BlockingIOError as e:
196 break
197 except OSError as e:
198 if e.errno in [errno.ENOBUFS, errno.ECONNRESET, errno.EPIPE]:
199 break
200 raise
201
202 # Receive as much as we can without blocking
203 print("receiving...", nr_send, nr_recv)
204 while nr_recv < nr_send:
205 for fileno, eventmask in ep.poll():
206 receiver = fileno_to_socket[fileno]
207
208 if eventmask & select.EPOLLRDNORM:
209 while True:
210 try:
211 recv_data, address = receiver.recvfrom(1024)
212 sender = addr_to_socket[address]
213 recv_hashes.setdefault((sender.fileno(),
214 receiver.fileno()), hashlib.sha256()).update(
215 f'<{recv_data}>'.encode('utf-8'))
216 nr_recv = nr_recv + 1
217 except BlockingIOError as e:
218 break
219
220 # exercise net/rds/tcp.c:rds_tcp_sysctl_reset()
221 for net in [NET0, NET1]:
222 ip(f"netns exec {net} /usr/sbin/sysctl net.rds.tcp.rds_tcp_rcvbuf=10000")
223 ip(f"netns exec {net} /usr/sbin/sysctl net.rds.tcp.rds_tcp_sndbuf=10000")
224
225print("done", nr_send, nr_recv)
226
227# the Python socket module doesn't know these
228RDS_INFO_FIRST = 10000
229RDS_INFO_LAST = 10017
230
231nr_success = 0
232nr_error = 0
233
234for s in sockets:
235 for optname in range(RDS_INFO_FIRST, RDS_INFO_LAST + 1):
236 # Sigh, the Python socket module doesn't allow us to pass
237 # buffer lengths greater than 1024 for some reason. RDS
238 # wants multiple pages.
239 try:
240 s.getsockopt(socket.SOL_RDS, optname, 1024)
241 nr_success = nr_success + 1
242 except OSError as e:
243 nr_error = nr_error + 1
244 if e.errno == errno.ENOSPC:
245 # ignore
246 pass
247
248print(f"getsockopt(): {nr_success}/{nr_error}")
249
250print("Stopping network packet captures")
251for p, pcap_tmp, pcap, fd in tcpdump_procs:
252 p.terminate()
253 p.wait()
254 os.close(fd)
255 shutil.move(pcap_tmp, pcap)
256
257# We're done sending and receiving stuff, now let's check if what
258# we received is what we sent.
259for (sender, receiver), send_hash in send_hashes.items():
260 recv_hash = recv_hashes.get((sender, receiver))
261
262 if recv_hash is None:
263 print("FAIL: No data received")
264 sys.exit(1)
265
266 if send_hash.hexdigest() != recv_hash.hexdigest():
267 print("FAIL: Send/recv mismatch")
268 print("hash expected:", send_hash.hexdigest())
269 print("hash received:", recv_hash.hexdigest())
270 sys.exit(1)
271
272 print(f"{sender}/{receiver}: ok")
273
274print("Success")
275sys.exit(0)