|
|
22 hours ago | |
|---|---|---|
| .. | ||
| .gitignore | 22 hours ago | |
| README.md | 22 hours ago | |
| TESTED.md | 22 hours ago | |
| network.py | 22 hours ago | |
| node.c | 22 hours ago | |
| probe.py | 22 hours ago | |
| run.py | 22 hours ago | |
| scenario.py | 22 hours ago | |
| test_isolation.py | 22 hours ago | |
| topology.py | 22 hours 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
Полная 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 всегда симметричен.
Cone/restricted mapping разрешается только после исходящего пакета, время активности 60 секунд обновляется на каждом исходящем пакете. TCP использует обычный stateful NAT без проброса входящих портов. Проверка перед uTun измеряет реальный внешний адрес/порт на двух публичных получателях и отдельно пробует вход от третьего, включая отсутствие mapping до исходящего трафика. Для проб до/после открытия используются разные внешние tuples, чтобы не переиспользовать отрицательный conntrack.
После калибровки ее 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.