Files
utun2/tests/stream_netns/README.md
T

148 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Стенд стриминга в network namespace
В стенде работают **16 отдельных процессов uTun**, каждый со своим veth, адресом, идентичностью и SQLite. Они создают CHAT-канал и вступают в него через штатный JOIN, ждут подписанные профили Merkle и BGP-маршруты, затем используют `stream_publish_open` / `stream_subscribe_open`. Пакеты, подписи, FEC, NCD, reverse и автоматический выбор поставщиков обрабатывает настоящее ядро. Время не ускоряется.
Все интерфейсы, маршруты, forwarding, nftables и tc создаются внутри новых user/mount/PID/network namespace. В основной сети выполняется только чтение снимков адресов, маршрутов, правил и qdisc до/после запуска. Нет TUN, default route наружу, именованных `ip netns`, сетевых мостов или firewall-правил на хосте. Внешние инструменты и пакетные зависимости стенд не устанавливает.
## Запуск
Нужны Linux, Python **3.10+**, iproute2 (`ip`, `tc`), nftables, ethtool, util-linux (`unshare`, `nsenter`), sysctl и поддержка user namespace, veth, bridge, IFB, HTB, netem, TBF и NAT в ядре. Обычно достаточно обычного пользователя с разрешенными user namespace. Если политика системы запрещает их, запуск требует разрешения администратора; стенд сообщает ошибку и не меняет основную сеть.
Из корня репозитория:
```sh
make -C tests stream_netns/stream_lab_node
python3 tests/stream_netns/run.py --network-only
python3 tests/stream_netns/run.py
```
Проверка детектора на всех 16 настоящих uTun, отдельно от стриминга:
```sh
python3 tests/stream_netns/run.py --nat-only --tcp
```
Резервное определение типа STUN на отдельных портах и переход к измерению ETCP:
```sh
python3 tests/stream_netns/run.py --stun-only --output /tmp/stun-interface-netns
```
Два RFC5780-сервера работают на двух реальных публичных IP, каждый с двумя UDP-портами.
По очереди проверяются n03 (cone), n14 (restricted) и n05 (dependent mapping).
Два рабочих порта одного IP/netif используют одну STUN-пробу. Сначала у владельца
нет ETCP-пиров, затем появляется один наблюдатель, затем независимый помощник.
Проверяются source=STUN -> source=ETCP, приоритет рабочего измерения и отсутствие
тестового внешнего порта в рекламе рабочего сокета. JOIN/стриминг в этом режиме не запускаются.
Случайные тестовые порты получают тот же класс NAT; правила для рабочего 32000 сохраняются.
Для испытания случайных портов активен один клиент каждого выбранного NAT-шлюза.
Расширенная классификация restricted/dependent включает реальные таймауты CHANGE,
поэтому этот режим обычно занимает несколько минут. Результат: `stun-result.json`.
`--nat-check` выполняет ту же проверку перед обычными JOIN/Merkle/BGP/стримингом.
Контроллер сначала запускает только ядро с ручными NAT-раундами, переносит
подписанные снимки через RPC и создаёт нужные NCD-подключения. Наблюдения,
испытания входа и TCP-handshake идут по настоящей сети. Помощники измерения
не требуют готового собственного NAT-профиля. Измерение входа происходит
до первого исходящего контакта рабочего сокета с помощником.
Проверяются отдельно mapping и inbound каждого UDP endpoint, точный собственный
внешний IP:port, отсутствие ложного STRICT без помощника и корректность TCP listener.
Ожидания: public — UNCHANGED/OPEN, cone — EIM/OPEN, restricted — EIM/CONTACT_REQUIRED,
strict — DEPENDENT без универсального внешнего адреса. TCP за NAT без port forwarding
остаётся UNKNOWN/no_listener. Результаты: `nat-profiles.json`, `nat-result.json`;
обычная телеметрия также включает `address_profiles`, контакты и причину неполного измерения.
Полная baseline-проверка длится 120 секунд после старта доставки, плюс калибровка и JOIN. Она покрывает оборот 16-битного timestamp и несколько циклов проб. Короткая проверка:
```sh
python3 tests/stream_netns/run.py --duration 20
```
Ухудшение WAN, восстановление и отключение подписок:
```sh
python3 tests/stream_netns/run.py --duration 120 --faults
```
Источник за строгим NAT и доступность TCP в дополнение к UDP:
```sh
python3 tests/stream_netns/run.py --publisher n05 --tcp --faults
```
Чтобы требовать именно TCP/STCP, без возможности выбрать UDP:
```sh
python3 tests/stream_netns/run.py --publisher n05 --tcp-only --duration 20
```
Ту же проверку запускает отдельная ручная цель:
```sh
make -C tests check-stream-netns STREAM_NETNS_ARGS='--duration 120 --faults'
```
Стенд не входит в обычный `make check` / `check.sh`. `--rate` задает входной bitrate в битах/секунду **до FEC и протокольных накладных расходов** (по умолчанию 1 Мбит/с). `--output` указывает новый каталог результатов; существующий каталог не перезаписывается. `--seed` фиксирует идентичности и генераторы netem/нагрузки; расписание процессов и случайные ключи канала/эпохи остаются недетерминированными.
## Сеть
| Сегмент | Узлы с публичными адресами | Узлы за NAT | Подсеть |
| --- | --- | --- | --- |
| A | n01, n02 | n03–n06 | 192.0.2.0/24 |
| B | n07, n08 | n09–n11 | 198.51.100.0/24 |
| C | n12, n13 | n14–n16 | 203.0.113.0/24 |
Всего 25 рабочих namespace: 16 узлов, 3 маршрутизатора стран, 6 NAT-шлюзов, плюс изолированный namespace контроллера. У каждого NAT своя частная LAN с одинаковой `10.64.1.0/24`: совпадение частных адресов между разными NAT намеренное. n16 имеет мобильный тип; его исключение из relay-кандидатов выполняет ядро.
| WAN | RTT | Jitter в одну сторону | Loss в одну сторону | Общая полоса в каждом направлении |
| --- | ---: | ---: | ---: | ---: |
| A↔B | 50 мс | 2 мс | 0,2% | 50 Мбит/с |
| A↔C | 100 мс | 5 мс | 0,5% | 30 Мбит/с |
| B↔C | 150 мс | 10 мс | 1% | 20 Мбит/с |
Внутри страны RTT около 2 мс, jitter 0,2 мс, без искусственных потерь. Задержка и loss применяются на **ingress узла через IFB** с классификацией по адресу физического отправителя. Это учитывает каждый переход потока через приложение и работает также для TCP, без искажения от TCP Small Queues на стороне отправителя. WAN-роутеры ограничивают совокупный трафик каждого межстранового линка через TBF; обходных IP-маршрутов нет. GSO/GRO/TSO на veth отключены. Размер очередей конечный; их состояние и drops сохраняются в снимках tc.
UDP NAT-профили:
- `g_a1`, `g_b1`: одинаковый внешний порт для разных назначений (EIM), вход от любого узла после открытия mapping (EIF).
- `g_c1`: EIM, вход только от ранее опрошенного IP/порта (APDF).
- `g_a2`, `g_b2`, `g_c2`: внешний UDP-порт зависит от **IP назначения** (ADM), возврат только по conntrack (APDF). Для известных внешних endpoints порты назначаются явно; это воспроизводимый строгий NAT, а не заявление, что обычный masquerade всегда симметричен.
Mapping разрешается после исходящего пакета, время активности/контакта 60 секунд обновляется исходящим трафиком.
Запрещённый вход отсекается в `prerouting priority raw` до conntrack. Иначе неуспешная
входящая проба могла закрепить tuple без DNAT и помешать последующему открытию фильтра.
TCP использует обычный stateful NAT без проброса входящих портов. Калибровка проверяет
внешний endpoint на разных назначениях, вход до mapping, незапрошенный вход и разрешённый
вход после исходящего контакта с тем же проверявшим узлом. После калибровки сеть пересоздаётся.
После калибровки ее namespace уничтожаются, затем создается новая сеть с теми же параметрами и пустыми NAT mappings/conntrack. Результаты проб не облегчают подключения реальных процессов. Снимки `network-calibration-initial.json` / `network-calibrated.json` относятся к пробам, `network-initial.json` / `network-final.json` — к uTun; команды содержат inode соответствующего namespace. PID в `namespaces.json` записаны относительно PID namespace контроллера.
## Что проверяется
Для каждого приложения DATA содержит sequence, ID источника, monotonic timestamp, seed и SHA256. Одна запись равна одной входной пачке 6+2; штатная кодограмма около 1300 байт. Получатели проверяют checksum, порядок, явные GAP, итоговый cursor и подписанный END. Начальный пропущенный префикс допустим: подписка начинает с последнего доступного блока. Дальнейшие пропуски должны быть полностью объяснены GAP; поврежденные или повторно доставленные записи приводят к FAIL.
One-way latency измеряется по общему CLOCK_MONOTONIC ядра Linux: time namespace не создается. Это внешняя проверка задержки; алгоритм стриминга продолжает использовать свой циклический timestamp. Histogram имеет шаг 1 мс, верхняя корзина 4095 мс; максимальная задержка записывается без ограничения.
При `--faults` A↔C снижается до 2 Мбит/с, RTT увеличивается до 300 мс, loss до 5%. После восстановления проверяется возобновление доставки. n02 и n13 затем закрывают подписки; счетчик полученных DATA обязан перестать расти. Перед окончанием проверяется END у оставшихся подписчиков и соответствие числа принятых/пропущенных записей числу записей источника. Каждый снимок проверяет предел данных кольца 32 MiB. GAP в условиях перегрузки записывается в отчет и сам по себе не считается повреждением.
Доступный TCP через `--tcp` не гарантирует, что ядро выберет TCP или несколько линков в конкретном запуске. Фактические соединения видны в `utun.log`; выбор поставщиков и пути DATA сохраняются в samples. Этот стенд проверяет доставку через реальные сетевые условия, но не подменяет отдельные unit-тесты криптографии и FEC.
## Результаты и диагностика
По умолчанию результаты находятся в новом `tests/stream_netns/runs/<date-time>/`:
- `result.json` — PASS/FAIL, ошибка, фазы, транспорт, bitrate, наблюдаемые обороты timestamp и результат сравнения основной сети; `host-check.json` — снимок сравнения, с исключением естественно уменьшающихся DHCP lifetime/cache timers.
- `calibration.json`, `topology.json`, `namespaces.json` — измерения, параметры, PID и inode namespace.
- `samples.jsonl`, `final-stats.json`, `last-stats.json`, `summary.csv` — доставленные записи, GAP, p50/p95/p99/max latency, буферы, пробы, миграции, source timestamp, rates/lag/stops и пути каждого субпотока. Последний снимок сохраняется также при FAIL.
- `network.jsonl`, `network-initial.json`, `network-final.json`, `g_*.nft` — точные команды, ошибки, маршруты, tc counters и правила NAT.
- `nXX/utun.log`, `stderr.log`, `rpc.jsonl`, `node.conf`, `db/` — диагностика и состояние каждого процесса. Конфиги содержат **тестовые** приватные ключи.
Категория `stream=debug`, JOIN/Merkle/BGP/connection — `info`; ошибки остальных категорий сохраняются. Для диагностики очередей можно запросить RPC `{"id":"1","op":"stats","loads":"1"}`; обычные снимки не опрашивают отсутствующие peer-load записи, чтобы не засорять лог. Stdout агента зарезервирован для JSONL.
Ctrl+C и нормальное завершение закрывают процессы и namespace. PID namespace с `--kill-child` также ограничивает время жизни процессов при аварии launcher; namespace не закрепляются mount-именами на хосте. Файлы результатов сохраняются для анализа.
Короткий TCP-only прогон выявил остановку оптимизатора при END первого поставщика, до дочитывания потребителем FEC-пачки. Для этого случая добавлена регрессия в `test_stream_service`: END устанавливает подписанную границу, автоматические пробы и восстановление продолжаются до прохождения consumer cursor за эту границу. Стенд проверяет завершение у всех подписчиков, а не наличие END в одном сетевом handle.
Прогон NAT-источника с ухудшением WAN выявил повтор DATA после restart через отстающего поставщика. Restart той же эпохи теперь сохраняет границу уже выданных DATA/GAP: старый подписанный bootstrap может установить общий старт, но не перематывает consumer cursor. Регрессии проверяют повтор bootstrap, перегрузку и два restart до доставки отложенного GAP.