Написал децентрализованную P2P меш-сеть на Go: QUIC, ChaCha20, прозрачный прокси и спутниковый интернет
Теги: go, p2p, mesh, quic, шифрование, self-hosted, сети, децентрализация
Вступление
Я живу на спутниковом интернете (SkyEdge, RTT ~1800мс). Каждое VPN-решение которое я пробовал либо плохо работало на высоком пинге, либо требовало настройки на каждом устройстве отдельно. Хотелось чего-то, что:
Настраивается один раз на домашнем сервере и защищает все устройства автоматически
Нормально работает при латентности 1-2 секунды
Не привязано к конкретному облачному провайдеру
Открытый код — можно проверить что происходит с трафиком
Так появился S.W.A.R.M. — Secure Worldwide Anonymous Routing Mesh.
Архитектура
[Телефон, ноутбук, телевизор, консоль] ↓ прозрачный прокси (TPROXY, без настройки на устройствах)[Домашний сервер — client нода S.W.A.R.M.] ↓ зашифрованный QUIC туннель ↓ ChaCha20-Poly1305 + X25519 обмен ключами[Bootstrap нода — VPS в другой стране] ↓ [Интернет] ↓ прозрачный прокси (TPROXY, без настройки на устройствах)
[Домашний сервер — client нода S.W.A.R.M.]
↓ зашифрованный QUIC туннель
↓ ChaCha20-Poly1305 + X25519 обмен ключами
[Bootstrap нода — VPS в другой стране]
↓
[Интернет]
Keenetic-роутер отдаёт домашнему серверу роль шлюза DHCP. Все устройства в сети автоматически идут через туннель — телефон, телевизор, игровая приставка — без единой настройки на каждом устройстве.
Технический стек
Транспорт: QUIC (quic-go v0.48.2)
Выбрал QUIC по нескольким причинам:
UDP — спутник блокирует меньше UDP чем TCP (и UDP не страдает от TCP head-of-line blocking)
Встроенный TLS 1.3 — handshake быстрее чем отдельный TLS поверх TCP
Multiplexed streams — несколько потоков в одном соединении
BBR congestion control — отлично работает на высоком RTT
Специально для спутника: HandshakeIdleTimeout: 30s (дефолтные 5с не хватает при 3 RTT × 1900мс = 5.7с), BBR включён на уровне ОС.
Шифрование: ChaCha20-Poly1305 + HKDF-SHA256
ChaCha20 быстрее AES-GCM на железе без аппаратного AES (мой домашний сервер — Pentium B960 2012 года). Poly1305 даёт аутентификацию — нельзя подменить или переиграть пакет.
Сессионные ключи выводятся через HKDF-SHA256 из ECDH shared secret — каждая сессия имеет уникальные ключи.
Идентичность: X25519 + Ed25519
X25519 — эфемерный DH для каждой сессии (PFS)
Ed25519 — подпись identity узла (защита от подмены bootstrap)
Протокол фрейма (packet.go)
┌──────────┬─────────┬──────────┬─────────────────┬──────────────┐│ 4 bytes │ 4 bytes │ 32 bytes │ N bytes │ 16 bytes ││ noise │ length │ nonce │ ciphertext │ Poly1305 MAC │└──────────┴─────────┴──────────┴─────────────────┴──────────────┘│ 4 bytes │ 4 bytes │ 32 bytes │ N bytes │ 16 bytes │
│ noise │ length │ nonce │ ciphertext │ Poly1305 MAC │
└──────────┴─────────┴──────────┴─────────────────┴──────────────┘
Нет magic bytes. Нет сигнатур. Трафик выглядит как случайный мусор — DPI не может отличить от любого другого UDP трафика.
Прозрачный прокси: TPROXY
// SO_TRANSPARENT позволяет bind() на чужой IPsetsockopt(fd, SOL_IP, IP_TRANSPARENT, &one, sizeof(one));// SO_ORIGINAL_DST получает оригинальный адрес назначенияgetsockopt(fd, SOL_IP, SO_ORIGINAL_DST, &addr, &addrLen);setsockopt(fd, SOL_IP, IP_TRANSPARENT, &one, sizeof(one));
// SO_ORIGINAL_DST получает оригинальный адрес назначения
getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, &addr, &addrLen);
iptables перенаправляет весь трафик на порт TPROXY, нода узнаёт оригинальный адрес назначения и устанавливает соединение на bootstrap узле.
Keepalive и устойчивость соединения
QUIC разрывает idle соединения через MaxIdleTimeout. Решение — MsgPing каждые 25 секунд (< 120с timeout). Пинг также используется для измерения RTT.
Для satellite это критично: соединение легко простаивает дольше таймаута пока скачивается большой файл (все данные идут в одну сторону, upstream пустой).
Защита от replay атак
В plaintext payload есть timestamp с точностью до наносекунды. При расшифровке проверяется расхождение часов:
MaxClockSkew = 5 * time.Minute // стандарт Kerberos/NTP5 минут — компромисс: защищает от replay, но не ломается на свежих VPS где NTP ещё не успел синхронизироваться (видел clock skew 5m37s на только что поднятой ноде One Dash).
Живая сеть
3 bootstrap ноды на 3 континентах:
Локация | Хостинг | Статус |
|---|---|---|
🇸🇪 Стокгольм | ✅ Работает | |
🇩🇪 Франкфурт | One Dash | ✅ Работает |
🇺🇸 Нью-Йорк | One Dash | ✅ Работает |
Домашний сервер видит все 3 ноды как peers, трафик балансируется round-robin. Протестирован failover — при падении Stockholm трафик уходит на Germany, при восстановлении peers снова 3.
Статус: https://stats.uptimerobot.com/p5plfaprdV
Что не реализовано (ещё)
DHT — сейчас peer discovery только через фиксированные bootstrap адреса
Circuit builder — явный multi-hop с выбором цепочки (Tor-style)
GUI клиенты — Windows/Android/iOS (только CLI сейчас)
Попробовать
Нужен любой Linux-сервер или роутер в домашней сети (Raspberry Pi, старый ПК, OpenWrt, Ubuntu-сервер):
# Установить client-ноду на домашний сервер/роутер:curl -sSL https://raw.githubusercontent.com/Rockenrol2017/swarm/main/install.sh | bash
# Требования: Linux, ~50 МБ RAM, root# После запуска: SOCKS5 прокси на 127.0.0.1:1090curl -sSL https://raw.githubusercontent.com/Rockenrol2017/swarm/main/install.sh | bash
# Требования: Linux, ~50 МБ RAM, root
# После запуска: SOCKS5 прокси на 127.0.0.1:1090
Два варианта использования:
1. Через прокси (без root на устройствах) — настрой браузер/систему на SOCKS5 127.0.0.1:1090
2. Прозрачный прокси (весь трафик LAN автоматически) — Keenetic/OpenWrt → шлюз = IP сервера → все устройства защищены без единой настройки на каждом
GitHub: https://github.com/Rockenrol2017/swarm (GPL v3)
Вопросы по настройке под конкретный роутер/железо — отвечу в комментариях.
Вопросы, замечания по архитектуре и предложения по безопасности — приветствуются в комментариях.