Как я сделал WireGuard-шлюз, который сам переживает блокировку VLESS-сервера

в 17:41, , рубрики: vpn, сетевая безопасность, Сетевые технологии, туннелирование

Привет, Habr! Рад представить сообществу проект, над которым корпел практически целый год.

Ссылка на git

Тяжело писать вступление для такой щепетильной темы, но так как мы на техническом ресурсе, я опущу все очевидные мотивы и просто опишу, что получилось.

Идея появилась, когда я решил попользоваться бесплатными VLESS-конфигами. Найти их нетрудно, а вот пользоваться постоянно невозможно: конфиг может перестать отвечать, потерять скорость или перестать пропускать отдельный сервис. Приходится возвращаться к подписке, перебирать варианты и искать замену вручную. Платные сервисы тоже этим грешат, что, если честно, вызывает жжение ниже спины. Вот одним вечером и появилось желание испытать себя и автоматизировать этот процесс.

Хотелось оставить на телефоне, ноутбуке и роутере по одному профилю, а поиск работающих конфигов перенести на сервер. Так появился Hydrat: шлюз, к которому устройства подключаются по WireGuard, а дальше их трафик уходит через выбранный VLESS или Tor. Источники могут обновляться, внешние серверы - отваливаться и возвращаться в строй. Профиль на конечном устройстве из-за этого менять не нужно.

Как это работает

Общий порядок такой:

  1. В админ-панель добавляются VLESS-ссылки, подписки и Tor-мосты. Hydrat собирает из них кандидатов и проверяет доступность, нужные сервсы и качество соединения.

  2. Прошедшие отбор кандидаты попадают в рабочий пул. Из него шлюз выбирает основные и резервные маршруты для клиентов.

  3. Пользователь подключается по обычному WireGuard. Xray, Tor и правила выбора внешнего выхода остаются на сервере; TCP и UDP могут уходить через разные маршруты.

  4. Hydrat продолжает следить за назначенными маршрутами. При отказе он подбирает замену, при ухудшении качества проверяет, достаточно ли хороша альтернатива, чтобы переносить на неё клиента.

В той же панели создаются WireGuard-клиенты, видны результаты проверок и текущие назначения. Для Tor поддерживаются мосты obfs4 и webtunnel.

WireGuard здесь нужен как постоянная точка входа. Когда внешний маршрут меняется, устройство продолжает обращаться к тому же шлюзу. До самого сервера Hydrat всё ещё нужно достучаться по WireGuard: выбор VLESS и Tor происходит уже за этим участком. Поэтому сервер должен находиться в Российской юрисдикции, у меня это домашний сервер с проектами.

У клиента два независимых назначения. TCP может использовать VLESS или подготовленный Tor-мост. UDP - только VLESS, который отдельно прошёл проверку UDP. Это позволяет сохранить работающий TCP, когда у того же выхода перестали проходить UDP-пакеты. Если замены для одного транспорта нет, блокируется только он. Самопроизвольного перехода всего трафика на прямой выход не происходит.

Поддерживаются geoip.dat файлы и кастомные правила маршрутизации, чтобы выбрать что должно ходить напрямую, а что через прокси. Приложения маркетплейсов перестали жаловаться на работающий VPN, что плюс к бесшовному опыту использования.

На сервере работают два Go-процесса в разных сетевых пространствах имён. Контроллер хранит источники и историю в SQLite, организует проверки, рассчитывает назначения и обслуживает панель. Агент управляет WireGuard, nftables, Xray и Tor, выполняет сетевые проверки и применяет рассчитанный план.

Так у принятия решения и его применения есть явная граница. Контроллер может готовить следующее назначение, пока агент обслуживает трафик по действующему. Уже применённые маршруты не снимаются при перезапуске контроллера. Сам агент сохраняет последний применённый план: после запуска ждёт Xray, восстанавливает выходы и правила и только затем сообщает о готовности. Это позволяет восстановить сетевую часть без одновременного перезапуска управляющей.

