You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
evgeny 39dd038831 Record ASAN validation of endpoint detector lifecycle 1 day ago
..
.gitignore Create dedicated stream network-namespace bench topology 1 day ago
README.md Measure owned endpoint mapping and inbound reachability separately 1 day ago
TESTED.md Record ASAN validation of endpoint detector lifecycle 1 day ago
nat_scenario.py Measure owned endpoint mapping and inbound reachability separately 1 day ago
network.py Measure owned endpoint mapping and inbound reachability separately 1 day ago
node.c Measure owned endpoint mapping and inbound reachability separately 1 day ago
probe.py Measure owned endpoint mapping and inbound reachability separately 1 day ago
run.py Measure owned endpoint mapping and inbound reachability separately 1 day ago
scenario.py Measure owned endpoint mapping and inbound reachability separately 1 day ago
test_isolation.py Document stream lab and test namespace isolation guards 1 day ago
topology.py Create dedicated stream network-namespace bench topology 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.