Перейти к содержимому
VC
Кейс № 28 · Сети · Инфраструктура

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

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

Индустрия
Собственная инфраструктура · сети
Стек
WireGuard · Python (stdlib) · HTTP API роутера
Сроки
Ядро — 4 дня, доводка — месяцы эксплуатации
Итог
В туннель уходит только нужное
01 · Боль

Туннель нужен для части ресурсов, а включается на весь интернет

Задача формулируется в одну строку: несколько внешних ресурсов должны быть доступны с устройств в сети, все остальные — работать как обычно. Решение «в лоб» — поднять VPN на роутере и завернуть туда весь трафик — задачу закрывает и сразу создаёт три новые.

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

Штатный ответ прошивки на это — маршрутизация по спискам доменов: заводишь группу доменов, привязываешь её к интерфейсу туннеля, прошивка сама подставляет маршруты. На практике на основном сегменте сети этот механизм маршрутов не создаёт вообще — он лишь кэширует резолвенные адреса. Обнаруживается это не из документации, а по факту: домен резолвится, а трафик всё равно уходит мимо туннеля.

02 · Решение

Два механизма вместо одного нерабочего: маршруты и точечный DNS

Селективная маршрутизация ломается в двух местах сразу: имя резолвится не туда и пакет уходит не туда. Поэтому механизма два, и они закрывают разные половины задачи — статические маршруты отвечают за то, куда уйдёт пакет, точечные DNS-привязки — за то, какой адрес вообще будет получен.

01
Классификация

У ресурса собственные AS-диапазоны или он живёт на общем облачном фронте — от этого зависит метод

02
Маршруты

Статические маршруты по AS-диапазонам прямо в таблицу маршрутизации, интерфейс — туннель

03
DNS

Точечные привязки: только эти домены резолвятся публичным резолвером через туннель

04
Автоматизация

HTTP API роутера, challenge-response авторизация, батчи, сохранение конфигурации

05
Контроль

Диагностика утечек, снимок конфигурации перед изменением, watchdog на стороне VPS

Статические маршруты — там, где у сервиса есть свои AS-диапазоны

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

Общий облачный фронт статикой не добивается

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

DNS-привязки — только точечные, никогда глобальные

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

  • привязка делается на домен, а не глобально — иначе моргнувший туннель убивает резолв всей сети
  • апексы общих CDN и облачных API привязывать нельзя — по ним ходит половина остального интернета
  • привязка бренд-домена подхватывает поддомены суффиксным сопоставлением — список не приходится вести руками
  • резолв проверяется только из того же сегмента: межсегментные DNS-запросы режутся, и это даёт ложный диагноз

Мобильные и игровые клиенты ходят по прямым адресам

Отдельный класс сюрпризов: приложения нередко коннектятся к своим API и игровым серверам по прямым адресам, часто по UDP, вообще не спрашивая DNS. DNS-привязка их не спасает — нужны именно маршруты. WireGuard при этом несёт UDP прозрачно, так что вопрос только в полноте списка диапазонов.

Автоматизация — через HTTP API, потому что SSH ненадёжен

SSH на устройстве регулярно недоступен: слоты сессий залипают, порт уходит в timeout ровно тогда, когда что-то надо срочно поправить. Вся конфигурация переведена на HTTP API роутера, хелпер — на стандартной библиотеке Python:

  • авторизация challenge-response: сервер отдаёт realm и challenge, клиент считает производный хеш и не передаёт пароль
  • сессия живёт минуты — обёртка сама переавторизуется на 401 и повторяет запрос
  • массовые правки идут батчами по два-три десятка записей — API дедуплицирует одинаковые объекты удаления, и слишком большой батч молча теряет часть работы
  • изменение вступает в силу до перезагрузки, только если явно вызвано сохранение конфигурации — это отдельный шаг, а не побочный эффект

Диагностика утечек: что резолвится, но идёт мимо туннеля

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

Снимок конфигурации перед изменением и заготовленный откат

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

03 · Стек

Ничего экзотического — важно, где именно оно применено

WireGuard

Туннель до собственного VPS; UDP приложений проходит прозрачно

Собственный VPS

Единственная точка выхода туннеля: systemd-юнит + watchdog по расписанию

Статические маршруты

Записи по AS-диапазонам в таблице маршрутизации — единственный механизм, который реально уводит трафик

Точечные DNS-привязки

Per-domain резолв через туннель вместо подменённых ответов провайдерского резолвера

HTTP API роутера

Challenge-response авторизация, короткая сессия, авто-переавторизация — вместо нестабильного SSH

Python (stdlib)

urllib, http.cookiejar, hashlib — ни одной внешней зависимости, скрипт запускается где угодно

