Source-based маршрутизация (PBR) на Linux: настройка ip rule и iptables | AdminWiki

Source-based маршрутизация (PBR) на Linux: настройка ip rule и iptables

22 июля 2026 8 мин. чтения
Содержание статьи

Что такое Policy-Based Routing и зачем он нужен

Стандартная маршрутизация в Linux принимает решение о том, куда отправить пакет, глядя исключительно на IP-адрес назначения. Ядро просматривает таблицу маршрутизации, находит наиболее подходящий маршрут и отправляет пакет через соответствующий интерфейс. Этот подход работает безупречно в простых сетях с одним шлюзом по умолчанию.

Policy-Based Routing (PBR), или маршрутизация на основе политик, ломает это ограничение. PBR позволяет задавать правила, которые направляют трафик в зависимости от адреса источника, порта, протокола или метки пакета. Вы получаете инструмент, который говорит: «весь трафик от приложения A отправляй через VPN, а от приложения B - через основного провайдера».

Типичные сценарии, где без PBR не обойтись:

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

Инструменты, которые реализуют PBR в Linux - это утилита ip rule для создания правил выбора таблицы маршрутизации и iptables с модулем mangle для маркировки пакетов меткой fwmark.

Основы работы с ip rule и таблицами маршрутизации

Ядро Linux хранит маршруты не в одной таблице, а в нескольких. По умолчанию существуют таблицы local (ID 255), main (ID 254) и default (ID 253). При поиске маршрута ядро последовательно проверяет правила в базе правил (Routing Policy Database, RPDB) и использует ту таблицу, на которую укажет первое совпавшее правило. Приоритет правила задаётся числом: чем оно меньше, тем выше приоритет.

Просмотр и управление правилами маршрутизации

Текущий список правил выводится командой:

ip rule show

Вывод выглядит так:

0:      from all lookup local
32766:  from all lookup main
32767:  from all lookup default

Правило с приоритетом 0 направляет любой трафик в таблицу local, где хранятся маршруты к локальным интерфейсам и broadcast-адреса. Правило 32766 отправляет всё остальное в main - основную таблицу, которую вы редактируете командами ip route add без указания таблицы. Правило 32767 - это fallback на таблицу default, которая обычно пуста.

Добавление нового правила:

ip rule add from 192.168.10.0/24 table 100 priority 1000

Эта команда предписывает: трафик, пришедший от подсети 192.168.10.0/24, маршрутизировать по таблице с ID 100. Удаление правила:

ip rule del priority 1000

Ключевые селекторы для ip rule:

  • from - адрес источника.
  • to - адрес назначения.
  • fwmark - метка пакета, установленная файрволом.
  • iif - входящий интерфейс.
  • oif - исходящий интерфейс.

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

Прежде чем ссылаться на таблицу в правилах, её стоит именовать. Файл /etc/iproute2/rt_tables связывает числовые идентификаторы с именами. Добавьте строку:

100     vpn_table

Теперь таблицу можно называть и по ID, и по имени. Наполнение таблицы маршрутами:

ip route add default via 10.8.0.1 dev tun0 table vpn_table
ip route add 192.168.1.0/24 via 192.168.1.1 dev eth0 table vpn_table

Проверка содержимого таблицы:

ip route show table vpn_table

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

Маркировка пакетов с помощью iptables и fwmark

Селектор from в ip rule удобен, когда трафик идентифицируется по IP-адресу источника. Но если нужно разделить трафик от разных приложений, работающих на одном хосте и с одним IP, на помощь приходит маркировка пакетов. Связка работает так: iptables ставит на пакет числовую метку в поле fwmark, а ip rule использует эту метку для выбора таблицы.

Метки устанавливаются в таблице mangle. Для локально сгенерированного трафика (приложения на самом сервере) используется цепочка OUTPUT. Для транзитного трафика (сервер работает как маршрутизатор) - цепочка PREROUTING.

Маркировка трафика конкретного приложения по UID/GID

Модуль owner в iptables позволяет фильтровать пакеты по идентификатору пользователя или группы, создавшего пакет. Это самый точный способ привязать маршрут к процессу. Запустите приложение под выделенным пользователем и направьте его трафик в VPN:

iptables -t mangle -A OUTPUT -m owner --uid-owner 1001 -j MARK --set-mark 1

Ограничение: модуль owner работает только в цепочках OUTPUT и POSTROUTING таблицы mangle. Транзитный трафик маркировать по UID нельзя - ядру неизвестно, какой процесс на удалённой машине его породил.

Сохранение правил iptables и автоматическая загрузка

Правила iptables живут до перезагрузки. Для сохранения используйте iptables-save:

iptables-save > /etc/iptables/rules.v4

Восстановление после перезагрузки обеспечивает пакет iptables-persistent (Debian/Ubuntu) или сервис netfilter-persistent. Установите пакет и при сохранении правил через iptables-save они автоматически подхватятся при старте системы.

Практический сценарий: разделение трафика между VPN и основным шлюзом

Задача: трафик приложения, запущенного от пользователя vpnuser (UID 1001), должен уходить через VPN-туннель tun0 с адресом шлюза 10.8.0.1. Весь остальной трафик сервера идёт через основной шлюз 192.168.1.1 на интерфейсе eth0.

Настройка маршрутов и правил

Именуем таблицу в /etc/iproute2/rt_tables:

echo "200 vpn" >> /etc/iproute2/rt_tables

