fxTunnel

· Часть 1 серии «Пишу свой ngrok на Go: дневник разработки»

Пишу свой ngrok на Go: идея и первый прототип

Все нормальные люди готовят оливье, а я сижу и злюсь на ngrok. У меня куча проектов на локалке: бот для Telegram, API для мобильного приложения, веб-сервис для клиента.

Зачем я вообще это делаю

24 декабря 2025. Канун Нового года. Все нормальные люди готовят оливье, а я сижу и злюсь на ngrok. У меня куча проектов на локалке: бот для Telegram, API для мобильного приложения, веб-сервис для клиента. И каждый раз, когда нужно быстро показать результат — «вот, смотри, всё работает» — начинается цирк. ngrok просит деньги за кастомный домен. serveo.net то работает, то нет. localhost.run — ограничения. И тут я подумал: а что если написать своё? Полностью. С нуля. На своём сервере, без ограничений. Звучит как безумие? Наверное. Но руки чешутся — значит надо делать.

Идея за 5 минут на салфетке

Вся штука называется «обратный туннель» (reverse tunnel). Объясняю максимально просто: Представьте: у вас на ноутбуке крутится веб-приложение — пока только на localhost:3000. Его никто в мире не видит. А вам нужно показать заказчику. Прямо сейчас. Обратный туннель — это невидимый мост:

Заказчик в браузере
       ↓
Ваш сервер (VPS за $5/мес)
       ↓
  ~ магия ~
       ↓
Ваш ноутбук → localhost:3000

Заказчик открывает myapp.yourdomain.com — и видит ваш локальный сервис, как будто он задеплоен. Никакого деплоя, никакого Docker, никакого AWS. Просто мост между ноутбуком и интернетом. Фишка в том, что именно ваш ноутбук подключается к серверу, а не наоборот. Поэтому не нужен белый IP, не нужно ковырять роутер, вообще ничего не нужно настраивать на стороне сети.

Стек: почему Go

Я выбрал Go. Три причины: 1. Один файл = весь продукт. Go компилируется в один бинарник. Никаких npm install, никаких виртуальных окружений. Скопировал файл на сервер — работает. 2. Заточен под сеть. В Go есть горутины — штука, которая позволяет обрабатывать тысячи одновременных соединений без боли. Для туннелей — то что нужно. 3. Кросс-компиляция. Одной командой собираю бинарники под Linux, Windows, macOS. Клиент должен работать везде. Для мультиплексирования взял yamux от HashiCorp (это ребята, которые делают Terraform и Vault). Одно TCP-соединение — множество логических каналов внутри. Как множество вкладок в одном окне браузера.

Протокол: JSON, но хитрый

Как сервер и клиент общаются? Я придумал простой протокол: каждое сообщение — это JSON, но перед ним идут 4 байта с длиной. Зачем? Потому что TCP — потоковый, он не знает, где заканчивается одно сообщение и начинается другое. 4 байта длины решают эту проблему.

[4 байта: "сколько читать"] [JSON: "что делать"]

Типы команд простые: - auth — «привет, вот мой токен» - tunnel_request — «хочу туннель на порт 3000» - tunnel_created — «готово, твой адрес myapp.example.com» - new_connection — «к тебе пришёл пользователь, открывай канал» - ping/pong — «ты жив?» / «жив» Вот и весь протокол. Просто, читаемо, дебажится в два клика.

Самый важный кусок кода

Вся магия туннелирования — в двух строках. Серьёзно. Когда кто-то заходит на myapp.example.com, сервер уведомляет клиент. Клиент подключается к локальному сервису и запускает двунаправленное копирование:

go
go func() { io.Copy(local, stream) }()   // интернет → ваш сервис
go func() { io.Copy(stream, local) }()   // ваш сервис → интернет

io.Copy — встроенная функция Go, которая просто перекачивает байты из одного потока в другой. Одна горутина качает в одну сторону, другая — в обратную. Всё. Конечно, вокруг этих двух строк — ещё 3500 строк обвязки: аутентификация, управление туннелями, обработка ошибок, реконнект. Но ядро — вот оно.

Что получилось за один вечер

К ночи 24 декабря — один коммит, 3578 строк кода, и оно работает:

bash
# На сервере:
./fxtunnel-server --config server.yaml

# На ноутбуке:
./fxtunnel http 3000 --server myserver:4443 --token sk_xxx
# ✓ Tunnel created: myapp.example.com → localhost:3000

Что умеет: - HTTP-туннели — показывай веб-сервис через красивый поддомен - TCP-туннели — пробрасывай SSH, базу данных, что угодно - UDP-туннели — даже это (игровые серверы, например) - Автореконнект — отвалился WiFi? Клиент сам переподключится - Токенная аутентификация — не любой может создать туннель Ноль UI, ноль тестов, только консоль. Но работало — а это главное на данном этапе.

Грабли

Без них никуда: yamux роли. Клиент и сервер должны использовать разные роли при создании сессии. Перепутал — всё зависло без единой ошибки. Дебажил 40 минут, пока не прочитал документацию. Мораль: RTFM. Timeout на авторизацию. Без таймаута кто угодно мог подключиться к серверу и «повиснуть» навечно, занимая ресурсы. Добавил 30-секундный дедлайн — не авторизовался, до свидания.

Итог

За один вечер — работающий self-hosted аналог ngrok. До продакшена, конечно, далеко. Но основа есть, и она работает. Пользоваться можно уже сейчас — пусть и из консоли.


Коммит: 9e851ab — feat: initial implementation of fxTunnel reverse tunneling system В следующей части: как за один день превратить консольную утилиту в нормальный продукт с веб-панелью, десктопным приложением и CI/CD. Спойлер: 24 000 строк кода и пять фиксов линтера подряд.

Все части дневника разработки