Перейти к содержимому

Разрешить сайту считать ваши посещения с помощью Яндекс Метрики? Для этого она сохранит в браузере файлы cookie. Как сайт использует cookie

VC
Кейс 19 из 25 · Сети · Инфраструктура

Избирательная маршрутизация через свой сервер: нужное идёт в туннель, остальное напрямую

Эту систему я сделал для своей инфраструктуры. В домашней и рабочей сети часть внешних сервисов должна работать через мой арендованный сервер (VPS). Весь остальной интернет идёт напрямую, на полной скорости. Встроенная функция роутера с этим не справилась. Поэтому маршрутизацию я настроил сам и полностью автоматизировал через HTTP API роутера.

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

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

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

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

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

02 · Решение

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

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

01
Разбор сервиса

Есть ли у сервиса свои диапазоны адресов или он работает на общей облачной площадке. От этого зависит способ

02
Маршруты

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

03
DNS

Точечные привязки: адреса только этих доменов запрашиваются у публичного DNS-сервера через туннель

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

HTTP API роутера, вход по схеме «запрос — ответ», правки пачками, сохранение настроек

05
Контроль

Поиск утечек, снимок настроек перед каждым изменением, сторожевой процесс на сервере

Статические маршруты — для сервисов со своими диапазонами адресов

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

Общую облачную площадку маршрутами не закрыть

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

DNS-привязки — по одному домену

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

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

Мобильные приложения и игры подключаются по адресу напрямую

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

Настройка через HTTP API, потому что SSH ненадёжен

SSH на роутере регулярно недоступен: сессии зависают, порт перестаёт отвечать как раз тогда, когда что-то нужно срочно поправить. Поэтому всю настройку я перевёл на HTTP API роутера, а вспомогательный скрипт написал на стандартной библиотеке Python:

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

Поиск утечек: адрес найден, а трафик идёт мимо туннеля

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

Снимок настроек перед изменением и готовый откат

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

03 · Технологии

Инструменты обычные, главное — где и как их применить

WireGuard

Туннель до моего сервера; UDP-трафик приложений проходит без проблем

Свой сервер

Единственная точка выхода туннеля: служба systemd и сторожевой процесс по расписанию

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

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

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

Адреса выбранных доменов запрашиваются через туннель

HTTP API роутера

Вход по схеме «запрос — ответ», короткая сессия, автоматический повторный вход. Ненадёжный SSH для настройки больше не нужен

Python (stdlib)

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

Сегменты сети

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

Снимки настроек

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

WireGuardPython stdlibHTTP API роутераСтатические маршрутыDNS-привязкиAS-диапазоныsystemdСторожевой процесс
04 · Результат

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

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

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

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

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

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

сторожевой процесс сам чинит испорченные настройки, заходить на сервер вручную не нужно

Режим выбирается подключением к сети

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

Сбой, из-за которого я переписал своё же правило

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

Смена сервера означает перенос всех правил

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

Сторожевой процесс доучили на реальном сбое

Однажды после перезагрузки VPS туннель не поднялся: в настройках остался пустой блок, и служба падала при каждом запуске. Сторожевой процесс этого не заметил. Он реагировал только на пропажу интерфейса и на то, что туннель давно не обменивался ключами (handshake). Теперь он ещё и чистит настройки перед перезапуском и срабатывает, когда служба переходит в состояние сбоя. Такие сбои система исправляет сама.

Итог: список того, что идёт через туннель, записан явно. Его можно проверить, а каждая версия сохраняется.

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

Где ещё работает тот же подход

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

  • → Офис или филиал, которому нужны конкретные внешние сервисы через выделенный канал, а всю сеть сажать на один арендованный сервер нельзя
  • → Внешние API, которые принимают запросы только с определённого адреса: через выделенный адрес идут только обращения к ним, остальной трафик идёт как раньше
  • → Разделение рабочего и личного трафика на одном канале: разные сегменты с разными правилами, без программ на самих устройствах
  • → Любое сетевое устройство с HTTP API: настройки превращаются в скрипт, который можно повторить, сохранить по версиям и откатить
  • → Проверка готовой настройки: сверяю, какой трафик на деле уходит в туннель, со списком того, что туда должно уходить
Что переиспользуется на следующих проектах
  • Модуль на стандартной библиотеке Python для входа «запрос — ответ» в HTTP API устройства: повторный вход по 401, правки пачками, явное сохранение настроек
  • Скрипт поиска утечек: сверяет адреса из DNS с таблицей маршрутов и находит трафик, который должен идти в туннель, но идёт мимо
  • Снимок настроек перед каждой партией правок и записанный порядок отката, который можно выполнить на уже сломанной сети
  • Правило выбора: у сервиса свои диапазоны — маршруты, общая облачная площадка — отдельный сегмент
  • Сторожевой процесс, который реагирует на сбой службы и чистит настройки
Похожая задача?

Нужно пустить через отдельный канал только часть трафика? Это настраивается

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