Добавляем маршрут по умолчанию в таблицу VPN:

ip route add default via 10.8.0.1 dev tun0 table vpn

Создаём правило, которое направляет пакеты с меткой 1 в таблицу vpn:

ip rule add fwmark 1 table vpn priority 100

Почему fwmark, а не from? Потому что IP-адрес источника у всех приложений на сервере один и тот же. Метка - единственный способ различить трафик разных процессов.

Маркировка трафика приложения в iptables

Правило в таблице mangle, цепочка OUTPUT:

iptables -t mangle -A OUTPUT -m owner --uid-owner 1001 -j MARK --set-mark 1

Цепочка OUTPUT обрабатывает пакеты, созданные локальными процессами. Цепочка PREROUTING здесь не сработает - она видит только входящий трафик, пришедший на сетевой интерфейс.

Проверка и диагностика

Утилита ip route get показывает, какой маршрут будет выбран для пакета с заданными параметрами. Проверьте маршрут для пакета с меткой 1:

ip route get 8.8.8.8 mark 1

Вывод должен содержать dev tun0 и шлюз 10.8.0.1. Сравните с маршрутом без метки:

ip route get 8.8.8.8

Этот пакет пойдёт через eth0 и шлюз 192.168.1.1.

Запустите приложение от нужного пользователя и проверьте трафик утилитой tcpdump на обоих интерфейсах. Счётчики правил iptables покажут, сколько пакетов было помечено:

iptables -t mangle -L OUTPUT -v

Перед настройкой PBR с несколькими внешними интерфейсами отключите reverse path filtering. Эта защита отбрасывает пакеты, пришедшие на интерфейс, если маршрут к источнику пакета не проходит через этот же интерфейс. При асимметричной маршрутизации, характерной для PBR, это гарантированно вызовет потерю трафика. Подробный разбор проблемы и методы её решения - в статье по асимметричной маршрутизации.

Дополнительные сценарии использования PBR

Разобранный кейс с VPN - самый востребованный. Но возможности PBR шире. Ниже - ещё два сценария, которые регулярно встречаются в продакшн-средах.

Маршрутизация по порту назначения

Задача: HTTP-трафик (порт 80) отправлять через одного провайдера, а весь остальной трафик - через другого. Решение - маркировка по порту назначения в iptables:

iptables -t mangle -A OUTPUT -p tcp --dport 80 -j MARK --set-mark 2

Далее создаётся правило ip rule add fwmark 2 table provider_http и соответствующая таблица с маршрутом через нужного провайдера. Этот подход удобен, когда разные сервисы должны выходить в интернет с разных IP-адресов.

Отказоустойчивость и балансировка с несколькими шлюзами

Linux поддерживает multipath-маршруты - несколько nexthop для одного маршрута с весами. Балансировка по двум каналам с резервированием:

ip route add default scope global nexthop via 192.168.1.1 dev eth0 weight 1 nexthop via 192.168.2.1 dev eth1 weight 2

Трафик будет распределяться между eth0 и eth1 в пропорции 1:2. При падении одного из каналов весь трафик уйдёт через оставшийся. Для отслеживания состояния каналов используйте демоны вроде ifplugd или скрипты, проверяющие доступность шлюза и удаляющие мёртвый nexthop из таблицы. Более детально тема раскрыта в руководстве по таблицам маршрутизации и ip rule.

Типичные проблемы и их решение

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

Reverse Path Filtering и асимметричная маршрутизация

Reverse path filtering (rp_filter) - это механизм ядра, который проверяет, что ответный пакет уйдёт через тот же интерфейс, на который пришёл запрос. Если PBR направляет исходящий трафик через интерфейс, отличный от того, на который пришёл входящий пакет, rp_filter отбрасывает входящий пакет. Соединение не устанавливается.

Решение - отключить rp_filter для интерфейсов, участвующих в PBR:

sysctl -w net.ipv4.conf.all.rp_filter=0
sysctl -w net.ipv4.conf.tun0.rp_filter=0
sysctl -w net.ipv4.conf.eth0.rp_filter=0

Для сохранения после перезагрузки пропишите эти параметры в /etc/sysctl.conf или в отдельный файл в /etc/sysctl.d/.

Влияние conntrack на маршрутизацию

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

Сбросить отслеживание для конкретного соединения можно удалением записи из /proc/net/nf_conntrack или полным сбросом таблицы:

conntrack -F

На продакшн-сервере сброс всей таблицы приведёт к разрыву всех активных соединений. Используйте точечное удаление по критериям или дождитесь естественного истечения таймаутов.

Локально сгенерированный трафик не попадает в PREROUTING

Пакеты, созданные процессами на самом сервере, проходят через цепочки OUTPUT и POSTROUTING. Цепочка PREROUTING применяется только к пакетам, пришедшим на сетевой интерфейс. Если вы настроили маркировку в PREROUTING для локального приложения, она не сработает. Используйте OUTPUT.

Порядок правил в ip rule и iptables

Правила в RPDB проверяются по возрастанию приоритета. Первое совпадение применяется, остальные игнорируются. Размещайте специфичные правила с меньшими числовыми значениями приоритета, чем общие. Правила iptables в цепочке обрабатываются последовательно. Действие MARK не прерывает обход цепочки, поэтому пакет может быть помечен несколько раз. Используйте отдельные метки для разных правил или добавляйте завершающее правило с ACCEPT после установки метки.

Поделиться:
Сохранить гайд? В закладки браузера