Policy routing (маршрутизация по политике) в Linux — это когда маршрут выбирается не только по адресу назначения, но и по дополнительным условиям: исходному адресу (source routing), метке fwmark, входящему интерфейсу, UID, DSCP и т.д. На практике это решает задачи «несколько шлюзов (multiple gateways)», раздельный трафик для разных IP, VPN/туннелей, multi-homing и управляемый failover.
Ниже — практические конфиги для systemd-networkd: как завести свои таблицы маршрутизации, добавить правила (аналог ip rule) и описать маршруты в этих таблицах. В конце — короткий блок про Netplan на Ubuntu и чек-лист отладки.
Короткая модель: как работает policy routing в Linux
В упрощённом виде ядро выбирает маршрут в два шага:
- Подбирает правило из
ip rule(routing policy): если пакет подходит под условие — смотреть маршруты в таблице N. - В выбранной таблице ищет подходящий
ip route(default via … или более специфичный префикс).
Типовая схема для двух аплинков:
- для каждого провайдера/линка — отдельная таблица (например, 100 и 200);
- в каждой таблице — свой default route через свой шлюз;
- правила отправляют трафик в нужную таблицу по признаку (чаще всего по исходному IP).
Policy routing не «склеивает» два шлюза в один. Это механизм выбора таблицы. Балансировка и «умный» failover требуют отдельной логики: метрик, multipath, мониторинга доступности или динамической маршрутизации.
Что подготовить перед настройкой
1) Убедитесь, что сеть обслуживает именно systemd-networkd
На Debian/Ubuntu чаще встречаются три варианта:
- чистый
systemd-networkd— работаем напрямую с/etc/systemd/network/*.network; - Netplan на Ubuntu — он генерирует конфиги либо для networkd, либо для NetworkManager; здесь нужен рендерер networkd;
- NetworkManager — тогда примеры ниже не применяются «как есть».
Быстрая проверка:
systemctl status systemd-networkd
networkctl status
2) Заранее спланируйте ID/имена таблиц
Удобно завести имена таблиц в /etc/iproute2/rt_tables. Это не строго обязательно (можно использовать числовой ID), но отладку упрощает заметно.
sudoedit /etc/iproute2/rt_tables
100 isp1
200 isp2
После этого в networkd можно писать Table=isp1 или Table=100 — выбирайте единый стиль и придерживайтесь его везде.

Базовый кейс: source routing для двух аплинков (2 шлюза)
Сценарий: у сервера два интерфейса/две подсети с разными шлюзами. Нужно, чтобы исходящий трафик «с IP провайдера 1» уходил через ISP1, а «с IP провайдера 2» — через ISP2. Это классический source routing: правило выбирается по From=.
Пример: два интерфейса (ens3 и ens4)
Допустим:
ens3:203.0.113.10/24, шлюз203.0.113.1(ISP1)ens4:198.51.100.10/24, шлюз198.51.100.1(ISP2)
Создадим два файла.
/etc/systemd/network/10-ens3.network (ISP1)
[Match]
Name=ens3
[Network]
Address=203.0.113.10/24
[Route]
Destination=203.0.113.0/24
Scope=link
Table=isp1
[Route]
Gateway=203.0.113.1
Table=isp1
[RoutingPolicyRule]
From=203.0.113.10/32
Table=isp1
Priority=1000
/etc/systemd/network/10-ens4.network (ISP2)
[Match]
Name=ens4
[Network]
Address=198.51.100.10/24
[Route]
Destination=198.51.100.0/24
Scope=link
Table=isp2
[Route]
Gateway=198.51.100.1
Table=isp2
[RoutingPolicyRule]
From=198.51.100.10/32
Table=isp2
Priority=1001
Применяем:
sudo systemctl restart systemd-networkd
Проверка: правила и маршруты по таблицам
ip rule show
ip route show table isp1
ip route show table isp2
Самый полезный тест — что именно выбрало ядро:
ip route get 1.1.1.1 from 203.0.113.10
ip route get 1.1.1.1 from 198.51.100.10
Ожидаемо: для каждого from будет свой шлюз и свой интерфейс.
Зачем добавлять link-route до своей подсети в «свою» таблицу
Когда вы включаете policy routing, важно, чтобы в выбранной таблице были не только default routes, но и «локальные» маршруты (connected routes) — минимум до подсети, где находится ваш шлюз. Иначе можно получить ситуацию: default в таблице есть, но до самого next-hop (шлюза) в этой таблице маршрута нет, и пакет не уйдёт корректно.
Практическое правило: каждая «провайдерская» таблица должна быть самодостаточной: маршрут до своего L2/L3-сегмента (
Scope=link) плюс default через свой gateway.
Один интерфейс, два IP и два шлюза (multiple gateways на одной карте)
Иногда оба адреса висят на одном интерфейсе (один порт, два публичных IP, два шлюза). Подход тот же: две таблицы, два default, два правила. Отличие — один Name= и два Address=.
[Match]
Name=ens3
[Network]
Address=203.0.113.10/24
Address=198.51.100.10/24
[Route]
Destination=203.0.113.0/24
Scope=link
Table=isp1
[Route]
Gateway=203.0.113.1
Table=isp1
[RoutingPolicyRule]
From=203.0.113.10/32
Table=isp1
Priority=1000
[Route]
Destination=198.51.100.0/24
Scope=link
Table=isp2
[Route]
Gateway=198.51.100.1
Table=isp2
[RoutingPolicyRule]
From=198.51.100.10/32
Table=isp2
Priority=1001
С точки зрения ядра это два независимых набора правил/таблиц; физически один интерфейс или два — принципиально не важно.
Как сделать «основной интернет» и не сломать сервер
Если вы описали policy routing, но не задали «главный» default route в таблице main, часть трафика без явного исходного адреса (системные службы, установщик пакетов, часть исходящих соединений) может начать вести себя непредсказуемо.
Рабочий подход для серверов:
- оставить один default в
main(основной провайдер), - а policy routing применять только для трафика с конкретных source-IP (правила
From=).
В networkd default в main задаётся маршрутом без Table=:
[Route]
Gateway=203.0.113.1
Metric=100
Failover gateway: что реально сделать статикой, а где нужен мониторинг
Важно различать два типа «переключения»:
- переключение при падении интерфейса (link down) — относительно просто, маршруты исчезают сами;
- переключение при частичных отказах (линк up, но «интернета нет» за шлюзом) — требует мониторинга доступности и реакции.
Простой failover: два default в main с разными Metric
Если достаточно «когда ISP1-интерфейс реально упал — уйти через ISP2», можно держать два default в main и разнести их метриками:
[Route]
Gateway=203.0.113.1
Metric=100
[Route]
Gateway=198.51.100.1
Metric=200
Но это не гарантирует переключение, если линк поднят, а апстрим сломан. Если нужен именно такой сценарий, обычно добавляют внешний мониторинг и автоматизацию, которая правит маршруты/правила через ip.
Практика: мониторинг + правка маршрутов
В связке с policy routing часто получается так: статикой (networkd) держим «правильную» структуру таблиц, а «умное» переключение делаем отдельным systemd unit + таймером. Он периодически проверяет доступность контрольного адреса и при необходимости меняет default/приоритеты.
Если вы ещё на стадии проектирования multi-homing, полезно заранее подумать про DNS-сторону и отказоустойчивость на уровне приложения. По теме может пригодиться статья про weighted routing и failover в DNS (когда часть логики переносится на уровень записи/пула адресов).

Netplan на Ubuntu: как это соотносится с networkd
Netplan — генератор конфигов. Если вы хотите именно systemd-networkd, убедитесь, что в Netplan указан renderer networkd. Дальше два варианта:
- описывать policy routing средствами Netplan (в современных версиях есть routing-policy),
- или завести конфиги напрямую в
/etc/systemd/network/и не усложнять YAML.
На практике всегда проверяйте «конечную истину»: networkctl status, ip rule и ip route show table .... Не столь важно, пришло это из Netplan или из ваших .network-файлов.
Отладка: быстрый чек-лист, если «не пингуется»
1) Смотрите фактические правила и маршруты
ip rule
ip route
ip route show table isp1
ip route show table isp2
2) Проверяйте выбор маршрута командой ip route get
ip route get 8.8.8.8 from 203.0.113.10
ip route get 8.8.8.8 from 198.51.100.10
3) Типовые ошибки
- В таблице есть default, но нет маршрута до подсети шлюза: добавьте
Destination=... Scope=linkв эту таблицу. - Асимметричная маршрутизация: вход пришёл через ISP1, а ответ ушёл через ISP2 — внешние фаерволы/антиспуфинг это часто режут. Лечится source routing по правильному IP и корректной привязкой сервисов.
- Сервис слушает на одном IP, а исходящий трафик идёт с другого: некоторым демонам нужно явно задавать bind/source address (зависит от приложения).
- Reverse path filtering (
rp_filter) ломает multi-homing: в сложных схемах часто используют loose-режимrp_filter=2на нужных интерфейсах.
Если у вас несколько публичных IP и дополнительно есть NAT/SNAT (например, часть сервисов должна выходить строго с конкретного адреса), посмотрите материал про управление SNAT для нескольких IP через nftables: это частая «вторая половина» задачи после корректных таблиц маршрутизации.
Мини-шпаргалка: соответствие networkd ↔ iproute2
[Route] Table=...→ip route add ... table ...[RoutingPolicyRule] From=... Table=... Priority=...→ip rule add from ... table ... priority ...Metric=→ метрика маршрута (аналогичноip route ... metric N)
Итог
systemd-networkd позволяет декларативно описывать policy routing: вы раскладываете маршруты по таблицам и управляете выбором таблицы через правила, аналогичные ip rule. Это надёжно работает на Debian/Ubuntu и хорошо сочетается с Netplan при renderer networkd.
Если вы подбираете площадку под проекты, где такие сетевые схемы встречаются регулярно (несколько аплинков, несколько публичных IP, VPN/туннели), удобнее держать всё на полноценном VDS: проще дебажить сеть, управлять sysctl и не упираться в ограничения окружения.


