You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

285 lines
9.6 KiB

#!/bin/bash
# run_test.sh — интеграционный тест tcp_proxy (transparent proxy)
#
# Топология (3 узла, трафик идёт через промежуточный узел транзитом):
# tcp_client.py (SO_MARK=1, dest=10.200.100.N) → table 100 → tun_test_proxy
# → tcp_proxy(lwIP, клиент) → ETCP → intermediate (транзит) → ETCP → exit
# → connect(10.200.100.N) → iptables DNAT (!mark=1) → 127.0.0.1 → echo → обратно
#
# Запуск: sudo ./run_test.sh
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
UTUN_BIN="$SCRIPT_DIR/../../utun"
LOG_DIR="$SCRIPT_DIR/log"
CLIENT_PY="$SCRIPT_DIR/tcp_client.py"
ECHO_PY="$SCRIPT_DIR/echo_server.py"
STRESS_PY="$SCRIPT_DIR/stress_client.py"
ECHO_PORT=19090
HC_PORT=19091
REFUSED_PORT=19099
N_IP=20
PASS=0; FAIL=0
# -------- setup / cleanup --------
setup_net() {
echo "=== Setting up networking ==="
sysctl -w net.ipv4.conf.all.rp_filter=0
# policy routing: marked packets → TUN (priority 199)
ip rule add fwmark 1 priority 199 table 100 2>/dev/null || true
local gw
gw=$(ip route show default | awk '/via/ {print $3; exit}')
[ -n "$gw" ] && ip route replace default via "$gw" table 100 2>/dev/null || true
# DNAT: unmarked packets to fake IPs → real localhost
for i in $(seq 1 $N_IP); do
iptables -t nat -C OUTPUT -d "10.200.100.${i}" -p tcp -m mark ! --mark 1 -j DNAT --to-destination 127.0.0.1:"$ECHO_PORT" 2>/dev/null \
|| iptables -t nat -A OUTPUT -d "10.200.100.${i}" -p tcp -m mark ! --mark 1 -j DNAT --to-destination 127.0.0.1:"$ECHO_PORT"
done
# conn_refused: .99 → порт без слушателя, exit получает ECONNREFUSED (иначе SYN уходит в tun_test_proxy и зацикливается)
iptables -t nat -C OUTPUT -d 10.200.100.99 -p tcp -m mark ! --mark 1 -j DNAT --to-destination 127.0.0.1:"$REFUSED_PORT" 2>/dev/null \
|| iptables -t nat -A OUTPUT -d 10.200.100.99 -p tcp -m mark ! --mark 1 -j DNAT --to-destination 127.0.0.1:"$REFUSED_PORT"
echo " Done"
}
setup_tun_route() {
for i in $(seq 1 60); do
if [ -e "/proc/sys/net/ipv4/conf/tun_test_proxy/rp_filter" ]; then
sysctl -w net.ipv4.conf.tun_test_proxy.rp_filter=0
ip route replace 10.200.100.0/24 dev tun_test_proxy table 100
ip route replace 10.200.100.0/24 dev tun_test_proxy # main table anti-martian
echo " tun route ready (${i}x0.3s)"
return 0
fi
sleep 0.3
done
echo " WARN: tun_test_proxy not found after 18s"
}
cleanup_net() {
echo "=== Cleaning up networking ==="
for i in $(seq 1 $N_IP); do
iptables -t nat -D OUTPUT -d "10.200.100.${i}" -p tcp -m mark ! --mark 1 -j DNAT --to-destination 127.0.0.1:"$ECHO_PORT" 2>/dev/null || true
done
iptables -t nat -D OUTPUT -d 10.200.100.99 -p tcp -m mark ! --mark 1 -j DNAT --to-destination 127.0.0.1:"$REFUSED_PORT" 2>/dev/null || true
ip rule del fwmark 1 priority 199 table 100 2>/dev/null || true
ip route del 10.200.100.0/24 dev tun_test_proxy table 100 2>/dev/null || true
ip route del 10.200.100.0/24 dev tun_test_proxy 2>/dev/null || true
ip route flush table 100 2>/dev/null || true
sysctl -w net.ipv4.conf.all.rp_filter=1
sysctl -w net.ipv4.conf.tun_test_proxy.rp_filter=1 2>/dev/null || true
echo " Done"
}
cleanup() {
echo ""; echo "=== Cleanup ==="
kill $EXIT_PID 2>/dev/null || true
kill $INTER_PID 2>/dev/null || true
kill $CLIENT_PID 2>/dev/null || true
kill $ECHO_PID 2>/dev/null || true
wait $EXIT_PID 2>/dev/null || true
wait $INTER_PID 2>/dev/null || true
wait $CLIENT_PID 2>/dev/null || true
wait $ECHO_PID 2>/dev/null || true
sleep 0.5; cleanup_net
}
wait_for_etcp() {
for i in $(seq 1 40); do
grep -q "Connection UP" "$LOG_DIR/exit_utun.log" 2>/dev/null \
&& grep -q "Connection UP" "$LOG_DIR/intermediate_utun.log" 2>/dev/null \
&& grep -q "Connection UP" "$LOG_DIR/client_utun.log" 2>/dev/null \
&& return 0
sleep 0.5
done
return 1
}
# -------- test runners --------
run_test() {
local name=$1; shift
local t_start log rc elapsed
t_start=$(date +%s%3N)
echo -n " $name ... "
log="$LOG_DIR/tcp_client_${name}.log"
if python3 "$CLIENT_PY" "$@" --name "$name" >"$log" 2>&1; then
rc=0
else
rc=$?
fi
elapsed=$(($(date +%s%3N) - t_start))
if [ $rc -eq 0 ]; then
echo "PASS (${elapsed}ms)"
tail -1 "$log"
return 0
else
echo "FAIL (rc=$rc, ${elapsed}ms)"
tail -3 "$log"
return 1
fi
}
run_test_neg() {
local name=$1; shift
local t_start log rc elapsed
t_start=$(date +%s%3N)
echo -n " $name ... "
log="$LOG_DIR/tcp_client_${name}.log"
if ! python3 "$CLIENT_PY" "$@" --name "$name" >"$log" 2>&1; then
rc=0
else
rc=1
fi
elapsed=$(($(date +%s%3N) - t_start))
if [ $rc -eq 0 ]; then
echo "PASS (expected error, ${elapsed}ms)"; return 0
else
echo "FAIL (expected error, got success, ${elapsed}ms)"; return 1
fi
}
run_concurrent() {
local name=$1 count=$2 size=$3; shift 3
local t_start pids ok elapsed
t_start=$(date +%s%3N)
pids=()
for i in $(seq 1 $count); do
python3 "$CLIENT_PY" \
--host "10.200.100.${i}" --port "$ECHO_PORT" \
--size "$size" --verify --timeout 15 \
--name "${name}_${i}" \
>"$LOG_DIR/tcp_client_${name}_${i}.log" 2>&1 &
pids+=($!)
done
ok=0
for pid in "${pids[@]}"; do wait "$pid"; [ $? -eq 0 ] && ((++ok)); done
elapsed=$(($(date +%s%3N) - t_start))
echo -n " $name ($count) ... "
if [ "$ok" -eq "$count" ]; then
echo "PASS ($ok/$count, ${elapsed}ms)"; return 0
else
echo "FAIL ($ok/$count, ${elapsed}ms)"
local fail=''
for i in $(seq 1 $count); do
grep -q "\[PASS\]" "$LOG_DIR/tcp_client_${name}_${i}.log" 2>/dev/null || fail+=" ${i}"
done
echo " failed:$fail"; return 1
fi
}
run_stress() {
local t_start elapsed
t_start=$(date +%s%3N)
echo -n " stress_sessions ... "
local log="$LOG_DIR/stress.log"
if python3 "$STRESS_PY" \
--host 10.200.100.1 --port-base "$ECHO_PORT" --ports 20 \
--threads 10 --iters 20 --min-size 1024 --max-size 65536 --timeout 60 --verbose \
>"$log" 2>&1; then
elapsed=$(($(date +%s%3N) - t_start))
echo "PASS (${elapsed}ms)"
head -1 "$log"; return 0
else
elapsed=$(($(date +%s%3N) - t_start))
echo "FAIL (${elapsed}ms)"
tail -5 "$log"; return 1
fi
}
# -------- main --------
if [ "$(id -u)" -ne 0 ]; then echo "ERROR: must be root"; exit 1; fi
[ -x "$UTUN_BIN" ] || { echo "ERROR: utun not found. Build first."; exit 1; }
echo "=== tcp_proxy full integration test ==="
mkdir -p "$LOG_DIR"; rm -f "$LOG_DIR"/*.log
setup_net
echo "Starting echo server on 127.0.0.1:$ECHO_PORT ..."
python3 "$ECHO_PY" 127.0.0.1 "$ECHO_PORT" >"$LOG_DIR/echo.log" 2>&1 &
ECHO_PID=$!; sleep 0.3
echo "Starting utun exit ..."
"$UTUN_BIN" -c "$SCRIPT_DIR/exit.conf" -f -l "$LOG_DIR/exit_utun.log" >"$LOG_DIR/exit_stdout.log" 2>&1 &
EXIT_PID=$!
echo "Starting utun intermediate ..."
"$UTUN_BIN" -c "$SCRIPT_DIR/intermediate.conf" -f -l "$LOG_DIR/intermediate_utun.log" >"$LOG_DIR/intermediate_stdout.log" 2>&1 &
INTER_PID=$!
echo "Starting utun client ..."
"$UTUN_BIN" -c "$SCRIPT_DIR/client.conf" -f -l "$LOG_DIR/client_utun.log" >"$LOG_DIR/client_stdout.log" 2>&1 &
CLIENT_PID=$!
trap cleanup EXIT
setup_tun_route
echo "Waiting for ETCP connection ..."
wait_for_etcp || { echo "ERROR: ETCP timeout"; tail -20 "$LOG_DIR/client_utun.log"; exit 1; }
echo "ETCP ready"; sleep 1
echo ""; echo "=== Running tests ==="
# 1: basic_1mb СНАЧАЛА (изолируем от стресса, чтобы понять — падает ли 1MB сам по себе)
run_test basic_1mb --host 10.200.100.1 --port "$ECHO_PORT" --size 1048576 --verify --timeout 10 \
&& ((++PASS)) || ((++FAIL))
sleep 1
# 2: stress (catch resource leaks)
run_stress && ((++PASS)) || ((++FAIL))
sleep 2
# 3: concurrent_5
run_concurrent concurrent_5 5 204800 && ((++PASS)) || ((++FAIL))
sleep 1
# 4: concurrent_20
run_concurrent concurrent_20 20 51200 && ((++PASS)) || ((++FAIL))
sleep 1
# 5: half_close
echo " half_close: one-shot echo on $HC_PORT ..."
python3 "$ECHO_PY" 127.0.0.1 "$HC_PORT" --read-timeout 2 >"$LOG_DIR/echo_hc.log" 2>&1 &
HC_PID=$!; sleep 0.3
run_test half_close --host 10.200.100.1 --port "$HC_PORT" --size 65536 --half-close --timeout 8 \
&& ((++PASS)) || ((++FAIL))
kill $HC_PID 2>/dev/null || true; wait $HC_PID 2>/dev/null || true
sleep 1
# 6: idle
run_test idle --host 10.200.100.1 --port "$ECHO_PORT" --size 32768 --verify --sleep-before 1 --timeout 5 \
&& ((++PASS)) || ((++FAIL))
sleep 1
# 7: conn_refused
run_test_neg conn_refused --host 10.200.100.99 --port "$REFUSED_PORT" --size 1 --verify --timeout 5 \
&& ((++PASS)) || ((++FAIL))
sleep 1
# 8-9: проверка что цепочка не заклинила после стресса
run_test basic_1mb_2 --host 10.200.100.1 --port "$ECHO_PORT" --size 1048576 --verify --timeout 15 \
&& ((++PASS)) || ((++FAIL))
sleep 1
run_test basic_1mb_3 --host 10.200.100.1 --port "$ECHO_PORT" --size 1048576 --verify --timeout 15 \
&& ((++PASS)) || ((++FAIL))
sleep 1
echo ""; echo "=========================================="
echo "Results: $PASS passed, $FAIL failed ($((PASS + FAIL)) total)"
echo "Logs: $LOG_DIR/"
echo "=========================================="
trap - EXIT; cleanup
[ "$FAIL" -gt 0 ] && exit 1
exit 0