Files
awg-dash/tests/netns/README.md
T
embeddevio 87cf5fe05f test(#21): интеграционные тесты netns-стенда и документация харнесса
- test_netns_stand.py — 8 тестов: топология и линки, policy routing по fwmark,
  правила меток и kill-switch, готовность мока и детектора, доставка handshake
  из LAN и из шлюза с ростом счётчика метки, имитация недоступности провайдера
  (mode=drop), положительный контроль утечки и блокировка при kill-switch.
  Тесты пропускаются вне run.sh, чтобы не падать при случайном pytest-прогоне;
- README.md харнесса — схема топологии, состав файлов, таблица тестов,
  переменные окружения и pitfalls (root/CAP_NET_ADMIN, rp_filter, отсутствие
  nft и ping в alpine-образе).

Refs #21
Refs #15
2026-10-21 23:48:51 +03:00

105 lines
7.4 KiB
Markdown

# 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 пакет
дошёл до детектора — тест падает.
## Запуск
```bash
make test-netns # поднять стенд, прогнать тесты, снести стенд
make test-netns-keep # оставить топологию и логи для отладки
make test-netns NETNS_ARGS="--no-tests" # только поднять стенд
make test-netns-ci # то же в привилегированном контейнере (путь для CI)
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 — оба). Код возврата `3` означает «заблокировано
окружением»: нет прав на создание netns.
## Состав
| Файл | Назначение |
|---|---|
| `run.sh` | точка входа: preflight → топология → мок → тесты → снос |
| `lib.sh` | конфигурация стенда + правила netfilter (nft/iptables) |
| `topology.sh` | `up`/`down`/`status`/`killswitch on|off` |
| `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` | каталог логов и отчёта стенда |
## Pitfalls
* **`ip netns add` в контейнере** требует `--privileged` (CAP_SYS_ADMIN) — иначе
`run.sh` завершится кодом 3 с сообщением «заблокировано окружением».
* **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`.