На Linux-серверах с двумя и более интерфейсами, несколькими IP-адресами, разными шлюзами или отдельной сетью для бэкапов рано или поздно всплывает одна и та же история: часть трафика ходит, часть внезапно пропадает, в dmesg появляются сообщения про martian source, а после очередного reboot ситуация повторяется. Особенно часто это встречается на Debian/Ubuntu, когда администратор настраивает policy routing через ip rule и ip route, но забывает про rp_filter.
Reverse path filtering — это механизм ядра Linux для проверки, что обратный маршрут к адресу источника пакета выглядит корректно с точки зрения таблицы маршрутизации. В простых конфигурациях он помогает отсекать спуфинг и явно неверный трафик. Но как только у сервера появляется асимметрия маршрутов, несколько uplink'ов, несколько source IP или VRF-подобная логика через policy routing, слишком строгая проверка начинает ломать вполне легитимные пакеты.
Проблема в том, что многие смотрят на rp_filter как на магический переключатель: 0 — плохо, 1 — безопасно, 2 — непонятно. На практике всё зависит от вашей схемы маршрутизации. Если у вас один интерфейс и один default route, можно годами не вспоминать про этот параметр. Если же входящий пакет приходит по одному интерфейсу, а лучший обратный маршрут по main-таблице указывает на другой, строгий rp_filter такой пакет отбросит.
Ниже разберём, как это работает в Debian/Ubuntu, где искать симптомы, как правильно проверять lookup ядра и как сочетать rp_filter с policy routing так, чтобы сеть после перезагрузки вела себя предсказуемо.
Что такое rp_filter и почему он мешает именно на multi-homed сервере
rp_filter — это проверка обратного пути. Когда на интерфейс приходит пакет, ядро пытается понять: если бы я сейчас отправлял ответ к IP-адресу источника, через какой интерфейс я бы его послал?
В строгом режиме ядро ожидает, что обратный маршрут укажет именно на тот интерфейс, на который пакет пришёл. Если пакет пришёл на eth1, а lookup показывает, что назад к источнику лучше идти через eth0, пакет может быть отброшен ещё до того, как его увидит приложение.
Это логично для защиты от подмены адресов, но конфликтует с асимметричной маршрутизацией. Асимметрия сама по себе не всегда ошибка. Например, сервер может принимать трафик по одному адресу на eth1, а основной исходящий маршрут держать через eth0. Или трафик должен уходить через разного провайдера в зависимости от source IP. В таких схемах без policy routing одно только наличие нескольких интерфейсов уже создаёт условия, при которых strict reverse path filtering начинает ломать нормальный трафик.
Значения параметра rp_filter
0— проверка отключена;1— strict mode, строгая проверка обратного пути;2— loose mode, мягкая проверка.
В loose mode ядро проверяет не то, что лучший обратный путь проходит через тот же интерфейс, а лишь то, что источник в принципе достижим через таблицы маршрутизации. Для реальных серверов это часто компромиссный вариант: базовая защита остаётся, но допустимая асимметрия не ломается на ровном месте.
Короткое практическое правило:
rp_filter=1хорошо подходит для простых одноинтерфейсных хостов, а на серверах с несколькими uplink чаще нужен режим2или точечное отключение на конкретном интерфейсе.
Где смотреть текущие значения и как понять, что виноват именно rp_filter
На Debian и Ubuntu параметры доступны через sysctl и /proc/sys. Проверять нужно не только глобальные значения, но и настройки конкретных интерфейсов. Частая ошибка — изменить только net.ipv4.conf.all.rp_filter и считать, что этого достаточно.
sysctl net.ipv4.conf.all.rp_filter
sysctl net.ipv4.conf.default.rp_filter
sysctl net.ipv4.conf.eth0.rp_filter
sysctl net.ipv4.conf.eth1.rp_filterТо же самое через /proc:
cat /proc/sys/net/ipv4/conf/all/rp_filter
cat /proc/sys/net/ipv4/conf/default/rp_filter
cat /proc/sys/net/ipv4/conf/eth0/rp_filter
cat /proc/sys/net/ipv4/conf/eth1/rp_filterЕсли сеть ведёт себя странно, сразу смотрите и маршруты, и правила:
ip addr
ip route show table main
ip rule show
ip route get 203.0.113.10
ip route get 203.0.113.10 from 198.51.100.20 iif eth1Последняя команда особенно полезна: она моделирует, как ядро воспринимает путь для конкретного исходного адреса и входного интерфейса. Если расчётный обратный путь не совпадает с вашей реальной схемой, причина уже почти найдена.
Ещё один важный источник информации — журнал ядра. Сообщения про martian source как раз намекают, что пакет пришёл с точки зрения текущей маршрутизации «не оттуда».
dmesg | grep -i martian
journalctl -k | grep -i martianТипичный сценарий: входящий пакет пришёл на eth1 с адреса, для которого main-таблица считает лучшим обратным маршрутом eth0. При активном strict mode пакет отбрасывается, а в логах появляется характерная запись.