Как из подписки получается рабочий пул

После добавления подписки у шлюза появляется много Tor и VLESS конфигов, о которых пока ничего не известно. Полная проверка каждого требует времени и сетевых запросов. Если сразу поставить всю подписку в одну дорогую очередь, заведомо неработающие варианты будут занимать ресурсы наравне с перспективными.

Поэтому импортированный список и рабочий пул разделены. В базе может храниться до 10 000 уникальных кандидатов, в рабочем пуле - до 200. VLESS сначала проходит короткую предварительную проверку: восемь обработчиков, до трёх секунд на кандидата. Прошедшие её попадают в очередь полного тестирования, где работают четыре обработчика с лимитом 20 секунд. Здесь отсеиваются конфиги, которые доступны, но не подходят для клиентского трафика.

Приоритет получают ещё непроверенные кандидаты. Следом идут вернувшиеся после временного запрета, кандидаты с устаревающими результатами и те, которым не хватает второго успешного полного теста. История привязана к отпечатку конфигурации: повторное появление того же конфига в подписке не обнуляет накопленные результаты. При ошибке обновления сохраняется последний успешно полученный набор.

Полный тест проверяет Cloudflare/GStatic, YouTube, ChatGPT, OpenAI API, Telegram Web и Telegram MTProto (полный список проверямых сервисов доступен в репозитории). Дополнительно выполняется ограниченная загрузка для оценки скорости. Такой набор нужен, потому что успешное подключение к прокси оставляет слишком много вопросов: откроется ли нужный сайт, отвечает ли Telegram по своему протоколу, проходит ли трафик по UDP.

Кандиату нужны две успешные полные проверки. Оценка берётся по худшему из двух последних успешных измерений: краткий удачный результат не должен завышать ожидания от маршрута. После трёх последовательных ошибок быстрых или полных проверок кандидат временно исключается из отбора. Успешный полный тест сбрасывает счётчик ошибок: через пять часов запрет и счётчик также снимаются, но прежняя оценка остаётся устаревшей. Вернувшемуся кандидату нужно снова подтвердить своё состояние.

При заполненном рабочем пуле новый участник должен обойти худший маршрут минимум на 15%. Этот порог удерживает состав пула от перестановок из-за небольших различий в измерениях. Попадание в пул пока ничего не меняет на устройствах пользователей: решение о переносе клиента принимается отдельно.

Деградация рабочих маршрутов

После назначения клиенту маршрута, результаты первоначального отбора постепенно устаревают. Сервер может перестать отвечать сразу, а может часами пропускать запросы с растущими задержками. Для этих случаев нужны проверки с разной частотой и стоимостью.

Активный контроль отвечает за обнаружение отказа. У него отдельные слоты Xray, которые не может занять фоновый перебор подписки. В одном цикле параллельно выполняются два независимых HTTP-запроса и короткая DNS-проверка через маршрут. Загрузки для оценки скорости, QUIC и полного набора прикладных тестов здесь нет: они задержали бы решение о недоступности. Для подтверждения DNS-отказа нужны две соседние неудачи при успешном прямом контрольном запросе.

Ухудшение качества отслеживает отдельный контур QoE. У него свои четыре слота Xray и до четырёх обработчиков. VLESS измеряется через изолированный проверочный выход. Для Tor используется отдельный процесс с тем же мостом, собственным SOCKS-портом и каталогом данных.

QoE загружает 65 536 байт с контрольного сервера Cloudflare и измеряет время до первого байта и скорость передачи. На тест отводится десять секунд.

Первые пять успешных измерений формируют базовый уровень по медиане. Дальше Hydrat смотрит, насколько маршрут отклонился от своих обычных показателей. Время до первого байта считается плохим, если одновременно превышает 1500 мс и выросло более чем в 2,5 раза. Для скорости граница - падение ниже 35% от базового уровня. Ошибки соединения, TLS, запроса, чтения тела и тайм-ауты также учитываются.

