Sprint 0 / #21: dev/staging стенд — docker-compose, тома, netns-харнесс #53

Merged
embeddevio merged 30 commits from feat/21-dev-stand into main 2026-10-28 22:12:03 +03:00
3 changed files with 502 additions and 0 deletions
Showing only changes of commit 79cbcab397 - Show all commits
+201
View File
@@ -0,0 +1,201 @@
#!/usr/bin/env bash
# lib.sh — общие настройки и утилиты netns-стенда awg-dash (задача #21, эпик #15).
#
# Подключается через `source` из topology.sh / run.sh. Ничего не выполняет при
# подключении, ничего не создаёт — только конфигурация и вспомогательные функции.
#
# Все имена/адреса переопределяются переменными окружения (см. tests/netns/README.md).
# shellcheck shell=bash
# --- имена сетевых namespace'ов ---------------------------------------------
: "${AWG_NETNS_PREFIX:=awgt21}"
: "${AWG_NS_CLIENT:=${AWG_NETNS_PREFIX}-client}" # LAN-клиент за шлюзом
: "${AWG_NS_GW:=${AWG_NETNS_PREFIX}-gw}" # хост со шлюзом awg-dash
: "${AWG_NS_UPLINK:=${AWG_NETNS_PREFIX}-uplink}" # «ISP» + мок WireGuard-endpoint
# --- адресация --------------------------------------------------------------
: "${AWG_NET_CLIENT:=10.213.0.0/24}"
: "${AWG_CLIENT_IP:=10.213.0.2}"
: "${AWG_GW_LAN_IP:=10.213.0.1}"
: "${AWG_NET_WAN:=10.214.0.0/24}"
: "${AWG_GW_WAN_IP:=10.214.0.1}"
: "${AWG_UPLINK_IP:=10.214.0.2}"
# «Провайдер»/мок-endpoint: порт как в wg-quick.
: "${AWG_MOCK_PORT:=51820}"
# Адрес из TEST-NET-2 (RFC 5737) — «публичный» получатель для детектора утечки.
: "${AWG_LEAK_IP:=198.51.100.9}"
: "${AWG_LEAK_PORT:=9999}"
: "${AWG_LEAK_NET:=198.51.100.0/24}"
# --- policy routing ---------------------------------------------------------
# Метка и таблица как в wg-quick: fwmark 51820 -> table 51820.
: "${AWG_FWMARK:=51820}"
: "${AWG_ROUTE_TABLE:=51820}"
# --- имена интерфейсов и константы стенда -----------------------------------
: "${AWG_VETH_CLIENT:=veth-c-gw}"
: "${AWG_VETH_GW_LAN:=veth-gw-lan}"
: "${AWG_VETH_GW_WAN:=veth-gw-wan}"
: "${AWG_VETH_UPLINK:=veth-uplink}"
NFT_TABLE="awgtest"
C_MARK_OUT="awgtest-mark-out"
C_MARK_FWD="awgtest-mark-fwd"
C_DROP="awgtest-killswitch-drop"
IPT_CHAIN_MARK="awgtest-mark"
IPT_CHAIN_FWD="awgtest-mark-fwd"
IPT_CHAIN_KS="awgtest-ks"
# Рабочий каталог стенда (логи мока, sink'а, отчёты). Задаётся run.sh.
: "${AWG_NETNS_WORKDIR:=/tmp/awg-netns}"
# --- вывод ------------------------------------------------------------------
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
_c_red=$'\033[31m'; _c_grn=$'\033[32m'; _c_yel=$'\033[33m'; _c_dim=$'\033[2m'; _c_off=$'\033[0m'
else
_c_red=''; _c_grn=''; _c_yel=''; _c_dim=''; _c_off=''
fi
log() { printf '%s[netns]%s %s\n' "$_c_dim" "$_c_off" "$*"; }
ok() { printf '%s[ ok ]%s %s\n' "$_c_grn" "$_c_off" "$*"; }
warn() { printf '%s[warn]%s %s\n' "$_c_yel" "$_c_off" "$*" >&2; }
fail() { printf '%s[fail]%s %s\n' "$_c_red" "$_c_off" "$*" >&2; exit 1; }
# Код возврата 3 — «заблокировано окружением» (нет root/CAP_NET_ADMIN/ip),
# см. deploy/dev-stand.md. 4 — внутренняя ошибка стенда.
blocked() { printf '%s[blocked]%s %s\n' "$_c_yel" "$_c_off" "$*" >&2; exit 3; }
# --- предусловия ------------------------------------------------------------
require_root() {
[ "$(id -u)" -eq 0 ] || blocked "нужен root (или CAP_NET_ADMIN): запустите под sudo"
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || blocked "нет утилиты '$1' в PATH"
}
# Проверяем, что ядро/контейнер вообще позволяют netns (иначе AC невыполним).
require_netns_capability() {
local probe="${AWG_NETNS_PREFIX}-probe"
if ! ip netns add "$probe" 2>/dev/null; then
blocked "не удалось создать netns — нет CAP_NET_ADMIN/SYS_ADMIN (запустите контейнер с --privileged)"
fi
ip netns del "$probe" 2>/dev/null || true
}
ns_exists() { ip netns list | awk '{print $1}' | grep -qx "$1"; }
# --- netfilter: метки policy routing и kill-switch --------------------------
# Стенд работает и на nftables (хост Debian/Ubuntu с nft), и на iptables
# (alpine-образ приложения: iptables без бинаря nft). Backend выбирается по
# наличию бинаря в netns шлюза; семантика правил одинаковая.
fw_kind() {
# AWG_FW_BACKEND=nft|iptables|auto — принудительный выбор backend (в alpine-образе
# приложения есть только iptables, на хосте обычно nft).
local want="${AWG_FW_BACKEND:-auto}"
case "$want" in
nft)
ip netns exec "$AWG_NS_GW" sh -c 'command -v nft' >/dev/null 2>&1 && { echo nft; return; }
echo none; return
;;
iptables)
ip netns exec "$AWG_NS_GW" sh -c 'command -v iptables' >/dev/null 2>&1 && { echo iptables; return; }
echo none; return
;;
esac
# command -v — builtin shell, поэтому проверяем через sh -c внутри netns
if ip netns exec "$AWG_NS_GW" sh -c 'command -v nft' >/dev/null 2>&1; then
echo nft
elif ip netns exec "$AWG_NS_GW" sh -c 'command -v iptables' >/dev/null 2>&1; then
echo iptables
else
echo none
fi
}
fw_available() {
local kind; kind="$(fw_kind)"
case "$kind" in
nft) ip netns exec "$AWG_NS_GW" nft list ruleset >/dev/null 2>&1 ;;
iptables) ip netns exec "$AWG_NS_GW" iptables -t mangle -L -n >/dev/null 2>&1 ;;
*) return 1 ;;
esac
}
# Правило метки для трафика шлюза (OUTPUT) и для транзитного трафика LAN (FORWARD).
fw_setup() {
local kind; kind="$(fw_kind)"
case "$kind" in
nft)
ip netns exec "$AWG_NS_GW" nft delete table inet "$NFT_TABLE" 2>/dev/null || true
ip netns exec "$AWG_NS_GW" nft -f - <<EOF
table inet $NFT_TABLE {
chain mark_out {
type route hook output priority mangle; policy accept;
udp dport $AWG_MOCK_PORT counter meta mark set $AWG_FWMARK comment "$C_MARK_OUT"
return
}
chain mark_fwd {
type filter hook forward priority mangle; policy accept;
udp dport $AWG_MOCK_PORT counter meta mark set $AWG_FWMARK comment "$C_MARK_FWD"
}
chain forward_ks {
type filter hook forward priority filter; policy accept;
ip saddr $AWG_NET_CLIENT ip daddr $AWG_LEAK_NET counter drop comment "$C_DROP"
}
}
EOF
;;
iptables)
local ipt="ip netns exec $AWG_NS_GW iptables"
$ipt -t mangle -N "$IPT_CHAIN_MARK" 2>/dev/null || true
$ipt -t mangle -F "$IPT_CHAIN_MARK"
$ipt -t mangle -A "$IPT_CHAIN_MARK" -p udp --dport "$AWG_MOCK_PORT" \
-m comment --comment "$C_MARK_OUT" -j MARK --set-mark "$AWG_FWMARK"
$ipt -t mangle -C OUTPUT -j "$IPT_CHAIN_MARK" 2>/dev/null \
|| $ipt -t mangle -A OUTPUT -j "$IPT_CHAIN_MARK"
$ipt -t mangle -N "$IPT_CHAIN_FWD" 2>/dev/null || true
$ipt -t mangle -F "$IPT_CHAIN_FWD"
$ipt -t mangle -A "$IPT_CHAIN_FWD" -p udp --dport "$AWG_MOCK_PORT" \
-m comment --comment "$C_MARK_FWD" -j MARK --set-mark "$AWG_FWMARK"
$ipt -t mangle -C FORWARD -j "$IPT_CHAIN_FWD" 2>/dev/null \
|| $ipt -t mangle -A FORWARD -j "$IPT_CHAIN_FWD"
$ipt -t filter -N "$IPT_CHAIN_KS" 2>/dev/null || true
$ipt -t filter -F "$IPT_CHAIN_KS"
$ipt -t filter -A "$IPT_CHAIN_KS" -s "$AWG_NET_CLIENT" -d "$AWG_LEAK_NET" \
-m comment --comment "$C_DROP" -j DROP
$ipt -t filter -C FORWARD -j "$IPT_CHAIN_KS" 2>/dev/null \
|| $ipt -t filter -A FORWARD -j "$IPT_CHAIN_KS"
;;
*)
fail "нет ни nft, ни iptables в ${AWG_NS_GW} — стенд не может проверить маркировку"
;;
esac
log "правила меток/kill-switch установлены (backend: $kind)"
}
# killswitch on|off — включить/выключить fail-closed правило FORWARD.
fw_set_killswitch() {
local state="$1" kind; kind="$(fw_kind)"
case "$kind:$state" in
nft:on)
ip netns exec "$AWG_NS_GW" nft delete table inet "$NFT_TABLE" 2>/dev/null || true
fw_setup
;;
nft:off)
ip netns exec "$AWG_NS_GW" nft flush chain inet "$NFT_TABLE" forward_ks
;;
iptables:on)
fw_setup
;;
iptables:off)
ip netns exec "$AWG_NS_GW" iptables -t filter -F "$IPT_CHAIN_KS"
;;
*)
fail "fw_set_killswitch: неизвестное состояние '$state' (ожидается on|off)"
;;
esac
}
+157
View File
@@ -0,0 +1,157 @@
#!/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"
+144
View File
@@ -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