test(#21): netns-стенд — топология client/gw/uplink, lib и точка входа
Основы харнесса для тестов без реального 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
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env bash
|
||||
# topology.sh — netns-топология тестового стенда awg-dash (задача #21).
|
||||
#
|
||||
# topology.sh up создать netns, veth-пары, адреса, маршруты, правила меток
|
||||
# topology.sh down удалить netns и veth-пары (идемпотентно)
|
||||
# topology.sh status показать состояние топологии (exit 0 — стенд поднят)
|
||||
# topology.sh killswitch on включить fail-closed правило в шлюзе
|
||||
# topology.sh killswitch off выключить fail-closed правило в шлюзе
|
||||
# topology.sh fw-kind напечатать backend netfilter (nft|iptables)
|
||||
#
|
||||
# Требует root и CAP_NET_ADMIN (в контейнере — --privileged либо NET_ADMIN+SYS_ADMIN).
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=tests/netns/lib.sh
|
||||
source "$HERE/lib.sh"
|
||||
|
||||
topology_present() {
|
||||
ns_exists "$AWG_NS_CLIENT" && ns_exists "$AWG_NS_GW" && ns_exists "$AWG_NS_UPLINK"
|
||||
}
|
||||
|
||||
topology_down() {
|
||||
local quiet="${1:-}"
|
||||
[ "$quiet" = quiet ] || log "удаляю топологию"
|
||||
# netns удаляем первыми: вместе с ними уходят veth-концы и процессы внутри.
|
||||
for ns in "$AWG_NS_CLIENT" "$AWG_NS_GW" "$AWG_NS_UPLINK"; do
|
||||
if ns_exists "$ns"; then ip netns del "$ns" 2>/dev/null || true; fi
|
||||
done
|
||||
# страховка на случай, если veth остался в корневом netns
|
||||
for ifc in "$AWG_VETH_CLIENT" "$AWG_VETH_GW_LAN" "$AWG_VETH_GW_WAN" "$AWG_VETH_UPLINK"; do
|
||||
if ip link show "$ifc" >/dev/null 2>&1; then ip link del "$ifc" 2>/dev/null || true; fi
|
||||
done
|
||||
[ "$quiet" = quiet ] || ok "топология удалена"
|
||||
}
|
||||
|
||||
topology_up() {
|
||||
require_root
|
||||
require_cmd ip
|
||||
require_cmd python3
|
||||
require_netns_capability
|
||||
|
||||
log "создаю netns: client=$AWG_NS_CLIENT gw=$AWG_NS_GW uplink=$AWG_NS_UPLINK"
|
||||
topology_down quiet
|
||||
|
||||
local ns
|
||||
for ns in "$AWG_NS_CLIENT" "$AWG_NS_GW" "$AWG_NS_UPLINK"; do
|
||||
ip netns add "$ns"
|
||||
ip -n "$ns" link set lo up
|
||||
done
|
||||
|
||||
# --- client <-> gw (LAN) --------------------------------------------------
|
||||
ip link add "$AWG_VETH_CLIENT" type veth peer name "$AWG_VETH_GW_LAN"
|
||||
ip link set "$AWG_VETH_CLIENT" netns "$AWG_NS_CLIENT"
|
||||
ip link set "$AWG_VETH_GW_LAN" netns "$AWG_NS_GW"
|
||||
ip -n "$AWG_NS_CLIENT" addr add "${AWG_CLIENT_IP}/24" dev "$AWG_VETH_CLIENT"
|
||||
ip -n "$AWG_NS_CLIENT" link set "$AWG_VETH_CLIENT" up
|
||||
ip -n "$AWG_NS_GW" addr add "${AWG_GW_LAN_IP}/24" dev "$AWG_VETH_GW_LAN"
|
||||
ip -n "$AWG_NS_GW" link set "$AWG_VETH_GW_LAN" up
|
||||
|
||||
# --- gw <-> uplink (WAN) --------------------------------------------------
|
||||
ip link add "$AWG_VETH_GW_WAN" type veth peer name "$AWG_VETH_UPLINK"
|
||||
ip link set "$AWG_VETH_GW_WAN" netns "$AWG_NS_GW"
|
||||
ip link set "$AWG_VETH_UPLINK" netns "$AWG_NS_UPLINK"
|
||||
ip -n "$AWG_NS_GW" addr add "${AWG_GW_WAN_IP}/24" dev "$AWG_VETH_GW_WAN"
|
||||
ip -n "$AWG_NS_GW" link set "$AWG_VETH_GW_WAN" up
|
||||
ip -n "$AWG_NS_UPLINK" addr add "${AWG_UPLINK_IP}/24" dev "$AWG_VETH_UPLINK"
|
||||
ip -n "$AWG_NS_UPLINK" link set "$AWG_VETH_UPLINK" up
|
||||
|
||||
# --- forwarding и rp_filter в шлюзе ---------------------------------------
|
||||
ip netns exec "$AWG_NS_GW" sysctl -q -w net.ipv4.ip_forward=1
|
||||
# loose rp_filter: маркированная политика даёт асимметрию reverse-пути
|
||||
ip netns exec "$AWG_NS_GW" sysctl -q -w net.ipv4.conf.all.rp_filter=2
|
||||
ip netns exec "$AWG_NS_GW" sysctl -q -w "net.ipv4.conf.${AWG_VETH_GW_LAN}.rp_filter=2"
|
||||
ip netns exec "$AWG_NS_GW" sysctl -q -w "net.ipv4.conf.${AWG_VETH_GW_WAN}.rp_filter=2"
|
||||
|
||||
# --- маршруты -------------------------------------------------------------
|
||||
ip -n "$AWG_NS_CLIENT" route add default via "$AWG_GW_LAN_IP" dev "$AWG_VETH_CLIENT"
|
||||
ip -n "$AWG_NS_GW" route add default via "$AWG_UPLINK_IP" dev "$AWG_VETH_GW_WAN" metric 100
|
||||
ip -n "$AWG_NS_UPLINK" route add "$AWG_NET_CLIENT" via "$AWG_GW_WAN_IP" dev "$AWG_VETH_UPLINK"
|
||||
# «публичный» адрес-приманка для детектора утечки (RFC 5737 TEST-NET-2)
|
||||
ip -n "$AWG_NS_UPLINK" addr add "${AWG_LEAK_IP}/32" dev lo
|
||||
|
||||
# --- policy routing: fwmark 51820 -> table 51820 -> аплинк ----------------
|
||||
ip -n "$AWG_NS_GW" route add default via "$AWG_UPLINK_IP" dev "$AWG_VETH_GW_WAN" table "$AWG_ROUTE_TABLE"
|
||||
ip -n "$AWG_NS_GW" rule add fwmark "$AWG_FWMARK" table "$AWG_ROUTE_TABLE"
|
||||
|
||||
# --- netfilter: метки + kill-switch ---------------------------------------
|
||||
fw_setup
|
||||
|
||||
ok "стенд поднят: ${AWG_NS_CLIENT}(${AWG_CLIENT_IP}) -> ${AWG_NS_GW}(${AWG_GW_LAN_IP}/${AWG_GW_WAN_IP}) -> ${AWG_NS_UPLINK}(${AWG_UPLINK_IP})"
|
||||
ok "policy routing: fwmark ${AWG_FWMARK} -> table ${AWG_ROUTE_TABLE} (default via ${AWG_UPLINK_IP})"
|
||||
ok "mock endpoint: udp/${AWG_MOCK_PORT} в ${AWG_NS_UPLINK}; детектор утечки: ${AWG_LEAK_IP}:${AWG_LEAK_PORT}/udp"
|
||||
}
|
||||
|
||||
topology_status() {
|
||||
local rc=0
|
||||
if ! topology_present; then
|
||||
printf 'topology: down\n'
|
||||
return 1
|
||||
fi
|
||||
printf 'topology: up\n'
|
||||
local ns
|
||||
for ns in "$AWG_NS_CLIENT" "$AWG_NS_GW" "$AWG_NS_UPLINK"; do
|
||||
printf '\n[%s] links:\n' "$ns"
|
||||
ip -n "$ns" -br addr show || rc=1
|
||||
done
|
||||
printf '\n[%s] ip rule:\n' "$AWG_NS_GW"
|
||||
ip -n "$AWG_NS_GW" rule show
|
||||
printf '\n[%s] table %s:\n' "$AWG_NS_GW" "$AWG_ROUTE_TABLE"
|
||||
ip -n "$AWG_NS_GW" route show table "$AWG_ROUTE_TABLE"
|
||||
printf '\n[%s] netfilter:\n' "$AWG_NS_GW"
|
||||
if [ "$(fw_kind)" = nft ]; then
|
||||
ip netns exec "$AWG_NS_GW" nft list table inet "$NFT_TABLE" 2>/dev/null || rc=1
|
||||
else
|
||||
ip netns exec "$AWG_NS_GW" iptables -t mangle -S 2>/dev/null || rc=1
|
||||
ip netns exec "$AWG_NS_GW" iptables -t filter -S FORWARD 2>/dev/null || rc=1
|
||||
fi
|
||||
return $rc
|
||||
}
|
||||
|
||||
case "${1:-status}" in
|
||||
up)
|
||||
topology_up
|
||||
;;
|
||||
down)
|
||||
require_root
|
||||
topology_down
|
||||
;;
|
||||
status)
|
||||
topology_status
|
||||
;;
|
||||
killswitch)
|
||||
require_root
|
||||
fw_set_killswitch "${2:?usage: topology.sh killswitch on|off}"
|
||||
ok "kill-switch: ${2}"
|
||||
;;
|
||||
fw-kind)
|
||||
require_root
|
||||
fw_kind
|
||||
;;
|
||||
*)
|
||||
fail "usage: topology.sh {up|down|status|killswitch on|off|fw-kind}"
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user