- Fix queue_entry_free before queue_dgram_free in all proxy files (UAF bug causing segfault) - Add PROXY/RP prefixed diagnostic logs for transparent proxy flow tracking - Add tcp_proxy integration test: real utun instances, ETCP, remote_proxy, DNAT routing - 7 test scenarios: basic_1mb, concurrent_5, concurrent_20, half_close, idle, conn_refused, stress (10 threads x 20 iterations) - stress_client.py with --verbose per-thread phase timing (connect/send/recv)
123 lines
3.9 KiB
Python
Executable File
123 lines
3.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Stress test: parallel threads, sequential echo requests, data verification."""
|
|
|
|
import os
|
|
import socket
|
|
import sys
|
|
import threading
|
|
import random
|
|
import time
|
|
|
|
SO_MARK = 36
|
|
|
|
|
|
def worker(thread_id, host, ports, iters, min_size, max_size, timeout, verbose, results_raw):
|
|
ok = 0
|
|
times = []
|
|
for i in range(iters):
|
|
size = random.randint(min_size, max_size)
|
|
port = ports[i % len(ports)]
|
|
payload = os.urandom(size)
|
|
|
|
t0 = time.monotonic()
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
s.setsockopt(socket.SOL_SOCKET, SO_MARK, 1)
|
|
s.settimeout(timeout)
|
|
try:
|
|
t1 = time.monotonic()
|
|
s.connect((host, port))
|
|
t2 = time.monotonic()
|
|
s.sendall(payload)
|
|
t3 = time.monotonic()
|
|
|
|
received = bytearray()
|
|
while len(received) < size:
|
|
chunk = s.recv(min(65536, size - len(received)))
|
|
if not chunk:
|
|
break
|
|
received += chunk
|
|
t4 = time.monotonic()
|
|
|
|
if len(received) != size or received != payload:
|
|
raise RuntimeError(f"data mismatch")
|
|
ok += 1
|
|
times.append((size, int((t2-t1)*1000), int((t3-t2)*1000), int((t4-t3)*1000)))
|
|
except Exception as e:
|
|
results_raw[thread_id] = (False, f"FAIL iter={i} size={size}: {e} "
|
|
f"connect={int((t2-t1)*1000)}ms send={int((t3-t2)*1000)}ms recv={int((t4-t3)*1000)}ms")
|
|
try:
|
|
s.close()
|
|
except Exception:
|
|
pass
|
|
return
|
|
finally:
|
|
if s:
|
|
try:
|
|
s.close()
|
|
except Exception:
|
|
pass
|
|
|
|
agg_connect = sum(t[1] for t in times)
|
|
agg_send = sum(t[2] for t in times)
|
|
agg_recv = sum(t[3] for t in times)
|
|
total_bytes = sum(t[0] for t in times)
|
|
results_raw[thread_id] = (True, f"PASS {ok}/{iters} c={agg_connect}ms s={agg_send}ms r={agg_recv}ms total={total_bytes}B")
|
|
|
|
if verbose:
|
|
for i, (sz, cm, sm, rm) in enumerate(times):
|
|
print(f" TH{thread_id:02d} iter={i:02d} size={sz:6d} c={cm:4d}ms s={sm:4d}ms r={rm:4d}ms", flush=True)
|
|
|
|
|
|
def main():
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--host", required=True)
|
|
parser.add_argument("--port-base", type=int, required=True)
|
|
parser.add_argument("--ports", type=int, default=20)
|
|
parser.add_argument("--threads", type=int, default=10)
|
|
parser.add_argument("--iters", type=int, default=20)
|
|
parser.add_argument("--min-size", type=int, default=1024)
|
|
parser.add_argument("--max-size", type=int, default=1048576)
|
|
parser.add_argument("--timeout", type=float, default=60)
|
|
parser.add_argument("--verbose", action="store_true")
|
|
args = parser.parse_args()
|
|
|
|
ports = [args.port_base + i for i in range(args.ports)]
|
|
random.seed(os.urandom(8))
|
|
|
|
threads = []
|
|
results_raw = [None] * args.threads
|
|
|
|
t0 = time.monotonic()
|
|
|
|
for t in range(args.threads):
|
|
th = threading.Thread(
|
|
target=worker,
|
|
args=(t, args.host, ports, args.iters, args.min_size, args.max_size, args.timeout, args.verbose, results_raw),
|
|
daemon=True,
|
|
)
|
|
th.start()
|
|
threads.append(th)
|
|
|
|
for th in threads:
|
|
th.join()
|
|
|
|
elapsed = time.monotonic() - t0
|
|
|
|
passed = sum(1 for r in results_raw if r and r[0])
|
|
total_iters = args.threads * args.iters
|
|
|
|
print(f"[{'PASS' if passed == args.threads else 'FAIL'}] stress: "
|
|
f"{passed}/{args.threads} threads ({total_iters} sessions) in {elapsed:.1f}s", flush=True)
|
|
|
|
for i, r in enumerate(results_raw):
|
|
if r:
|
|
print(f" thread {i}: {r[1]}", flush=True)
|
|
if passed < args.threads:
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|