Зачем policy routing
Стандартная [[routing-table|routing-таблица]] выбирает next-hop по destination-IP: longest-prefix-match. Этого мало когда:
- Два uplink-провайдера - трафик источника A в Telecom, источника B в Cogent
- Source-based routing - "из 10.0.1.0/24 - через провайдера X"
- Per-tenant routing в multitenant-системе ([[cni-plugins|CNI]], OpenStack)
- NAT split - пакет с fwmark=0x100 идёт через NAT-машину, остальные напрямую
- Транзитный трафик через VPN для одного namespace, локально для остального
- VRF-like изоляция на роутере
Решение: множественные routing-таблицы + правила выбора. Это и есть policy routing.
RPDB - Routing Policy Database
Linux держит набор rules (ip rule list), которые выполняются
последовательно по приоритету. Каждое правило говорит "если match -
применить эту routing-таблицу". Первое match'и выигрывает.
По умолчанию:
$ ip rule list
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
Три предопределённых таблицы:
- local (255) - адреса самого хоста, broadcast'ы. Заполняется ядром автоматически
- main (254) - всё что ты добавляешь через обычный
ip route add - default (253) - редко используется, для "fallback"
Можно добавлять свои таблицы. Имена/номера таблиц - в
/etc/iproute2/rt_tables:
# echo "100 isp_a" >> /etc/iproute2/rt_tables
# echo "200 isp_b" >> /etc/iproute2/rt_tables
Имена - удобство, ядро работает с номерами 0-255 (с lwt-расширением - больше).
Множественные таблицы - пример с двумя uplink
Две внешних линии: eth0 (10.0.1.1 → ISP A) и eth1 (10.0.2.1 → ISP B).
Хочешь: трафик из 192.168.10.0/24 идёт через A, из 192.168.20.0/24 -
через B.
Шаг 1: добавить таблицы:
ip route add default via 10.0.1.254 dev eth0 table isp_a
ip route add 10.0.1.0/24 dev eth0 src 10.0.1.1 table isp_a
ip route add default via 10.0.2.254 dev eth1 table isp_b
ip route add 10.0.2.0/24 dev eth1 src 10.0.2.1 table isp_b
Шаг 2: правила:
ip rule add from 192.168.10.0/24 table isp_a priority 1000
ip rule add from 192.168.20.0/24 table isp_b priority 1001
Теперь ядро при пересылке смотрит: пакет с src 192.168.10.5 → правило
на priority 1000 - использовать isp_a - дефолт через 10.0.1.254 на
eth0.
Селекторы правил
ip rule add принимает много селекторов:
| Селектор | Что matches |
|---|---|
from <prefix> | source IP попадает в prefix |
to <prefix> | destination IP попадает в prefix |
iif <name> | пришёл на этот интерфейс |
oif <name> | уходит через этот интерфейс |
tos <value> | DSCP/TOS-байт |
fwmark <mark> | netfilter mark на пакете |
uidrange <a-b> | UID процесса (для local-генерируемого трафика) |
l3mdev | l3-master (для VRF) |
ipproto <proto> | IP protocol (TCP/UDP/...) |
Действия:
lookup <table>- использовать routing-таблицу Ngoto <priority>- перепрыгнуть к правилуnop- ничего, продолжить дальшеblackhole,unreachable,prohibit- дропнуть
fwmark + policy routing - типичный паттерн
Хочешь: HTTP-трафик идёт через VPN, остальное - напрямую. Связка iptables/[[nat|netfilter]] + policy routing:
# mark HTTP-трафик
iptables -t mangle -A OUTPUT -p tcp --dport 80 -j MARK --set-mark 0x100
iptables -t mangle -A OUTPUT -p tcp --dport 443 -j MARK --set-mark 0x100
# routing для marked трафика
ip route add default dev wg0 table 100
ip rule add fwmark 0x100 table 100 priority 500
# marked HTTP/HTTPS теперь идёт в wg0 (WireGuard tunnel)
Этот паттерн используется в:
- Split-tunnel VPN (только конкретный трафик через VPN)
- Transparent proxy (mark → routing в локальный proxy)
- DDoS scrubbing (mark подозрительный трафик → отдельный path)
- Container networking ([[cni-plugins|CNI]]: mark per namespace → отдельная таблица)
reverse path filter и policy routing
При policy routing часто асимметричный routing - входящий пакет
на eth0, ответ через eth1. Стандартный rp_filter=1 (strict mode)
сравнивает source с reverse-маршрутом - не сходится - дроп.
Решение - loose mode:
sysctl -w net.ipv4.conf.all.rp_filter=2
sysctl -w net.ipv4.conf.eth0.rp_filter=2
sysctl -w net.ipv4.conf.eth1.rp_filter=2
В loose mode принимается любой src если он reachable through any iface. Иначе ассиметричное routing просто не работает.
VRF - Virtual Routing and Forwarding
Linux 4.3+ имеет VRF-lite - виртуальные routing-instance'ы. Каждый VRF - свой набор интерфейсов и своя таблица routing'а, изолированы друг от друга.
# создать VRF "tenant-a" с table 100
ip link add vrf-a type vrf table 100
ip link set vrf-a up
# добавить интерфейс в VRF
ip link set eth1 master vrf-a
# маршруты добавляются в table 100
ip route add default via 10.0.1.1 dev eth1 table 100
Процесс может быть привязан к VRF (ip vrf exec tenant-a curl ...).
Используется в:
- Multitenant routing на одном Linux-роутере
- Management plane separation (управление через mgmt-vrf)
- Cumulus Linux, SONiC, FRR-based маршрутизаторы
Источник пакета: src-routing для исходящего трафика
Если у хоста несколько IP, ядро выбирает src согласно routing'у. Чтобы заставить использовать конкретный src:
ip route add 8.8.8.8 via 10.0.1.254 src 10.0.1.99
Или явно при отправке:
curl --interface 10.0.1.99 https://example.com
ping -I 10.0.1.99 8.8.8.8
Это важно для multi-IP хостов где разные сервисы binding на разные адреса (mail с одного, web с другого).
Очерёдность обработки пакета (упрощённо)
Для исходящего пакета:
- Process socket →
OUTPUT(mangle, nat, filter chains) - Routing decision:
ip rule list→ таблица →ip route show table N POSTROUTING(mangle, nat)- Отправка на link
Для forwarded пакета:
PREROUTING(mangle, nat, conntrack)- Routing decision (forwarding)
FORWARDchainPOSTROUTING
Mangling fwmark в PREROUTING/OUTPUT работает с policy routing'ом
(rule с fwmark), потому что mark выставляется до routing decision.
Когда что-то пошло не так
- Правило добавлено, но не работает - проверь priority. Меньше = выше
приоритет. Если
from all lookup main(32766) match'ится раньше твоего - перенеси своё правило с priority < 32766. - Маршрут добавлен в таблицу, но трафик идёт мимо - убедись правило
указывает на эту таблицу:
ip rule list | grep <table>. - Ассиметричный routing → drop - rp_filter=1. Поставь 2 (loose).
ip route get <dst>показывает не то - используйip route get <dst> from <src> mark <mark>чтобы симулировать твой кейс с RPDB.- Правила пропадают после ребута - не сохраняются автоматически. Через NetworkManager dispatcher script, systemd-networkd, либо собственный скрипт в /etc/network/if-up.d/.
- VRF + сервисы: процесс не видит VRF, пока не запущен через
ip vrf exec <name>или socket optionSO_BINDTODEVICE.
Полезное
ip route flush cache- сбросить routing cache (старая практика, в Linux 3.6+ кэш дезактивирован, но команда осталась)ip rule list table <name>- какие правила указывают на эту таблицуip route show table all- все маршруты во всех таблицах- suppress_prefixlength: правило, которое match только если routing-decision дал prefix короче N - используется в L3 VPN'ах, чтобы default не "перехватывал" специфические маршруты