Теги: 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/NTP

5 минут — компромисс: защищает от replay, но не ломается на свежих VPS где NTP ещё не успел синхронизироваться (видел clock skew 5m37s на только что поднятой ноде One Dash).


Живая сеть

3 bootstrap ноды на 3 континентах:

Локация

Хостинг

Статус

🇸🇪 Стокгольм

hip.hosting

✅ Работает

🇩🇪 Франкфурт

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:1090

curl -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)

Вопросы по настройке под конкретный роутер/железо — отвечу в комментариях.


Вопросы, замечания по архитектуре и предложения по безопасности — приветствуются в комментариях.