Почему одной main-таблицы недостаточно
По умолчанию Linux использует основную таблицу маршрутизации — main. Для простого сервера этого достаточно: один default gateway, локальные connected route, всё предсказуемо. Но как только сервер должен отправлять ответы через разные провайдеры в зависимости от source IP или интерфейса, main перестаёт отражать реальную сетевую логику.
Например, у сервера есть два адреса и два шлюза:
eth0—198.51.100.10/24, шлюз198.51.100.1;eth1—203.0.113.10/24, шлюз203.0.113.1.
Если в main оставить только один default route, то ответы с адреса 203.0.113.10 могут уходить через eth0. Удалённая сторона может отбросить такие пакеты из-за anti-spoofing у провайдера, а локальный strict rp_filter будет считать входящие пакеты на eth1 неправильными. В итоге ломается и вход, и выход.
Здесь и нужна policy routing: отдельные таблицы и правила, которые говорят ядру, какую таблицу использовать для определённого source IP, интерфейса или другого признака. Если у вас такой сервер развёрнут на VDS, эти правила особенно важно сохранить в постоянной конфигурации, чтобы не ловить плавающие инциденты после рестарта сети или узла.
Базовая схема policy routing через ip rule и ip route
Самый распространённый случай — source-based routing. Мы хотим, чтобы трафик, исходящий с конкретного адреса, всегда уходил через соответствующий шлюз. Тогда обратный путь становится симметричным, а поведение приложений — предсказуемым.
Сначала добавим имена таблиц:
echo '100 uplink1' >> /etc/iproute2/rt_tables
echo '200 uplink2' >> /etc/iproute2/rt_tablesТеперь наполним таблицы маршрутами:
ip route add 198.51.100.0/24 dev eth0 src 198.51.100.10 table uplink1
ip route add default via 198.51.100.1 dev eth0 table uplink1
ip route add 203.0.113.0/24 dev eth1 src 203.0.113.10 table uplink2
ip route add default via 203.0.113.1 dev eth1 table uplink2Дальше — правила:
ip rule add from 198.51.100.10/32 table uplink1 priority 1000
ip rule add from 203.0.113.10/32 table uplink2 priority 1001И проверка:
ip rule show
ip route show table uplink1
ip route show table uplink2
ip route get 1.1.1.1 from 198.51.100.10
ip route get 1.1.1.1 from 203.0.113.10Теперь ядро знает, что ответ с каждого адреса должен идти через свой uplink. Это не обходной манёвр, а явное описание сетевой политики. После такой настройки вероятность конфликтов с rp_filter заметно снижается.
Что делать с main-таблицей
main всё равно остаётся важной. Обычно в ней оставляют connected routes и один основной default route либо минимальный набор маршрутов, если логика вынесена в policy routing. Важно, чтобы это не создавало неожиданных путей для служебного трафика самого хоста.
Если у вас параллельно решается задача исходящего NAT для нескольких адресов, полезно отдельно посмотреть материал про управление SNAT для нескольких IP через nftables: он хорошо дополняет схему с source-based routing.
Когда достаточно loose mode, а когда лучше отключать rp_filter точечно
На практике есть три рабочих подхода, и выбор зависит не от «правильного мнения», а от вашей топологии.
Оставить strict mode
Подходит, если сервер реально одноhomed, асимметрии нет, а policy routing либо отсутствует, либо выстроена так, что обратный lookup всегда совпадает с входным интерфейсом. Это самый строгий режим, но он плохо переносит сложные сети.
Перейти на loose mode
Это практичный выбор для многих продакшн-серверов с двумя интерфейсами, вторичными адресами или нестандартной маршрутизацией. Входящий пакет не будет отброшен только потому, что лучший ответ идёт через другой интерфейс, если источник вообще достижим.
sysctl -w net.ipv4.conf.all.rp_filter=2
sysctl -w net.ipv4.conf.default.rp_filter=2
sysctl -w net.ipv4.conf.eth0.rp_filter=2
sysctl -w net.ipv4.conf.eth1.rp_filter=2Это изменение временное и пропадёт после перезагрузки, если не записать его в постоянную конфигурацию.
Отключить rp_filter на конкретном интерфейсе
Иногда один интерфейс участвует в специфичной схеме: туннель, VPN, overlay, VRRP или отдельная backhaul-сеть. Тогда разумнее не ослаблять проверку везде, а отключить её только там, где она объективно мешает.
sysctl -w net.ipv4.conf.eth1.rp_filter=0Это почти всегда лучше, чем глобально отключать фильтр на всём хосте без понимания последствий.
Как сделать настройки постоянными в Debian/Ubuntu
Чтобы параметры не исчезли после перезагрузки, создайте отдельный файл в /etc/sysctl.d/. Так сопровождать систему заметно проще, чем править общий /etc/sysctl.conf.
cat > /etc/sysctl.d/60-rp-filter.conf << 'EOF'
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.eth0.rp_filter = 2
net.ipv4.conf.eth1.rp_filter = 0
EOF
sysctl --systemЕсли интерфейсы создаются динамически, значение default особенно важно: оно будет применяться к новым интерфейсам. Но уже существующие интерфейсы лучше настраивать явно, иначе можно получить смесь старых и новых значений.
Сами маршруты и правила тоже должны переживать reboot. Конкретный способ зависит от того, чем вы управляете сетью: ifupdown, netplan, systemd-networkd или NetworkManager. Важно не только прописать команды, но и убедиться, что они применяются в правильном порядке: сначала адреса и connected routes, затем таблицы, затем ip rule.

