79cbcab397
Основы харнесса для тестов без реального VPN-провайдера: - lib.sh — конфигурация стенда (netns, адреса, fwmark/таблицы) и правила netfilter с поддержкой nft и iptables (в alpine-образе приложения nft нет); - topology.sh — up/down/status/killswitch: veth-пары client<->gw<->uplink, ip_forward, ip rule fwmark 51820 -> table 51820, метки OUTPUT/FORWARD, DROP kill-switch для прямого выхода в «публичную» сеть; - run.sh — preflight, запуск мока и детектора утечки в netns uplink, прогон тестов, снос стенда с проверкой отсутствия остатков. Refs #21 Refs #15
158 lines
6.5 KiB
Bash
158 lines
6.5 KiB
Bash
#!/usr/bin/env bash
|
|
# run.sh — точка входа netns-стенда awg-dash (вызывается из `make test-netns`).
|
|
#
|
|
# tests/netns/run.sh [опции]
|
|
#
|
|
# Что делает:
|
|
# 1) preflight: root/CAP_NET_ADMIN/ip/python3;
|
|
# 2) поднимает netns-топологию (topology.sh up);
|
|
# 3) поднимает мок WireGuard-endpoint и детектор утечки внутри netns `uplink`;
|
|
# 4) прогоняет интеграционные тесты стенда (tests/netns/test_netns_stand.py);
|
|
# 5) сносит стенд и проверяет, что namespace'ы удалены.
|
|
#
|
|
# Опции:
|
|
# --keep-topology не удалять топологию после прогона (для отладки: make test-netns-keep)
|
|
# --keep-logs не удалять рабочий каталог с логами (по умолчанию сохраняется всегда
|
|
# при провале или при --keep-topology)
|
|
# --no-tests только поднять стенд и напечатать сводку
|
|
# --report PATH путь к JSON-отчёту (по умолчанию $AWG_NETNS_WORKDIR/report.json)
|
|
# --workdir PATH рабочий каталог стенда (логи, флаги готовности)
|
|
# -h|--help эта справка
|
|
#
|
|
# Коды возврата: 0 — успех; 1 — тесты упали; 3 — заблокировано окружением
|
|
# (нет root/CAP_NET_ADMIN); 4 — ошибка стенда.
|
|
set -euo pipefail
|
|
|
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# shellcheck source=tests/netns/lib.sh
|
|
source "$HERE/lib.sh"
|
|
|
|
KEEP_TOPO=0
|
|
NO_TESTS=0
|
|
REPORT=""
|
|
WORKDIR_OPT=""
|
|
|
|
usage() { sed -n '2,25p' "$HERE/run.sh" | sed 's/^# \{0,1\}//'; }
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--keep-topology) KEEP_TOPO=1 ;;
|
|
--keep-logs) : ;;
|
|
--no-tests) NO_TESTS=1 ;;
|
|
--report) REPORT="${2:?--report требует путь}"; shift ;;
|
|
--workdir) WORKDIR_OPT="${2:?--workdir требует путь}"; shift ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) fail "неизвестная опция: $1 (см. --help)" ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# --- preflight ---------------------------------------------------------------
|
|
require_root
|
|
require_cmd ip
|
|
require_cmd python3
|
|
require_netns_capability
|
|
|
|
if [ -n "$WORKDIR_OPT" ]; then
|
|
export AWG_NETNS_WORKDIR="$WORKDIR_OPT"
|
|
else
|
|
export AWG_NETNS_WORKDIR="${AWG_NETNS_WORKDIR:-$(mktemp -d -t awg-netns-XXXXXX)}"
|
|
fi
|
|
mkdir -p "$AWG_NETNS_WORKDIR"
|
|
: "${REPORT:=${AWG_NETNS_WORKDIR}/report.json}"
|
|
|
|
# пути артефактов процессов — в дочерние процессы уходят через окружение
|
|
export AWG_MOCK_LOG="${AWG_NETNS_WORKDIR}/mock-endpoint.jsonl"
|
|
export AWG_MOCK_READY="${AWG_NETNS_WORKDIR}/mock-endpoint.ready"
|
|
export AWG_MOCK_MODE_FILE="${AWG_NETNS_WORKDIR}/mock-mode"
|
|
export AWG_LEAK_LOG="${AWG_NETNS_WORKDIR}/leak-sink.jsonl"
|
|
export AWG_LEAK_READY="${AWG_NETNS_WORKDIR}/leak-sink.ready"
|
|
|
|
# чистое состояние артефактов прошлого прогона
|
|
rm -f "$AWG_MOCK_LOG" "$AWG_MOCK_READY" "$AWG_LEAK_LOG" "$AWG_LEAK_READY"
|
|
printf 'respond\n' > "$AWG_MOCK_MODE_FILE"
|
|
|
|
MOCK_PID=""
|
|
SINK_PID=""
|
|
TEST_RC=0
|
|
CLEANED=0
|
|
|
|
cleanup() {
|
|
[ "$CLEANED" -eq 1 ] && return 0
|
|
CLEANED=1
|
|
for pid in "$MOCK_PID" "$SINK_PID"; do
|
|
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
kill -TERM "$pid" 2>/dev/null || true
|
|
wait "$pid" 2>/dev/null || true
|
|
fi
|
|
done
|
|
if [ "$KEEP_TOPO" -eq 1 ]; then
|
|
warn "топология сохранена (--keep-topology): ${AWG_NS_CLIENT} ${AWG_NS_GW} ${AWG_NS_UPLINK}"
|
|
warn "снести: bash tests/netns/topology.sh down"
|
|
else
|
|
bash "$HERE/topology.sh" down >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
# --- стенд -------------------------------------------------------------------
|
|
log "рабочий каталог: ${AWG_NETNS_WORKDIR}"
|
|
bash "$HERE/topology.sh" up
|
|
|
|
log "запускаю мок WireGuard-endpoint (udp/${AWG_MOCK_PORT}) в ${AWG_NS_UPLINK}"
|
|
ip netns exec "$AWG_NS_UPLINK" python3 "$HERE/mock_wg_endpoint.py" \
|
|
--bind "0.0.0.0:${AWG_MOCK_PORT}" --log "$AWG_MOCK_LOG" --ready "$AWG_MOCK_READY" \
|
|
--mode-file "$AWG_MOCK_MODE_FILE" --mode respond &
|
|
MOCK_PID=$!
|
|
|
|
log "запускаю детектор утечки (${AWG_LEAK_IP}:${AWG_LEAK_PORT}) в ${AWG_NS_UPLINK}"
|
|
ip netns exec "$AWG_NS_UPLINK" python3 "$HERE/leak_sink.py" \
|
|
--ip "$AWG_LEAK_IP" --port "$AWG_LEAK_PORT" --log "$AWG_LEAK_LOG" --ready "$AWG_LEAK_READY" &
|
|
SINK_PID=$!
|
|
|
|
# ждём готовности обоих процессов (файлы-флаги пишутся после bind)
|
|
for _ in $(seq 1 100); do
|
|
if [ -f "$AWG_MOCK_READY" ] && [ -f "$AWG_LEAK_READY" ]; then break; fi
|
|
if ! kill -0 "$MOCK_PID" 2>/dev/null; then fail "мок WireGuard-endpoint упал при старте"; fi
|
|
sleep 0.1
|
|
done
|
|
[ -f "$AWG_MOCK_READY" ] || fail "мок WireGuard-endpoint не поднялся за 10с"
|
|
[ -f "$AWG_LEAK_READY" ] || fail "детектор утечки не поднялся за 10с"
|
|
ok "мок-endpoint и детектор утечки готовы"
|
|
|
|
# --- тесты -------------------------------------------------------------------
|
|
if [ "$NO_TESTS" -eq 1 ]; then
|
|
log "--no-tests: стенд поднят, тесты пропущены"
|
|
else
|
|
log "прогон интеграционных тестов стенда"
|
|
AWG_NETNS_RUN=1 AWG_NETNS_WORKDIR="$AWG_NETNS_WORKDIR" \
|
|
python3 "$HERE/run_tests.py" --report "$REPORT" || TEST_RC=$?
|
|
fi
|
|
|
|
# --- проверка сноса ----------------------------------------------------------
|
|
TOPOLOGY_REMOVED=true
|
|
if [ "$KEEP_TOPO" -eq 0 ]; then
|
|
cleanup
|
|
trap - EXIT INT TERM
|
|
for ns in "$AWG_NS_CLIENT" "$AWG_NS_GW" "$AWG_NS_UPLINK"; do
|
|
if ns_exists "$ns"; then
|
|
warn "после сноса остался netns ${ns} — стенд не очищен"
|
|
TOPOLOGY_REMOVED=false
|
|
TEST_RC=4
|
|
fi
|
|
done
|
|
[ "$TOPOLOGY_REMOVED" = true ] && ok "топология снесена, namespace'ы удалены"
|
|
fi
|
|
|
|
if [ "$NO_TESTS" -eq 1 ] && [ "$TOPOLOGY_REMOVED" = true ]; then
|
|
ok "стенд поднят успешно (AC-3: netns + мок-endpoint без реального провайдера)"
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$TEST_RC" -eq 0 ]; then
|
|
ok "netns-стенд: тесты пройдены; отчёт: ${REPORT}"
|
|
else
|
|
warn "netns-стенд: тесты/очистка упали (код ${TEST_RC}); логи: ${AWG_NETNS_WORKDIR}"
|
|
fi
|
|
exit "$TEST_RC"
|