Единичный плохой результат ещё не меняет состояние. Три плохих из последних пяти переводят маршрут в статус деградации, для восстановления нужны четыре хороших из пяти. Во время деградации базовый уровень заморожен, иначе затянувшееся замедление постепенно стало бы новой нормой. В исправном состоянии он обновляется по хорошим результатам.

Отдельно учитываются два подтверждённых отказа в окне из 20 измерений. Это нужно чтобы заметить сервер, который быстро отвечает между регулярными сбоями: несколько успешных загрузок подряд не должны скрывать потерянные запросы. Для восстановления число отказов в этом окне должно опуститься ниже двух. История хранится семь суток; изменение состояния записывается в SQLite одной транзакцией с измерением, которое его вызвало.

Когда клиента пора переносить

Теперь у Hydrat есть пригодные кандидаты, история их качества и сведения об отказах. Можно было бы после каждого измерения выбирать лидера и отправлять клиентов к нему. Но тогда небольшие колебания скорости превращались бы в постоянные изменения маршрутизации. Пользователь, у которого всё работает, получал бы перестановки, пользы от которых мог бы и не заметить.

Поэтому исправное назначение сохраняется. Планировщик использует алгоритм случайного взвешивания с наибольшим приоритетом (weighted rendezvous hashing) при первом выборе маршрута для клиента. Добавление других пользователей и колебания нагрузки не перетасовывают действующие маршруты: неактивные учётные записи не учитываются в нагрузке.

Плановый перенос требует устойчивого преимущества минимум в 30%. Клиент должен провести на текущем маршруте хотя бы 30 минут, а улучшение - подтвердиться на трёх проверках. Для сравнения QoE используется медианное эффективное время: время до первого байта плюс время передачи 64 КиБ. Оно учитывает и задержку начала ответа, и последующую скорость.

Такие переносы начинаются только в периодическом цикле размещения, по одному транспорту за цикл. Восстановившийся прежний маршрут автоматически клиентов назад не забирает. Порог 15% на входе в заполненный рабочий пул и эти 30% решают разные задачи: состав доступных вариантов можно улучшать, не трогая уже работающие подключения.

При отказе конфига - сначала контроллер рассматривает подготовленный резерв. Если подтверждение его доступности или сетевой обработчик устарели, ищет другого пригодного кандидата в общем пуле. Транспорт блокируется, только когда подходящих вариантов действительно не осталось; отсутствие готовой пары «основной - резервный» само по себе к блокировке не приводит.

Как переключить маршрут и не сломать его

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

Поэтому сетевую конфигурацию меняет один последовательный обработчик. Он сначала добавляет новый исходящий обработчик Xray, затем целиком заменяет набор правил маршрутизации и только после этого удаляет устаревшие выходы. К моменту изменения правил новый выход уже существует. Промежуточной блокировки клиента в этой последовательности нет.

С проверками проблема в порядке завершения. Допустим, проверка № 10 ещё ждёт ответа, а запущенная следом № 11 уже зафиксировала отказ. Затем десятая заканчивается успехом. Применив ответы в порядке прихода, контроллер ошибочно восстановил бы маршрут по более старому наблюдению.

Для быстрых, полных и активных проверок ведутся отдельные сохраняемые счётчики. Номер резервируется до сетевого запроса, а результат принимается, только если он новее последнего применённого результата своего вида. Сам запуск № 11 не отменяет № 10: они ещё могут корректно завершиться по порядку. Отклоняется именно запоздавший результат после уже применённого более нового.

Что получилось

В собственном тестировании проект уже чуть более двух месяцев, проверял на разных устройствах: от ТВ до роутера. Устанавливается через поднятие контейнера докера и заполнение .env, подробнее в репозитории. Сервис не решает проблему белых списков, но даёт предсказуемую стабильность по проводному соединению.

В планах расширить поддерживаемые протоколы, и добавить возможность смены входного WireGuard на что-то более незаметное для DPI систем, хотя пока проблем с использованием я не обнаружил.

Автор: hydrat

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js