splify2
Ядро steer

steer-obfs — серверная половина «WireGuard поверх TCP»#

Ставится на тот же VPS, где живёт WireGuard. Принимает поддельный TCP от роутеров и отдаёт разобранные датаграммы локальному WireGuard; обратно — так же. Ключей, конфигурации туннеля и доступа к нему не требует: для него это просто UDP-порт на localhost.

роутер                                  VPS
wg0 ──udp──► steer obfs ──поддельный TCP──► steer obfs-server ──udp──► wg (51820)

Каталог самодостаточен: в нём нет ничего, кроме установщика, юнита и этой страницы, и он переносится в отдельный репозиторий целиком, если серверную часть захочется выпускать отдельно от ядра. Единственная связь с ядром — исходники, из которых собирается тот же бинарник.

Настройка на стороне роутера — блок obfs у выхода kind: interface; поля и требования к MTU описаны в ../docs/contract-v1.md §1.

Установка#

Готовым архивом — так проще и компилятор на сервере не нужен:

# со страницы релизов: steer-obfs-<версия>-<x86_64|aarch64>.tar.gz
tar xzf steer-obfs-<версия>-x86_64.tar.gz
cd obfs-x86_64
sudo sh install.sh --port 4567 --forward 127.0.0.1:51820

Если страница релизов не открывается или архив не скачивается — скорее всего, у провайдера этого сервера закрыт githubusercontent.com: прямая ссылка релиза перенаправляет именно туда. Те же архивы лежат в ветке dist-vps, которую отдают хосты, не затронутые такой блокировкой. Установщик хаба (xs_install.sh) ходит по обоим адресам сам и называет каждый, который не ответил.

Бинарник в архиве статический (musl), собран из тех же исходников обфускации (obfs.c, obfsmain.c), что модуль steer-obfs в пакете для роутера: отдельного серверного кода нет и быть не должно — две реализации означали бы две обфускации, расходящиеся в мелочах на проводе. В пакете роутера тот же код разложен по libsteer.so и модулю, а здесь он в одном файле без библиотек. Статика означает, что дистрибутив и версия libc на VPS не имеют значения.

Из исходников — если своей архитектуры в релизе нет:

git clone https://github.com/splify2/steer && cd steer
sudo sh server/install.sh --port 4567 --forward 127.0.0.1:51820

Установщик один на оба пути: лежит рядом готовый бинарник — берёт его, нет — собирает системным cc (внешних зависимостей у базовой сборки нет вовсе). Кладёт его в /usr/local/sbin/steer, пишет /etc/default/steer-obfs и поднимает systemd-юнит steer-obfs. Повторный запуск — обновление на месте.

Настройки живут в /etc/default/steer-obfs, а не в юните: systemctl edit не понадобится, а обновление установщиком юнит перезапишет.

Проверить:

systemctl status steer-obfs
journalctl -u steer-obfs -f

Что нужно открыть#

  • TCP на порт обфускации (в примере 4567) — со стороны интернета. Это единственное, что видно снаружи; UDP-порт WireGuard наружу открывать больше не нужно и лучше закрыть: смысл затеи в том, чтобы снаружи не было видно UDP.
  • Порт WireGuard остаётся слушать на 127.0.0.1 или на всех адресах — сюда приходит уже локальный трафик.

Правило против RST ставит сам процесс (таблица nft inet steer_obfs) и снимает его при остановке. Оно нужно потому, что сокета на этом порту у ядра нет, и на входящий сегмент оно ответило бы RST, обрывая сессию. Если на сервере нет nft, поставьте эквивалент вручную:

iptables -I OUTPUT -p tcp --sport 4567 --tcp-flags RST RST -j DROP

MTU#

Внешний конверт — 20 байт IP плюс 20 байт TCP, сам WireGuard — ещё 32. Значит

MTU интерфейса wg = MTU канала − 72      (1428 при обычных 1500)

и он обязан совпадать на обеих сторонах туннеля. Расхождение не ломает связь целиком: маленькие пакеты ходят, большие пропадают — самый трудный в диагностике случай.

Совместимость#

Формат на проводе совместим с phantun: вместо этого сервера может стоять phantun_server, а вместо клиента на роутере — phantun_client. Отличия нашей стороны — случайный начальный номер, MSS в SYN и регулярные подтверждения — это поля, которые вторая сторона игнорирует.

Разница в требованиях к системе: phantun_server работает через TUN и требует ip_forward, DNAT на адрес tun-пира и правило NAT. Здесь ничего этого нет — сырой сокет и одно правило.

Чего этот слой не делает#

Не шифрует (это делает WireGuard) и не защищает от активного зондирования: поток без ретрансмиссий и без обновлений окна отличим от настоящего TCP при целенаправленном анализе. Задача — пройти там, где UDP режут или пропускают по белому списку протоколов, а не спрятаться от исследователя.