Сегментация сети

Три сегмента с разными политиками: полный туннель / селективно / напрямую

Снимки конфигурации

JSON-дамп маршрутов, привязок и групп доменов перед каждой партией правок

WireGuardPython stdlibHTTP API роутераStatic routesDNS-биндингиAS-диапазоныsystemdwatchdog
04 · Результат

Нужные ресурсы доступны, остальной интернет не замедлился

Доля трафика в туннеле
весь точечно

остальное идёт напрямую через провайдера, без потери скорости и без смены гео

Правил в работе
≈ 340

≈ 155 статических маршрутов + ≈ 190 точечных DNS-привязок, все заведены скриптом

Восстановление туннеля
≤ 2 мин

watchdog чинит битую конфигурацию сервиса сам, без ручного захода на сервер

Политика выбирается подключением к сети, а не перенастройкой устройства

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

Инцидент, который переопределил собственное правило

После очередной перезагрузки пропал весь интернет: не резолвилось вообще ничего, время не синхронизировалось, работал только туннель. Причина оказалась внешней — резолвер провайдера перестал отдавать корректные ответы. Лечение противоречило ранее записанному правилу «никогда не вешать глобальный резолвер на туннель»: пришлось игнорировать DNS-серверы, приходящие от провайдера по DHCP, и поднять публичный резолвер через живой туннель. Правило в шпаргалке было переписано вместе с условием, при котором оно перестаёт действовать, — это, пожалуй, полезнее самого фикса.

Смена точки выхода — это миграция всех правил, а не переключение тумблера

Когда пришлось перевести контур на другой VPS, выяснилась цена связки «маршруты + DNS»: перевести нужно и все маршруты, и все доменные привязки. Оставленная на мёртвом интерфейсе привязка не даёт ошибки — она просто тихо ломает резолв конкретного домена. Перенос сделан тем же скриптом, батчами, со сверкой после каждого шага.

Watchdog доучили на реальном отказе

Отдельный отказ: после перезагрузки VPS сервис туннеля не поднимался — в конфигурации остался пустой блок пира, и сервис падал при каждом старте. Watchdog это не ловил, потому что умел реагировать только на пропажу интерфейса и на протухший handshake. Теперь он дополнительно санирует конфигурацию перед пересборкой и триггерится на состояние «сервис в failed». Класс отказа стал самовосстанавливающимся.

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

05 · Применимость

Где ещё ложится та же методология

Это кейс не про домашний роутер. Это типовая задача «часть трафика — по особому маршруту, остальное — как обычно, и всё это воспроизводимо». Тот же подход переносится почти без изменений:

  • Офис или филиал, которому нужны конкретные внешние сервисы, но нельзя сажать всю сеть на один арендованный канал
  • Гео-зависимые внешние API — через выделенный exit-адрес идут только вызовы к ним, остальной трафик сервиса не меняет маршрут
  • Разделение рабочего и личного трафика на одном физическом канале — сегментами с разными политиками, без агентов на устройствах
  • Любое сетевое устройство с HTTP API — конфигурация становится скриптом: воспроизводимой, версионируемой и откатываемой
  • Аудит существующего периметра: что реально уходит в туннель, а что только считается ушедшим — это разные списки, и их надо сверить
Что переиспользуется на следующих проектах
  • Хелпер на stdlib для challenge-response авторизации в HTTP API устройства: авто-переавторизация по 401, батчинг правок, явное сохранение конфигурации
  • Скрипт диагностики утечек: сверка резолвенных адресов с покрытием таблицы маршрутов — находит то, что считается завёрнутым, но идёт мимо
  • Снимок конфигурации перед каждой партией правок + записанная процедура отката, исполнимая на уже сломанной сети
  • Правило разделения: собственные AS-диапазоны — маршрутами, общий облачный фронт — отдельным сегментом, а не широкими блоками
  • Watchdog, который триггерится на failed-состояние сервиса и санирует конфигурацию, а не только следит за наличием интерфейса
Похожая задача?

Если нужен доступ к конкретным ресурсам, а не весь интернет через чужой канал — это настраивается

Начинать имеет смысл с инвентаризации: какие ресурсы действительно нужны через туннель, есть ли у них собственные адресные диапазоны и что уже утекает мимо. Дальше конфигурация превращается в скрипт — с бэкапом, откатом и watchdog, а не в набор ручных настроек, которые никто не помнит.

Готовы начать?

Аудит за 5 000 ₽ — с конкретным отчётом и сметой

Расскажу что внедрить в вашем бизнесе в первую очередь, какая будет окупаемость, и нужен ли вообще AI для вашей задачи (иногда — нет).

Или просто напишите свой вопрос — отвечу в течение 2 часов