Policy-Based Routing (PBR) — это механизм маршрутизации в Linux, который позволяет принимать решения о пути следования пакетов не только на основе адреса назначения, но и по другим критериям: исходному IP-адресу, порту, интерфейсу или специальной метке (fwmark). Это ключевой инструмент для решения задач, где стандартной маршрутизации через один шлюз по умолчанию недостаточно. В этой статье вы получите пошаговую инструкцию по настройке PBR для принудительного направления трафика от конкретных Docker-контейнеров, системных сервисов или приложений через выделенный сетевой интерфейс, например, VPN-туннель WireGuard или OpenVPN. Мы разберем работу с цепочкой ip rule, ip route, iptables/nftables и маркировкой пакетов, а также методы проверки и обеспечения надежности конфигурации.
Зачем нужна маршрутизация на основе политик (PBR) и в чем её сила
Стандартная маршрутизация в Linux использует таблицу маршрутизации (чаще всего основную, main), где для каждого адреса назначения определен следующий хоп (шлюз). Когда у системы один интернет-канал, этого достаточно. Проблемы начинаются, когда появляется второй канал (например, VPN-туннель) и возникает потребность в гибком разделении трафика: один сервис должен общаться через VPN, а остальные — через основной канал. Именно здесь PBR перестает быть абстракцией и становится практическим инструментом для решения конкретных рабочих задач, связанных с безопасностью, compliance и построением сложных сетевых архитектур.
Типичные задачи, которые решает PBR: от безопасности до обхода блокировок
PBR позволяет реализовать сценарии, которые сложно или невозможно осуществить другими методами:
- Обеспечение compliance: Весь трафик определенного критичного сервиса (например, системы бэкапов в S3-совместимое облако) должен идти исключительно через корпоративный VPN для шифрования и аудита.
- Гео-таргетинг и обход блокировок для отдельных приложений: Контейнер с веб-скрапером или медиа-клиентом должен получать данные через VPN-сервер в определенной стране, в то время как остальной трафик сервера идет напрямую. Учитывая, что, по данным из открытых источников, до 90% бесплатных VPN-клиентов могут блокироваться провайдерами в короткие сроки, важно направлять через VPN только целевой трафик, минимизируя риски.
- Повышение безопасности: Изоляция «грязного» или подозрительного трафика (например, от honeypot-системы или sandbox-окружения) в отдельный, контролируемый туннель для анализа.
- Оптимизация качества обслуживания (QoS): Маршрутизация чувствительного к задержкам трафика (VoIP, видеоконференции по UDP) через низколатентный или более стабильный канал.
- Разделение трафика управления и данных: Трафик для администрирования сервера (SSH, мониторинг) идет через основную, защищенную сеть, а пользовательский трафик — через выделенный канал.
PBR против других методов: когда выбирать именно этот подход
Прежде чем погружаться в настройку, важно понять, почему PBR может быть оптимальнее альтернатив. Вот краткое сравнение:
- VPN Kill-Switch: Пассивная защита, которая лишь блокирует весь трафик при падении туннеля. Не дает гибкости для маршрутизации только части трафика.
- Прокси (SOCKS5/HTTP): Решение на уровне приложения. Подходит для программ с явной поддержкой прокси (браузеры, некоторые CLI-утилиты). Однако многие системные сервисы, Docker-демон или низкоуровневые TCP/UDP-соединения не работают через прокси. Хотя современный SOCKS5 и поддерживает UDP (что важно для стриминга или игр), его настройка не является прозрачной для всей системы.
- Сетевые неймспейсы (ip netns): Обеспечивают максимальную изоляцию на уровне ядра, создавая виртуальную копию стека сетевых интерфейсов, таблиц маршрутизации и правил. Мощный, но более сложный в настройке и интеграции с существующими приложениями (требует запуска процессов в конкретном неймспейсе).
- Policy-Based Routing (PBR): Работает на уровне ядра (
netfilter,iproute2), прозрачно для приложений. Позволяет гибко маршрутизировать трафик на основе множества критериев (IP, порт, интерфейс, пользователь) через отдельные таблицы маршрутизации. Оптимален, когда требуется стабильная, гибкая и прозрачная маршрутизация части трафика без изоляции всего процесса.
Вывод: Если вам нужно заставить определенный трафик (по любому критерию) всегда идти через конкретный интерфейс (VPN), и при этом все должно работать «из коробки» для любых приложений, PBR — ваш выбор.
Архитектура решения: как Linux заставляет пакеты идти нужным путём
Чтобы понимать, какие команды вы вводите, нужно представлять себе общую схему работы PBR в Linux. Это не магия, а четко определенный конвейер обработки пакетов.
Принцип работы можно описать следующей цепочкой:
- Приложение отправляет пакет (например, запрос на
api.external-service.com). - Маркировка пакета (fwmark): Правило в
iptablesилиnftables (чаще всего в цепочкеOUTPUTилиPREROUTING) «помечает» этот пакет, основываясь на заданном критерии (исходный IP контейнера, порт и т.д.). Метка — это просто число (например,100или0x64в hex), хранящееся в метаданных пакета. - Правило (
ip rule): Ядро проверяет набор правил политик маршрутизации. Одно из правил гласит: «Если у пакета есть метка0x64, ищи маршрут для него в таблице с номером100». - Таблица маршрутизации (
ip route table 100): Это отдельная, изолированная «карта дорог». В ней, скорее всего, прописан маршрут по умолчанию (default via 10.8.0.1 dev tun0), ведущий через VPN-интерфейс, а не через основной шлюз. - Сетевой интерфейс: Пакет отправляется через указанный в таблице интерфейс (
tun0,wg0и т.д.).
Ключевые концепции:
fwmark(firewall mark) — метка пакета, которую ставитiptables/nftables.ip rule— правило выбора таблицы маршрутизации на основе метки, исходного адреса или других атрибутов.ip route table <N>— отдельный набор маршрутов, существующий независимо от основной таблицы (main).
Важное замечание о DNS: Маршрутизация работает на сетевом уровне (IP). Если приложение, трафик которого направлен через VPN, будет использовать DNS-серверы из основной системы (прописанные в /etc/resolv.conf), DNS-запросы пойдут через основной канал, что приведет к утечке информации о ваших запросах. Поэтому настройка DNS для изолированного трафика — обязательный шаг.
Пошаговая настройка PBR для изоляции трафика через VPN
Перейдем к практике. Предполагается, что у вас уже настроен и работает VPN-клиент (например, WireGuard или OpenVPN), создавший интерфейс (например, wg0 или tun0). Вам известен его IP-адрес и шлюз (gateway).
Шаг 0: Проверка текущей конфигурации
Перед началом работы посмотрите на текущие маршруты и правила:
ip route show
ip rule list
Создание и настройка отдельной таблицы маршрутизации для VPN
Сначала создадим «альтернативную карту дорог» для нашего изолированного трафика. Для этого нужно дать имя новой таблице маршрутизации, отредактировав файл /etc/iproute2/rt_tables.
echo "100 custom_vpn" >> /etc/iproute2/rt_tables
Теперь добавим маршрут по умолчанию в эту таблицу, который будет указывать на шлюз VPN-интерфейса. Замените 10.8.0.1 и wg0 на актуальные для вас значения.
ip route add default via 10.8.0.1 dev wg0 table custom_vpn
Важно: Если ваш VPN-сервер находится в локальной сети и доступен через основной интерфейс (например, eth0), нужно добавить в таблицу custom_vpn явный маршрут до сервера VPN через основной шлюз, иначе установить соединение с самим сервером для изолированного трафика не получится. Это предотвратит проблему с асимметричной маршрутизацией и reverse path filtering.
# Пример: VPN-сервер имеет IP 192.168.1.254
ip route add 192.168.1.254/32 dev eth0 via 192.168.1.1 table custom_vpn
Маркировка пакетов: от простого правила к сложной логике
Теперь научим систему отличать «особый» трафик, который нужно направить в новую таблицу. Делается это с помощью маркировки пакетов в iptables (или nftables).
Сначала создадим правило, которое будет смотреть на метку 0x64 (100 в десятичной системе) и направлять пакет на поиск маршрута в таблицу custom_vpn.
ip rule add fwmark 0x64 table custom_vpn
Теперь настроим iptables для маркировки. Вот несколько примеров для разных критериев (выберите подходящий):
Пример 1: Маркировка по исходному IP-адресу (подходит, если у приложения или контейнера фиксированный IP).
iptables -t mangle -A OUTPUT -s 172.20.0.10 -j MARK --set-mark 0x64
Пример 2: Маркировка по исходной подсети (удобно для целой Docker-сети).
iptables -t mangle -A OUTPUT -s 172.20.0.0/24 -j MARK --set-mark 0x64
Пример 3: Маркировка по пользователю (UID) (если приложение запущено от определенного пользователя).
iptables -t mangle -A OUTPUT -m owner --uid-owner 1001 -j MARK --set-mark 0x64
Правила добавляются в таблицу mangle, которая предназначена для модификации пакетов. Цепочка OUTPUT перехватывает пакеты, исходящие с самого хоста.
Для nftables эквивалентное правило будет выглядеть так (для подсети):
nft add rule ip mangle OUTPUT ip saddr 172.20.0.0/24 meta mark set 0x64
Шаг 4: Настройка DNS для помеченного трафика
Чтобы DNS-запросы от изолированного трафика тоже шли через VPN, их нужно либо маркировать аналогичным образом (если они исходят от того же источника), либо явно настроить DNS-серверы для процессов, использующих изолированный маршрут. Самый надежный способ — использовать systemd-resolved и привязать DNS-серверы к конкретному интерфейсу:
resolvectl dns wg0 1.1.1.1 8.8.8.8
Если приложение, чей трафик маркируется, использует стандартный механизм разрешения имен, запросы теперь будут отправляться через интерфейс wg0.
PBR для Docker-контейнеров: изоляция трафика контейнера в VPN
Один из самых востребованных сценариев — направить трафик конкретного Docker-контейнера через VPN, не затрагивая другие контейнеры и хост-систему. Проблема в том, что трафик контейнера по умолчанию выходит через bridge-интерфейс Docker (docker0 или пользовательский bridge), и маркировка по IP хоста не сработает.
Настройка отдельной Docker-сети и привязка правил маршрутизации
Наиболее простое и эффективное решение — создать для контейнера отдельную сеть Docker с известной нам подсетью, а затем маркировать весь трафик, исходящий с этой подсети.
1. Создаем пользовательскую сеть Docker с фиксированной подсетью:
docker network create --subnet=172.20.0.0/24 --opt com.docker.network.bridge.name=br-vpn vpn_net
Опция --opt задает имя bridge-интерфейса на хосте для удобства.
2. Запускаем контейнер в этой сети, при желании зафиксировав за ним IP-адрес:
docker run -d --name my-vpn-container \
--network=vpn_net \
--ip=172.20.0.2 \
nginx:alpine
3. Теперь добавляем правило iptables для маркировки всего трафика с подсети 172.20.0.0/24. Важно: так как трафик из контейнера сначала попадает на bridge-интерфейс, а затем на хост, правило нужно применять в цепочке PREROUTING таблицы mangle (или FORWARD, если контейнер взаимодействует с внешним миром через NAT).
iptables -t mangle -A PREROUTING -s 172.20.0.0/24 -j MARK --set-mark 0x64
Это правило будет маркировать пакеты еще до того, как они попадут в цепочку OUTPUT хоста, что гарантирует их обработку нашим правилом ip rule.
Для более глубокой настройки сети Docker, включая создание пользовательских мостов и управление статическими IP, обратитесь к нашему полному руководству по пользовательским Docker-сетям.
Проверка работы и диагностика проблем
После настройки критически важно убедиться, что трафик идет по задуманному пути, и уметь находить проблемы.
Как убедиться, что трафик идет через VPN, а не основной канал
Самый простой способ — проверить внешний IP-адрес, с которого видны запросы из изолированного окружения.
Для теста с хоста можно использовать curl с указанием маркировки (требуются права root):
curl --max-time 5 --interface wg0 ifconfig.me # Должен показать IP VPN
curl --max-time 5 ifconfig.me # Должен показать основной IP
Более точный метод — использовать команду ip route get с указанием метки. Она покажет, какой маршрут и через какой интерфейс будет выбран для целевого адреса с данной меткой.
ip route get 8.8.8.8 mark 0x64
Для прямого наблюдения за трафиком в туннеле используйте tcpdump:
tcpdump -i wg0 -n -c 5
Запустите в изолированном контейнере команду (например, apt update или curl ifconfig.me) и убедитесь, что пакеты проходят через интерфейс wg0.
Тестирование на утечку DNS: обязательный этап
Утечка DNS — одна из самых частых и критичных проблем. Если DNS-запросы уходят через основной канал, вся конфиденциальность, ради которой настраивался VPN, сводится на нет.
1. Проверка из контейнера: Зайдите в контейнер, работающий через изолированную сеть, и выполните:
docker exec my-vpn-container apk add bind-tools # Установка dig для Alpine
docker exec my-vpn-container dig +short TXT whoami.ds.akahelp.net
Эта команда вернет IP-адрес DNS-резолвера, который использовался для запроса. Он должен принадлежать вашему VPN-провайдеру или указанному вами DNS-серверу (например, 1.1.1.1), но не вашему основному провайдеру.
2. Использование онлайн-сервисов: Запустите в изолированном контейнере браузер в текстовом режиме (lynx или curl) и откройте страницу типа dnsleaktest.com. Результат должен показывать серверы вашего VPN, а не локального провайдера.
Обеспечение постоянства конфигурации (Persistence)
Настройки, выполненные командами ip и iptables, сбрасываются после перезагрузки. Для production-среды это недопустимо.
- Правила и маршруты (
ip rule,ip route):
Для систем с netplan (Ubuntu 18.04+): Добавьте конфигурацию в файл/etc/netplan/99-pbr.yaml.
Для систем с/etc/network/interfaces(Debian): Создайте скрипт в/etc/network/if-up.d/, который будет применять правила при поднятии интерфейса.
Универсальный способ: Создать systemd-сервис с зависимостью от сетевого таргета и VPN-интерфейса. - Правила iptables: Используйте пакет
iptables-persistent(Debian/Ubuntu) или сохраните правила вручную:
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6 - Правила nftables: Сохраните текущий набор правил в конфигурационный файл:
nft list ruleset > /etc/nftables.conf - Docker-сети: Помните, что пользовательские сети Docker не сохраняются после перезагрузки демона Docker. Их нужно создавать заново при старте системы, например, через тот же systemd-сервис или скрипт инициализации.
Для комплексного подхода к управлению Docker в production, включая надежный деплой и мониторинг, изучите наше полное руководство по Docker в production.
Доводка безопасности: предотвращаем утечки при падении VPN
Базовая настройка PBR направляет трафик через VPN, но что произойдет, если туннель внезапно упадет? В этом случае маршрут по умолчанию в таблице custom_vpn станет недостижимым, и ядро может отправить помеченные пакеты по основному маршруту (утечка). Чтобы этого не произошло, нужно добавить второй уровень защиты — фаервол.
Добавьте правило в iptables, которое будет отбрасывать весь помеченный трафик, если он попытается выйти не через VPN-интерфейс. Это правило ставится в таблице filter, цепочке OUTPUT (или FORWARD для контейнеров).
iptables -A OUTPUT -m mark --mark 0x64 ! -o wg0 -j DROP
Это правило гласит: «Все пакеты с меткой 0x64, которые пытаются выйти через интерфейс, отличный от wg0, должны быть отброшены». Таким образом, даже если маршрут через wg0 исчезнет, трафик не «вытечет» в открытую сеть, а будет заблокирован.
Дополнительно рекомендуется использовать стабильные и современные протоколы VPN, такие как WireGuard, которые меньше подвержены нестабильности по сравнению с некоторыми реализациями OpenVPN. Это минимизирует риск самого факта падения туннеля.
Итоговая архитектура безопасности получается двухуровневой: 1) PBR направляет трафик по нужному пути, 2) Фаервол блокирует любые попытки этого трафика пойти другим путем.