# Policy routing - rule-based маршрутизация _Сеть: L2 / L3 · LinuxLab Knowledge Base_ **TL;DR:** Policy routing - выбор routing-таблицы по src-IP, fwmark, iif, tos. ip rule + ip route table N. Multi-uplink, source-based routing, VRF, split-tunnel VPN. RPDB - Routing Policy Database. ## Зачем 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 ` | source IP попадает в prefix | | `to ` | destination IP попадает в prefix | | `iif ` | пришёл на этот интерфейс | | `oif ` | уходит через этот интерфейс | | `tos ` | DSCP/TOS-байт | | `fwmark ` | netfilter mark на пакете | | `uidrange ` | UID процесса (для local-генерируемого трафика) | | `l3mdev` | l3-master (для VRF) | | `ipproto ` | IP protocol (TCP/UDP/...) | Действия: - `lookup ` - использовать routing-таблицу N - `goto ` - перепрыгнуть к правилу - `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 с другого). ## Очерёдность обработки пакета (упрощённо) Для исходящего пакета: 1. Process socket → `OUTPUT` (mangle, nat, filter chains) 2. Routing decision: `ip rule list` → таблица → `ip route show table N` 3. `POSTROUTING` (mangle, nat) 4. Отправка на link Для forwarded пакета: 1. `PREROUTING` (mangle, nat, conntrack) 2. Routing decision (forwarding) 3. `FORWARD` chain 4. `POSTROUTING` Mangling fwmark в PREROUTING/OUTPUT работает с policy routing'ом (rule с `fwmark`), потому что mark выставляется до routing decision. ## Когда что-то пошло не так - **Правило добавлено, но не работает** - проверь priority. Меньше = выше приоритет. Если `from all lookup main` (32766) match'ится раньше твоего - перенеси своё правило с priority < 32766. - **Маршрут добавлен в таблицу, но трафик идёт мимо** - убедись правило указывает на эту таблицу: `ip rule list | grep
`. - **Ассиметричный routing → drop** - rp_filter=1. Поставь 2 (loose). - **`ip route get ` показывает не то** - используй `ip route get from mark ` чтобы симулировать твой кейс с RPDB. - **Правила пропадают после ребута** - не сохраняются автоматически. Через NetworkManager dispatcher script, systemd-networkd, либо собственный скрипт в /etc/network/if-up.d/. - **VRF + сервисы**: процесс не видит VRF, пока не запущен через `ip vrf exec ` или socket option `SO_BINDTODEVICE`. ## Полезное - **`ip route flush cache`** - сбросить routing cache (старая практика, в Linux 3.6+ кэш дезактивирован, но команда осталась) - **`ip rule list table `** - какие правила указывают на эту таблицу - **`ip route show table all`** - все маршруты во всех таблицах - **suppress_prefixlength**: правило, которое match только если routing-decision дал prefix короче N - используется в L3 VPN'ах, чтобы default не "перехватывал" специфические маршруты ## Команды ```bash ip rule list ``` Все правила RPDB в порядке приоритета - кто что lookup'ит ```bash ip route show table 100 ``` Маршруты в конкретной таблице (по номеру или имени из rt_tables) ```bash ip rule add from 192.168.10.0/24 table isp_a priority 1000 ``` Source-based: пакеты из этой подсети используют таблицу isp_a ```bash ip rule add fwmark 0x100 table 100 ``` Пакеты с fwmark 0x100 (выставленным в iptables mangle) идут через таблицу 100 ```bash ip route add default via 10.0.2.254 dev eth1 table isp_b ``` Дефолтный маршрут в кастомной таблице isp_b ```bash ip route get 8.8.8.8 from 10.0.1.99 ``` Симулировать routing decision для конкретной (src,dst) пары ```bash sysctl -w net.ipv4.conf.all.rp_filter=2 ``` Loose RPF - обязательно при policy routing с асимметричным трафиком ```bash ip vrf exec mgmt-vrf curl https://api.internal ``` Запустить процесс в контексте VRF - его трафик пойдёт через VRF-таблицу ## См. также - [Routing table](/kb/routing-table.md) - [Default gateway - выход из своей сети](/kb/default-gateway.md) - [ip - швейцарский нож сетевой настройки](/kb/cmd-ip.md) - [NAT и masquerade](/kb/nat.md) - [CNI plugins - сеть Kubernetes (calico, cilium, flannel)](/kb/cni-plugins.md) - [IP forwarding - превратить хост в роутер](/kb/ip-forwarding.md)