|
|
1 day ago | |
|---|---|---|
| .. | ||
| .gitignore | 1 day ago | |
| README.md | 1 day ago | |
| TESTED.md | 1 day ago | |
| nat_scenario.py | 1 day ago | |
| network.py | 1 day ago | |
| node.c | 1 day ago | |
| probe.py | 1 day ago | |
| run.py | 1 day ago | |
| scenario.py | 1 day ago | |
| test_isolation.py | 1 day ago | |
| topology.py | 1 day ago | |
README.md
Стенд стриминга в 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. Если политика системы запрещает их, запуск требует разрешения администратора; стенд сообщает ошибку и не меняет основную сеть.
Из корня репозитория:
make -C tests stream_netns/stream_lab_node
python3 tests/stream_netns/run.py --network-only
python3 tests/stream_netns/run.py
Проверка детектора на всех 16 настоящих uTun, отдельно от стриминга:
python3 tests/stream_netns/run.py --nat-only --tcp
--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 и несколько циклов проб. Короткая проверка:
python3 tests/stream_netns/run.py --duration 20
Ухудшение WAN, восстановление и отключение подписок:
python3 tests/stream_netns/run.py --duration 120 --faults
Источник за строгим NAT и доступность TCP в дополнение к UDP:
python3 tests/stream_netns/run.py --publisher n05 --tcp --faults
Чтобы требовать именно TCP/STCP, без возможности выбрать UDP:
python3 tests/stream_netns/run.py --publisher n05 --tcp-only --duration 20
Ту же проверку запускает отдельная ручная цель:
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.