Files
awg-dash/tests/netns/README.md
T
embeddevio 94d3a28adf docs(#21): README netns и runbook dev-stand — ci.sh, приоритет AWG_NS_EXEC_MODE, DEV_SKIP_BUILD
Приведение документации к фактическому поведению после ревью PR #53 (раунд 2):
точка входа для CI без make (контракт 0/1/3/4), приоритет AWG_NS_EXEC_MODE над
make-алиасом, сборка без сети (DEV_SKIP_BUILD=1).

Refs #21
Refs #56
2026-10-28 21:58:15 +03:00

10 KiB
Raw Blame History

tests/netns — сетевой тестовый стенд

Задача #21, используется тестами split-tunnel (#40, #41, #43).

Цель: проверять маршрутизацию, policy routing, метки трафика и fail-closed без реального VPN-провайдера и без доступа к продакшн-сети.

Топология

        client ns                 gw ns (шлюз awg-dash)              uplink ns («ISP»)
   10.213.0.2/24   ──veth──►  10.213.0.1/24   10.214.0.1/24  ──veth──►  10.214.0.2/24
   (LAN-устройство)           ip_forward=1, ip rule fwmark 51820      мок-endpoint udp/51820
                              → table 51820 → default via uplink       детектор утечки
                                                                      198.51.100.9:9999
Namespace По умолчанию Роль Адреса
awgt21-client AWG_NS_CLIENT LAN-устройство за шлюзом 10.213.0.2/24
awgt21-gw AWG_NS_GW хост со шлюзом: forwarding, policy routing, netfilter 10.213.0.1/24, 10.214.0.1/24
awgt21-uplink AWG_NS_UPLINK «ISP»: мок WireGuard-endpoint + детектор утечки 10.214.0.2/24, 198.51.100.9/32

Правила в gw: ip rule fwmark 51820 lookup 51820 + table 51820 default via 10.214.0.2 (как в wg-quick), метки ставит netfilter на исходящий трафик шлюза (OUTPUT) и на транзитный трафик LAN (FORWARD). Kill-switch — правило DROP в FORWARD для трафика LAN, уходящего напрямую в «публичную» сеть 198.51.100.0/24 (RFC 5737 TEST-NET-2). Адрес детектора утечки поднят на lo аплинка: если при включённом kill-switch пакет дошёл до детектора — тест падает.

Запуск

make test-netns                 # поднять стенд, прогнать тесты, снести стенд
make test-netns-keep            # оставить топологию и логи для отладки
make test-netns NETNS_ARGS="--no-tests"      # только поднять стенд
make test-netns-ci              # то же в привилегированном контейнере (путь для CI)

bash tests/netns/ci.sh          # точка входа для CI #17: контейнер без make
                                # (make отдаёт 2 на любом провале — см. ниже)

bash tests/netns/topology.sh status          # состояние топологии
bash tests/netns/topology.sh down            # снести топологию вручную
bash tests/netns/topology.sh killswitch on   # включить fail-closed правило

Требования: Linux, root (или CAP_NET_ADMIN + CAP_SYS_ADMIN), ip, python3; backend netfilter — nft или iptables (в alpine-образе приложения есть только iptables, на Debian/Ubuntu — оба).

Коды возврата: 0 — тесты прошли, 1 — тесты упали, 3 — «заблокировано окружением» (нет прав на создание netns), 4 — ошибка самого стенда (сюда же нормализуются неожиданные коды утилит, например 255 от ip netns exec в контейнере).

В CI (#17) используйте bash tests/netns/ci.sh, а не make test-netns-ci: GNU make возвращает 2 на любой упавший рецепт, поэтому через make контракт 0/1/3/4 не виден и пайплайн не отличит «раннеру не дали прав» (3) от «регрессия стенда» (4). ci.sh запускает тот же контейнер напрямую и отдаёт его код как есть (2 у него означает «сломан вызов/нет docker/нет образа»).

Вход в namespace выполняется через ns_exec(): ip netns exec в контейнере падает (mount of /sys failed, issue #56), поэтому харнесс сам переключается на nsenter --net=/var/run/netns/<ns>; режим форсируется AWG_NS_EXEC_MODE (auto|ip|nsenter). Тот же режим через make — NETNS_EXEC_MODE (алиас; AWG_NS_EXEC_MODE имеет приоритет), например: AWG_NS_EXEC_MODE=ip make test-netns-ci.

Состав

Файл Назначение
run.sh точка входа: preflight → топология → мок → тесты → снос
ci.sh точка входа для CI #17: тот же прогон в контейнере без make, коды 0/1/3/4 доходят до пайплайна
lib.sh конфигурация стенда + правила netfilter (nft/iptables)
topology.sh up/down/status/`killswitch on
mock_wg_endpoint.py мок WireGuard-endpoint: отвечает на handshake initiation
leak_sink.py детектор утечки: UDP+TCP 198.51.100.9:9999
probe.py генератор тестового трафика из указанного netns
netns_common.py утилиты для тестов (ns_exec, счётчики правил, JSONL-логи)
run_tests.py прогон unittest + JSON-отчёт
test_netns_stand.py 8 интеграционных тестов стенда

Что проверяют тесты (test_netns_stand.py)

  1. test_01 — netns и veth-пары созданы, адреса выданы, ip_forward=1.
  2. test_02 — policy routing: ip rule fwmark 51820 → table 51820 → аплинк.
  3. test_03 — правила меток (OUTPUT/FORWARD) и DROP kill-switch на месте.
  4. test_04 — мок-endpoint и детектор утечки слушают и залогировали listen.
  5. test_05 — handshake initiation из LAN доходит до мока, мок отвечает (148 → 92 байта), счётчик правила метки растёт.
  6. test_06 — то же для трафика, инициированного самим шлюзом (OUTPUT-метка).
  7. test_07 — режим drop мока имитирует недоступность провайдера: ответа нет, пакеты фиксируются; возврат в respond восстанавливает стенд.
  8. test_08 — kill-switch: при выключенном правиле трафик доходит до детектора (положительный контроль), при включённом — утечки нет.

Параметры (переменные окружения)

Переменная По умолчанию Назначение
AWG_NETNS_PREFIX awgt21 префикс имён namespace/veth
AWG_NS_CLIENT / AWG_NS_GW / AWG_NS_UPLINK awgt21-* имена namespace
AWG_MOCK_PORT 51820 порт мока WireGuard-endpoint
AWG_LEAK_IP / AWG_LEAK_PORT 198.51.100.9 / 9999 адрес и порт детектора утечки
AWG_FWMARK / AWG_ROUTE_TABLE 51820 / 51820 метка и таблица policy routing
AWG_FW_BACKEND auto nft, iptables или автоопределение
AWG_NETNS_WORKDIR mktemp -d каталог логов и отчёта стенда
AWG_NS_EXEC_MODE auto вход в netns: ip, nsenter или auto (проба) — issue #56; make-алиас — NETNS_EXEC_MODE
AWG_NS_PRIVILEGED 1 только для ci.sh: 0 — прогон без privileged (проверка ветки «код 3»)
AWG_DASH_TEST_ARTIFACTS <repo>/artifacts каталог для netns-report.json (ci.sh)

Pitfalls

  • ip netns add в контейнере требует --privileged (CAP_SYS_ADMIN) — иначе run.sh завершится кодом 3 с сообщением «заблокировано окружением».
  • ip netns exec и ip -n <ns> в контейнере не работают (iproute2 6.x перемонтирует /sys: mount of /sys failed: Operation not permitted, rc=255), хотя ip netns add и nsenter --net=... работают. Поэтому в скриптах только ns_exec/ns_prefix (lib.sh) и ns_exec_mode() (netns_common.py), а все ip -n <ns> заменены на ns_exec <ns> ip ... (issue #56). Форсировать режим: AWG_NS_EXEC_MODE=nsenter|ip.
  • rp_filter в шлюзе выставлен в 2 (loose): маркированная политика даёт асимметрию reverse-пути, strict-режим молча дропает ответы.
  • Мок не умеет криптографию: он повторяет раскладку сообщений WireGuard (тип и длины), но не выполняет Noise IK handshake. Годится для топологии, меток, policy routing и fail-closed — не для проверки шифрования, MTU и пропускной способности.
  • nft в alpine-образе нет: в контейнере используется iptables (nft-backend). Стенд поддерживает оба backend; при отладке форсируйте AWG_FW_BACKEND=iptables.
  • ping в образе нет (пакет iputils не установлен) — тесты не используют ICMP: связность проверяется UDP-трафиком через мок и детектор утечки.
  • Остатки после падения: bash tests/netns/topology.sh down и ip netns list.