Практический сценарий: два провайдера и два публичных IP
Представим сервер, у которого два интерфейса. На eth0 опубликован основной сайт, на eth1 — API для партнёров с отдельным IP и отдельным uplink. Приложения слушают оба адреса. Если оставить одну default route через eth0, то ответы с адреса на eth1 могут уходить через eth0, часть клиентов увидит таймауты, а ядро начнёт логировать martian source.
Правильный путь здесь такой:
- описать отдельные таблицы для каждого uplink;
- добавить source-based rules для каждого адреса;
- проверить
ip route getдля обоих source IP; - перевести
rp_filterв2или отключить его на проблемном интерфейсе, если схема заведомо асимметрична; - протестировать входящий и исходящий трафик отдельно.
Если подобная схема используется ещё и для site-to-site-связности, полезно проверить, как policy routing сочетается с туннелями — об этом отдельно есть статья про site-to-site на WireGuard.
Как диагностировать проблему пошагово
Если подозреваете конфликт между rp_filter и policy routing, не меняйте всё сразу. Лучше пройти короткий чек-лист.
- Посмотрите адреса и интерфейсы:
ip addr. - Проверьте правила:
ip rule show. - Проверьте
mainи дополнительные таблицы:ip route show table all. - Смоделируйте route lookup для конкретного source:
ip route get ... from ... iif .... - Проверьте журнал ядра на
martian source. - Временно переключите проблемный интерфейс в
rp_filter=0или2и сравните поведение. - Если проблема исчезла, доведите policy routing до состояния, в котором логика маршрутизации описана явно и сохраняется после перезагрузки.
Очень полезно параллельно запускать tcpdump на обоих интерфейсах. Если SYN приходит на eth1, а ответный SYN-ACK уходит через eth0, у вас классическая асимметрия.
tcpdump -ni eth0 host 203.0.113.50
tcpdump -ni eth1 host 203.0.113.50Частые ошибки в реальных конфигурациях
Одна default route на сервере с двумя независимыми uplink
Это самый частый источник проблем. Система вроде работает, пока трафик не начинает зависеть от source IP.
Настроили ip rule, но забыли маршруты в таблице
Правило само по себе ничего не даёт, если в соответствующей таблице нет connected route и default gateway. Очень часто забывают маршрут до собственной подсети через интерфейс.
Поменяли только all и default, но не конкретный интерфейс
Из-за этого после тестов остаётся неожиданный набор значений: глобально уже loose mode, а на интерфейсе всё ещё strict.
Отключили rp_filter навсегда, не разобравшись с маршрутизацией
Иногда это допустимо, но чаще просто маскирует реальную проблему. Через какое-то время появляется новый адрес, новый сервис или туннель — и старая неявная логика снова ломается.
Не проверили поведение после reboot
Временные команды через ip и sysctl -w отлично помогают в диагностике, но исчезают после перезагрузки. Если не оформить изменения постоянно, вы получите плавающий инцидент.
Рекомендуемый практический подход
- Сначала описать желаемую исходящую политику через
ip ruleи отдельные таблицы. - Проверить выбор маршрута командами
ip route getдля каждого source IP. - Если схема асимметрична по дизайну, перевести
rp_filterв2. - Если особый случай касается одного интерфейса, отключить фильтр только на нём.
- Зафиксировать всё в постоянной конфигурации и обязательно проверить результат после
reboot.
Такой подход лучше, чем бинарный выбор между «строго везде» и «выключить всё». Он сохраняет управляемость и делает сетевую схему понятной не только вам сегодня, но и коллеге через полгода.
Итог
rp_filter не является врагом policy routing — проблемы начинаются, когда серверу навязывают слишком простую модель маршрутизации в сложной сети. Если у вас один интерфейс, strict mode обычно полезен. Если сервер использует несколько source IP, несколько шлюзов или асимметричные пути, нужно явно описать политику через ip rule и ip route, а затем подобрать подходящий режим reverse path filtering.
Если вы видите martian source в dmesg, не считайте это «глюком ядра». Чаще всего это честный сигнал о том, что текущая таблица маршрутизации не совпадает с реальным движением трафика. Приведите в порядок policy routing, сохраните настройки на постоянной основе и отдельно проверьте всё после перезагрузки — тогда сеть будет вести себя предсказуемо и без неприятных сюрпризов в продакшне.


