NAT traversal в embedded P2P-мессенджере на Go: почему overlay routing, а не STUN/TURN/ICE
- четверг, 4 июня 2026 г. в 00:00:11
Несколько месяцев пилю embedded P2P-мессенджер на Matrix-протоколе как личный pet-проект в свободное от основной работы время. Стек: форк Dendrite (Matrix homeserver на Go), Pinecone overlay routing от matrix.org research, gomobile bind для упаковки в .aar и .xcframework, modernc.org/sqlite вместо CGO-варианта (иначе gomobile капризничает). Не туториал и не “hello world на gomobile”, а серьёзная архитектурная амбиция в свободное время. Делюсь reasoning’ами почему такие архитектурные выборы и где они начинают течь.
Без обещаний неубиваемости. Проект в активной разработке, на этапе интеграции в клиентское приложение поверх Rust SDK matrix.org. Цифры приведу с явной маркировкой “где замерено на моём стенде, где плановая оценка, что ещё не проверено”. Production-NAT-кейсы (CGNAT, реальные мобильные сети) - впереди в следующем рывке. Если что-то принципиально новое всплывёт - напишу продолжение.
Стек, который сейчас собран и работает у меня дома:
Matrix-протокол, форк Dendrite (Matrix homeserver на Go).
Embedded на мобильнике через gomobile bind: .aar для Android, .xcframework для iOS.
modernc.org/sqlite вместо mattn/go-sqlite3 (pure Go, без CGO - иначе gomobile в продакшен-сборке начинает капризничать).
Pinecone от matrix.org как overlay-роутинг.
Клиентское приложение поверх Rust SDK matrix.org делает E2E (Olm/Megolm), моя Go-библиотека в шифрование не лезет - только маршрутизирует.
NATS JetStream встроен в Dendrite как async-брокер событий.
Это библиотека-обёртка, не shipping-мессенджер. Тестовые приложения с двумя кнопками собирал под Android и iOS чтобы проверить connectivity между устройствами. Финальное product-приложение клиентской части - в планах, пока что библиотека собирается, интегрируется в клиент через bridge, и проверяется на demo-сборках.
Что уже работает: P2P-обмен сообщениями между двумя устройствами в одной Wi-Fi сети, cross-platform (Android ↔ iOS прошёл), fallback на Matrix-сервер при недоступности P2P, multi-device sync, SSL pinning к серверу через SHA-256 SPKI. Что сейчас в работе: двусторонний handshake-протокол через два custom Matrix event types для включения P2P-режима, мультидевайс через overlay-форвардинг.
Когда сел проектировать NAT traversal, рассматривал четыре класса подходов. Без оценочных суждений в этом разделе, оценки дальше:
STUN/TURN/ICE (WebRTC-style). Классика. Клиенты обмениваются ICE candidates через signaling channel, пробивают NAT через STUN, при провале гонят трафик через TURN-сервер. Подробные русскоязычные разборы - WebRTC для всех и каждого (перевод webrtcforthecurious.com) и 5 ошибок при разработке WebRTC звонков от Voximplant.
libp2p. Модульный P2P-стек от Protocol Labs, используется в IPFS/Filecoin/Kubo. Kademlia DHT для discovery, AutoNAT v2 для определения reachability, DCUtR (Direct Connection Upgrade Through Relay) для hole punching, Circuit Relay v2 для fallback. Реализации на Go и Rust. Русскоязычные обзоры - P2P на Go: библиотека libp2p от Otus и flagship-перевод 2021 Азбука libp2p от Textile.
Overlay routing крипто-адресуемых сетей. Yggdrasil, cjdns, Hyperboria. У каждого узла IPv6-адрес производный от ed25519-ключа, маршрутизация по spanning tree (Yggdrasil) или по локально-вычисляемым координатам в виртуальном пространстве. NAT traversal неявный: узел открывает outbound TCP до bootstrap-пира, и связность достигается тем что 100% участников держат хотя бы одно outbound-соединение. Подробно - Что такое Yggdrasil Network от @Revertis (мейнтейнер Android-клиента) и Yggdrasil как встраиваемая библиотека от @AsciiMoth (форк yggdrasil-go).
Overlay routing matrix.org-style (Pinecone). Концептуально близко к Yggdrasil, но протокол спроектирован специально под Matrix federation. Двухслойная маршрутизация: SNEK (Sequentially Networked Edwards Key) для известных адресатов, spanning tree как fallback и для bootstrap’а. Open source - github.com/matrix-org/pinecone. На Habr не разобран, белое пятно.
Mesh-VPN корпоративного класса (Tailscale, ZeroTier, NetBird) тоже посмотрел - не подошли по сценарию (это VPN-доступ к собственным ресурсам, не peer-to-peer messaging). Подход librats + Hyperswarm + DCUtR из P2P в РФ: почему нужна система, а не протокол (kda2210, апрель 2026) - третий путь, DHT с hole-punching. Не делал бенчмарков, но в той статье есть конкретные измерения “1.4 MB RAM при 100 пирах vs 400 MB у JS-libp2p”.
Не из идеологии. Конкретно под этот use case шесть причин:
Нет естественного signaling channel. STUN/TURN/ICE предполагает что у клиентов уже есть способ обменяться SDP-offer/answer и ICE candidates. В P2P-мессенджере на Matrix-стеке такой канал, технически, есть - Matrix-сервер. Но архитектурная идея конкретно в том чтобы НЕ зависеть от него постоянно: сервер используется как “телефонная книга” pinecone-ключей и резервный канал доставки, не как signaling. Запустить overlay через сервер один раз для регистрации ключа и дальше коммуницировать через overlay - архитектурно чище.
CGNAT в мобильных сетях. МТС, Tele2, Билайн через UE-инициированный CGNAT по сути дают симметричный NAT. Hole punching через STUN там не работает. Все WebRTC-проекты в этом сценарии падают на TURN. Это значит обязательный production-grade TURN-фарм и постоянные расходы на relay-трафик. Автор статьи про Jami в России прямо подтверждает: “встроенные TURN-серверы turn.jami.net недоступны (подтверждено тестами)”. TURN-инфраструктуру всё равно придётся поднимать (relay-узлы в следующем рывке), но как часть overlay-семантики, а не отдельную WebRTC-инфру.
Бесплатные публичные STUN - известная ловушка. Voximplant в 5 ошибках при разработке WebRTC называет это ошибкой №1: “Не надейтесь на бесплатные STUN сервера (например, широко известные stun.l.google.com:19302) и тем более на ‘бесплатные’ TURN сервера”. Если строить production-grade мессенджер - нужен свой STUN/TURN. Это снова инфра.
Размер deps на embedded. Pure-Go WebRTC-стек (pion/webrtc + ICE-агент) тащит несколько мегабайт. Для серверной разработки это ничего, для мобильной библиотеки каждый мегабайт критичен. У меня и так с Dendrite + Pinecone production-сборка .aar выходит в районе 35-40 MB. Добавлять полноценный ICE-stack ради одного use case (peer-to-peer соединение) - перебор.
Мультидевайс плохо ложится на peer-to-peer connection. STUN/TURN/ICE - про установление соединения между двумя точками. У одного юзера может быть несколько устройств (телефон, планшет), и сообщение от собеседника должно дойти на все активные. Это или N×M ICE-соединений (плохо), или relay через signaling, что снова возвращает к Matrix-серверу как единой точке доверия, чего и пытаюсь избежать.
Synchronous offer/answer. WebRTC signaling предполагает синхронность установки сессии. Это нормально для video-call’а, но плохо ложится на async-семантику мессенджера, где сообщение может прийти через несколько часов после отправки, и где соединение между двумя пирами либо есть постоянно, либо его нет.
Это не значит что WebRTC плохой. Для video calling, browser-based applications, screen sharing - идеален. Просто у меня другая задача: persistent multihop routing между подписанными ключами, а не peer-to-peer media stream.
С libp2p отдельная история. Не подошёл по другим причинам:
Heavyweight для embedded. go-libp2p тащит Kademlia DHT, GossipSub, Noise/TLS handshake stack, несколько muxer’ов (Yamux, mplex), несколько транспортов. Для серверного P2P это нормально, для мобильной библиотеки набегает несколько MB только на dependency tree, и большая часть функционала мне не нужна (своя discovery через registry на Matrix-сервере, своя federation-семантика Matrix).
AutoNAT v2 на проде ещё прохладен. В апреле-мае 2026 опубликован cross-implementation audit от ProbeLab с конкретным root-cause: dialerHost в AutoNAT v2 наследует UDP black hole detector в read-only mode, и для свежих узлов (с пустой историей) detector трактует “пробное соединение неуспешно” как “соединение в blacklist”. Клиент остаётся в Unknown reachability state навсегда. Полный отчёт - github.com/probe-lab/autonat-perf-audit. Пробовал отправить fix в libp2p/go-libp2p (PR #3505), мейнтейнер закрыл с обоснованием что v2 не должен повторять архитектурный workaround от v1 - это, говорит, latch existing behavior, а не design pattern. Нормальная design conversation, но иллюстрирует что connectivity-стек ещё не стабилен.
Нет нативного fit с Matrix federation. Matrix S2S API - HTTP-like запросы между серверами. libp2p даёт streams. Заворачивать Matrix в libp2p streams значит писать кастомный wrapper и поддерживать его. Pinecone специально проектировался matrix.org research как HTTP-transport-compatible: federation идёт почти-обычными HTTP-запросами поверх overlay.
DCUtR требует Circuit Relay. Direct Connection Upgrade Through Relay сначала устанавливает соединение через relay, потом пытается upgrade’нуть до direct - это работает, но требует инфраструктуру relay-узлов и retry-цикл с известными граничными условиями. Те же relay-узлы у меня всё равно появятся (в следующем рывке), но в Pinecone-семантике (где relay - это просто узел overlay с лучшим reach), а не как отдельный libp2p-слой.
Снова - не приговор libp2p. IPFS, Filecoin, Kubo живут на нём, ProbeLab в общем заключении говорит что протокол работает. Для моего use case - overhead и mismatch архитектурной семантики.
Overlay routing в одной фразе: каждый узел открывает outbound TCP/WebSocket к одному из известных bootstrap-пиров, дальше трафик ходит многохопно через виртуальную сеть поверх обычного TCP. NAT проходится потому что соединение исходящее изнутри NAT’а - правило трансляции остаётся активным пока кому-то нужно отвечать через ту же сессию. Hole punching не нужен.
Это не магия и не “беспроигрышная альтернатива” - просто другой набор trade-off’ов. Overlay-подход переносит сложность из “договориться о direct connection между двумя пирами за NAT’ом” в “обеспечить связность виртуальной сети”. Yggdrasil делает это через крипто-spanning-tree. Pinecone - через комбинацию SNEK + spanning tree.
SNEK в одном абзаце. Sequentially Networked Edwards Key. Алгоритм построения виртуальной линии узлов, упорядоченной по ed25519-ключам. Каждый узел периодически отправляет bootstrap-сообщение в направлении возрастания ключа, ближайший по ключу сосед регистрирует себя как “descending” peer. В результате - двунаправленная линейная структура, по которой можно дойти от любого узла до любого через логарифмическое число хопов от размера сети. Реализация лежит в router/state_snek.go, периодический интервал bootstrap’а - 5 секунд.
Spanning tree. Параллельный механизм. Один узел сети объявляет себя root’ом (по наибольшему ed25519-ключу - детерминированно), root шлёт периодические TreeAnnouncement фреймы, узлы выстраивают tree относительно root’а. Координаты узла в tree - путь от root’а как вектор portID. Когда SNEK-путь неизвестен (свежий узел, ещё не накопил history), трафик идёт через tree.
Пять типов фреймов. Keepalive, TreeAnnouncement, Bootstrap (для SNEK setup), Traffic (пользовательский), WakeupBroadcast. Каждый фрейм имеет 10-байтный header с magic 0x70696e65 (ASCII pine). Описание - в types/frame.go.
Транспорты. Pinecone поддерживает Pipe (для тестов), Multicast (UDP в LAN), Bonjour (mDNS на macOS/iOS), Remote (TCP/WebSocket с TLS - основной production-транспорт), Bluetooth (через gomobile binding на мобильниках). Выбор маршрута приоритизирует Multicast < Remote < Bluetooth.
Самое главное для NAT-аргумента. В Pinecone нет STUN-клиента. Нет TURN-сервера. Нет ICE candidate negotiation. Цитата из README:
Pinecone peering connections look like regular TCP or WebSocket connections and will work fine through firewalls or NATs. If you make an outbound connection to a static node, you will still be able to receive incoming Pinecone traffic over that peering.
То есть: мобильный клиент знает адрес одного-двух bootstrap-узлов (свои Relay в дальнейшем плане или публичные узлы matrix.org для текущего PoC), открывает к ним outbound TCP+TLS, NAT создаёт трансляцию. Другие узлы overlay-сети узнают этот клиент через распространение TreeAnnouncement и шлют ему трафик через тот же канал обратным путём. Hole punching не нужен - связность достигается тем что 100% клиентов хотя бы один outbound держат всегда.
Упрощаю, конечно. В реальной сети есть очереди фреймов, watermarks для дедупликации, backpressure-механизмы, выбор маршрута между tree и SNEK при наличии обоих. Деталей - в Pinecone-репозитории, подкаталог docs/. Там же есть симулятор - cmd/pineconesim/, поднимает виртуальную сеть с веб-визуализацией на localhost:65432. Удобно если хочется поиграться с топологией.
Архитектура с Go-стороны:
Клиент (мобильное приложение на Rust SDK matrix.org) │ ▼ через gomobile bridge [Go-библиотека: моя обёртка] ├── Embedded Dendrite (Matrix homeserver) │ └─ Matrix federation API │ └─ через Pinecone overlay routing │ └─ TCP/WebSocket → bootstrap peer (outbound из NAT) │ ├── REST-клиент к Matrix-серверу (publish/get/deactivate Pinecone-ключа в registry) │ └─ через HTTPS с SSL pinning │ └── Колбэки в клиент (handshake events, lifecycle статусы)
Клиент через Rust SDK хранит расшифрованную историю и делает Olm/Megolm. Моя Go-библиотека в шифрование не лезет - маршрутизирует обёрнутые matrix-events через overlay, и всё.
Архитектурные решения, на которые сел после reasoning:
Защита per-chat, а не per-account. Юзер включает “щит” в конкретном чате через UI, не одной кнопкой на весь аккаунт. Pinecone-узел запускается один раз при первом щите в любом чате, переиспользуется для всех защищённых чатов, останавливается когда юзер выключает последний щит. Это даёт юзеру granular контроль и не заставляет постоянно держать P2P-стек для обычной переписки. Запуск идемпотентен: повторные вызовы из разных мест UI не пересоздают ключ и не делают повторных публикаций.
Двусторонний handshake через Matrix-события. Включение защиты идёт через стандартный Matrix Client API: два custom event types - один для запроса включения P2P-режима, второй для ответа. В payload - pinecone-ключ инициирующей стороны. Только после обоюдного согласия активируется overlay-доставка для этой комнаты. Это обходит проблему “первое сообщение через overlay требует уже знать peer’s key” - до handshake’а overlay-узел может быть даже не запущен.
modernc.org/sqlite вместо CGO-SQLite. Критично для gomobile. Стандартный mattn/go-sqlite3 - CGO-биндинг к libsqlite3.c. gomobile bind с CGO собирается, но требует Android NDK toolchain, увеличивает размер артефактов на несколько MB, и периодически ломается при обновлении gomobile (последний раз попал на это пару месяцев назад - дебаг-сценарий с lipo и манипуляциями над .xcframework). modernc.org/sqlite - pure Go, libsqlite3 транспилирован через c2go. Trade-off: примерно 5-10% медленнее на write-heavy workload (синтетика), зато gomobile собирается чисто без NDK-плясок.
Мультидевайс синхронизация через overlay, а не через Matrix-сервер. У одного юзера может быть несколько устройств. Каждое регистрирует свой Pinecone-ключ в registry на сервере. При получении overlay-сообщения от собеседника моя библиотека находит другие свои устройства через registry и форвардит им то же зашифрованное (Rust SDK расшифрует на их стороне) сообщение через Pinecone, с меткой forwarded:true для защиты от петель. Matrix-сервер в синхронизации между своими устройствами не участвует. Дедупликация на принимающей стороне - по Matrix event_id. Это standard multicast-style sync с loop-protection меткой, у Yggdrasil примерно та же идея.
SSL Pinning к Matrix-серверу через SHA-256 SPKI. Все HTTPS-запросы проверяют SHA-256 хеш Subject Public Key Info через кастомный http.Transport.VerifyPeerCertificate. Два хеша вшиты в код: основной + резервный. Это нужно для ротации сертификата без обновления приложения: вводим новый сертификат, в переходный период оба хеша валидны, через несколько недель приложение обновляется и primary заменяется. Известный паттерн, но в pure-Go реализации деталей хватает - возможно, отдельная статья.
Disclaimer перед таблицами: цифры - с моего тестового стенда, не из синтетических бенчмарков. Это PoC-сборки, не production-готовый артефакт. Production-NAT-кейсы (CGNAT, реальные мобильные сети) ещё впереди. Кроме того, не все измерения одинакового качества: iOS-цифры замерял на реальном устройстве через Xcode Instruments, для Android реальных замеров на флагмане пока нет, эмулятор недостоверен.
iOS, 30-минутный тест, iPhone 16 Pro Max:
Метрика | Значение |
|---|---|
RAM | 6 MB |
CPU при отправке/получении | 3% |
CPU средняя нагрузка | 1% |
Батарея | 0.1%/час |
Размер артефактов:
Артефакт | Значение | Замечание |
|---|---|---|
| 35-40 MB | Релизная сборка без debug-символов |
| около 68 MB | С debug-символами, тестовые сборки |
| 161 MB | Два слайса (device arm64 + simulator arm64), каждый около 80 MB. Production-замер не делал |
Android RAM/CPU отдельно. Замеры пока только на эмуляторе - получаю около 120 MB RAM и ~0.5% CPU при средней нагрузке. Эмулятор делит ресурсы с хост-системой и эти числа недостоверны для реального устройства. На реальном Android-флагмане замеры в планах после релиз-сборки. На уровне гипотезы - ожидаю в районе 70-110 MB RAM на Snapdragon 8 Gen 2-3 классе, но это вилка, не измерение.
Из чего основной вес .aar. Главный вклад: NATS JetStream (встроенный async-брокер событий внутри Dendrite, не вырезается - критичен для federation flow), gomatrixserverlib (Matrix-логика как таковая), crypto (Olm/Megolm библиотеки), Bleve (поисковый индекс - на 6 MB, в плане вырезать через build tags после стабилизации функционала). Pinecone сам по себе небольшой - около 1-2 MB чистого кода.
Cross-platform connectivity подтверждён. Android и iOS на реальных устройствах в одной Wi-Fi обмениваются сообщениями через Pinecone multicast. Cross-network (через bootstrap peer в другой сети) пока тестировал только в локальном setup’е с docker-сетью. Реальная мобильная сеть с CGNAT - впереди.
Multi-device fallback flow. Полный fallback реализован: если overlay не доходит (peer offline, нет route’а, timeout) - моя библиотека возвращает типизированную ошибку, и клиент отправляет сообщение стандартным Matrix-каналом через сервер. Шифрование (Olm/Megolm) делает Rust SDK независимо от overlay. То есть worst-case - обычный Matrix-клиент с E2E. Best-case - overlay-доставка вообще мимо сервера.
80-90% direct P2P без relay. Это плановая цифра из архитектурного видения, основана на статистике других overlay-сетей. Не моя измеренная. До production-relay-инфраструктуры и теста на реальном CGNAT подтвердить не могу.
Раздел про ограничения - обязательный, без него получится “магическая альтернатива WebRTC”, которой не бывает. Что overlay-подход на текущий момент не закрывает или закрывает с оговорками:
CGNAT в мобильных сетях. Когда оператор гонит всех клиентов через один шлюз, outbound TCP от мобильника к bootstrap-peer’у работает (правило трансляции открывается), но второй мобильник в той же сети не может стать сам bootstrap’ом для других. Нужны public-IP relay’и где-то снаружи. Это явная цена overlay-подхода: relay-узлы не “опционально”, они часть боевой архитектуры. У Tailscale это DERP-серверы, у I2P - Tor-relay’и, у Yggdrasil - публичные peer-lists, у Pinecone это будут мои relay-узлы в следующем рывке. Никто этого не избежал.
Cross-network NAT traversal в реальных условиях ещё не проверен. PoC работает на одной Wi-Fi через Pinecone multicast. Связность через bootstrap-peer в другой сети - тестировал в локальном Docker-setup’е, но реальную мобильную сеть с CGNAT ещё не гонял. Это в плане. Если что-то всплывёт принципиально неожиданное - напишу честно.
Invite routing нестабилен. Известная боль: invite-flow между Matrix-серверами через Pinecone overlay не всегда корректно маршрутизируется через многохоп. В PoC обхожу через создание комнаты без invite + join по room_id (юзер копирует room_id, не получает invite-event). Production-fix потребует или deeper rework Dendrite’s federation handler’а, или явного изменения UX.
iOS background - 30 секунд жизни процесса. Это не overlay-проблема, это Apple. Любое мобильное приложение в фоне убивается через ~30 секунд, все TCP-соединения дропаются. Решается стандартным паттерном: APNs push → Notification Service Extension → wake-up Pinecone-узла в lite-режиме на короткий срок, чтение pending overlay-сообщений, сохранение в Rust-SDK-store для UI. У Signal и Element X точно тот же подход. NSE имеет жёсткий 30-секундный лимит на работу, поэтому lite-режим Pinecone должен подняться за <5 секунд.
Android background - vendor wars. Foreground Service с уведомлением держит overlay-узел постоянно. Но на Xiaomi/Huawei/Samsung-оболочках Foreground Service всё равно убивается battery saver’ом, если юзер не добавил приложение в исключения. Это не моя вина, но юзеры воспринимают как мою. Понадобится отдельный гайд “добавьте в исключения battery saver’а на вашем телефоне”. Все мессенджеры в этом контексте в одной лодке, эталонная ссылка - dontkillmyapp.com.
Размер артефактов. .aar 35-40 MB production, .xcframework 161 MB debug - это существенно больше чем у lightweight WebRTC-стека (несколько MB). Стоит ли overlay-подход этого веса - зависит от того, готов ли продукт не зависеть от своего сигнального сервера. Для большинства мессенджеров - нет, дешевле взять WebRTC и держать TURN-фарм. Для моего use case (privacy-first, anti-blocking, мультидевайс) - да.
Overlay routing - не новая магическая альтернатива WebRTC, и не претендует. Это другой набор trade-off’ов:
меньше зависимость от STUN/TURN-инфраструктуры → больше зависимость от bootstrap/relay-узлов
меньше per-connection negotiation → больше per-network maintenance
меньше CGNAT-головной боли в connectivity → больше архитектурного веса в саму библиотеку
Под мой сценарий (privacy-first P2P-мессенджер на Matrix, embedded на мобильнике, мультидевайс) - работает. Под другие сценарии (browser-based, video conferencing, screen sharing) - не подойдёт, лучше WebRTC.
Если хочется поковырять самостоятельно - Pinecone open source, есть симулятор в cmd/pineconesim/ который поднимает виртуальную overlay-сеть на сотни узлов с веб-визуализацией. Удобно потрогать как трафик расходится при разной топологии. Yggdrasil тоже хороший entry-point если интересен этот класс задач, и AsciiMoth недавно написал отличный разбор Yggdrasil как библиотеки - похожая ниша, другая реализация.
После следующего рывка (relay-инфраструктура, CGNAT cross-network тестирование, push-flow для iOS) - посмотрю что вылезет в реальных мобильных сетях. Может, напишу продолжение с замерами уже на CGNAT и доли direct-vs-relay. А пока - спасибо что дочитали)
Pinecone и сопредельное:
Pinecone в github.com/matrix-org/pinecone - исходники и docs
matrix.org research на P2P Matrix - текущий статус P2P-направления Matrix
По NAT traversal и hole punching:
Tailscale: How NAT traversal works - стандарт ссылок по теме
Bryan Ford: Peer-to-Peer Communication Across Network Address Translators (2005) - классический paper по hole punching
WebRTC for the Curious - книга про WebRTC stack
libp2p:
libp2p docs - официальная документация, включая DCUtR и Circuit Relay v2
ProbeLab: AutoNAT v2 performance audit - cross-implementation audit
Соседние работы на Habr:
WebRTC для всех и каждого. Часть 1 - перевод webrtcforthecurious
5 ошибок при разработке WebRTC звонков - flagship по anti-pattern’ам
Что такое Yggdrasil Network - Revertis, май 2026
Yggdrasil как встраиваемая библиотека - AsciiMoth, май 2026
P2P в РФ: почему нужна система, а не протокол - librats/Hyperswarm с измерениями
Jami в России: почему гениальный P2P-мессенджер не работает - CGNAT в РФ
P2P на Go: библиотека libp2p - туториал
Использование оверлейных сетей для обхода NAT - сравнение Tor/I2P/Yggdrasil
dontkillmyapp.com - что делать с Android battery saver