Skip to content

About

Динамическая маршрутизация по источнику в Linux

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

Динамическая маршрутизация по источнику

Ответ должен уйти тем же интерфейсом, которым пришёл запрос. Обычная таблица маршрутов этого не обещает: она смотрит на адрес назначения и спокойно выбирает другой выход. В несимметричной сети, у чёрной дыры и там, где из сети есть несколько выходов, такой выбор ломает разговор.

Этот репозиторий хранит привязку в самом соединении. На первом пакете цель nftables DYNSRCROUTE записывает в расширение conntrack выходной интерфейс и, если он есть в кадре, адрес соседа на канальном уровне. Дальше ядро, уже в поиске маршрута, принуждает ответ идти через этот интерфейс. Условие DYNRT только проверяет, что запись для текущего направления уже есть.

Правило нужно один раз, на класс трафика. Отдельной метки fwmark, отдельного ip rule и отдельной таблицы на каждый поток нет.

Проверенная связка: ядро Linux 6.12.111, nftables 1.1.3, libnftnl 1.2.9. Модуль собирается вне дерева ядра. В ядро входят шесть узких патчей, и без загруженного модуля они ничего не меняют в поведении.

Подробный рассказ, со схемами и с историей испытаний, лежит в article/article_habr_02.md. Рядом черновик article/article_habr.md.

Что лежит в корне

Makefile                         сборка модуля
srcdynroute.c                    цель DYNSRCROUTE и условие DYNRT
include/dynrt.h                  раскладка ячейки и договор с ядром
patches/kernel/0001 … 0006       патчи ядра 6.12.111
patches/userspace/1001, 1002     грамматика nftables и libnftnl
tests/                           серия S1–S8 и обвязка
article/                         статьи и схемы
MANIFEST                         sha256 перечисленных деревьев
LICENSE                          GPL-2.0

Патчи ядра, по порядку:

патч что делает
0001-dynrt-abi-header заголовок include/net/netfilter/dynrt.h: две ячейки по 16 байт, 32 байта на соединение
0002-ct-ext-src-dyn-rt расширение conntrack NF_CT_EXT_SRC_DYN_RT
0003-nf-dynrt-helper встроенный посредник net/netfilter/nf_dynrt.c: указатель на операции под RCU и пустые обёртки, пока модуль не загружен
0004-fib-oif-forcing принудительный выходной интерфейс в поиске маршрута IPv4 и IPv6, на входном и исходящем пути; если маршрута нет, один запасной поиск без принуждения
0005-neighbour-l2-override подмена адреса канального уровня в neigh_resolve_output и neigh_connected_output
0006-fib-blackhole-beats-forced-oif маршрут blackhole не перебивает уже закреплённый выходной интерфейс

Патч 0003 включается ключом CONFIG_NF_DYNRT. Патчи 0004–0006 зависят от этого заголовка, не от conntrack и не от nftables.

Пользовательские правки:

патч что делает
1001_nft-dynrt.patch слова DYNSRCROUTE и DYNRT в грамматике nftables 1.1.3
1002_libnftnl-dynrt.patch операции без атрибутов в libnftnl 1.2.9

Их ставят в /usr/local, чтобы не затереть дистрибутивный /usr/sbin/nft.

Адреса в этом дереве

Примеры, схемы и испытания говорят на двух сетях:

роль адрес
первый сегмент, хост enp4s0 192.168.200.67
первый сегмент, шлюз enp0s3 192.168.200.12
второй сегмент, мост brvm0 192.168.129.1
второй сегмент, шлюз enp0s8 192.168.129.12

Сети 192.168.200.0/24 и 192.168.129.0/24 не пересекаются. Испытания ждут именно эти адреса на названных интерфейсах. Переименование в файлах само по себе ничего не меняет на живых машинах: интерфейсам эти адреса нужно назначить до прогона.

Рядом живут другие сети, их замена не касалась. IPv6 в S2 — fd00:db8:beef::/64. Пересылка в S3 — 10.66.0.0/24. Пара пространств имён в S4 — 10.0.1.0/24 и 10.0.2.0/24. Синтетические сегменты S6 — 172.30.10.0/24, 172.31.10.0/24, 172.33.10.0/24. Наложение VXLAN — 192.168.77.0/24 поверх первого сегмента.

Имена интерфейсов в тестах свои: на шлюзе enp0s3 и enp0s8, на хосте enp4s0 и brvm0. Команды на шлюз уходят по ssh, псевдоним хоста в ~/.ssh/config — deb13. nft на шлюзе ожидается в /usr/local/sbin/nft.

Сборка ядра

Из корня репозитория клонируют дерево 6.12.111 и накладывают патчи из соседнего каталога patches/kernel:

git clone --depth 1 --branch v6.12.111 \
    https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git linux-6.12.111
cd linux-6.12.111
for p in 0001-dynrt-abi-header \
         0002-ct-ext-src-dyn-rt \
         0003-nf-dynrt-helper \
         0004-fib-oif-forcing \
         0005-neighbour-l2-override \
         0006-fib-blackhole-beats-forced-oif; do
    git apply ../patches/kernel/$p.patch
done
make olddefconfig
scripts/config --enable NF_DYNRT
make -j"$(nproc)" bindeb-pkg

bindeb-pkg собирает пакеты. Для одной лишь сборки модуля достаточно make modules_prepare в этом дереве. Каталог linux-6.12.111/ в репозиторий не входит.

Сборка nftables

Архивы распаковывают в корне репозитория, чтобы ../patches/userspace смотрел куда нужно.

tar xf libnftnl-1.2.9.tar.bz2
cd libnftnl-1.2.9
git apply ../patches/userspace/1002_libnftnl-dynrt.patch
./configure --prefix=/usr/local && make -j"$(nproc)" && make install
cd ..

tar xf nftables-1.1.3.tar.bz2
cd nftables-1.1.3
git apply ../patches/userspace/1001_nft-dynrt.patch
./configure --prefix=/usr/local && make -j"$(nproc)" && make install
cd ..
ldconfig

Проверка, что в пути именно эта сборка: ldd /usr/local/sbin/nft.

Сборка модуля

KDIR — дерево, в которое уже вошли патчи 0001–0006. Если каталог /root/srcdynroute/linux-6.12.111 существует, Makefile берёт его сам. Иначе нужен явный путь, либо заголовки ядра, на котором идёт сборка.

make KDIR=/путь/к/linux-6.12.111
make KDIR=/путь/к/linux-6.12.111 install
modprobe srcdynroute

Модуль зависит от nf_conntrack и nf_tables. Псевдонимы nft-expr-DYNSRCROUTE и nft-expr-DYNRT позволяют ему загрузиться при первом обращении из nft. Выгрузка (rmmod) имеет смысл ещё и потому, что обработчик выхода очищает таблицу anchor. Пока на модуль ссылается правило, ядро ответит Module in use: сначала удаляют таблицу nftables.

Правило

В блоке chain { } каждая строка — отдельное правило. Условия и DYNSRCROUTE пишут одной строкой. Иначе цель сработает на любом пакете, а пустые строки условий не отсекают ничего. Это штатный разбор nftables, не особенность нашей сборки.

table inet dynrt {
  chain wr {
    type filter hook prerouting priority filter; policy accept;
    iifname "enp0s3" ip daddr 192.168.200.12 icmp type echo-request counter DYNSRCROUTE
  }
}

Проверка, что правило одно: /usr/local/sbin/nft --debug=netlink list table inet dynrt.

Смысл строки: пакет запроса вошёл в enp0s3 и адресован шлюзу. Ядро запоминает этот вход. Ответ того же соединения уйдёт обратно через enp0s3, даже если маршрут по умолчанию смотрит в enp0s8.

Для сценариев с маршрутом blackhole на время проверки ослабляют rp_filter:

sysctl -w net.ipv4.conf.all.rp_filter=0
sysctl -w net.ipv4.conf.enp0s3.rp_filter=0

Испытания

Серия S1–S8 гоняется с хоста. Обвязка — tests/lib.sh, вход — tests/run_all.sh. Перед прогоном скрипт сам сбрасывает остаточные таблицы, перезагружает модуль и делает conntrack -F. Без этого отрицательная фаза может увидеть старую запись anchor: она живёт 60 секунд после удаления правила. Это свойство механизма, и тесты его учитывают.

cd tests
./run_all.sh          # итог пишется в /tmp/dynrt_results.log
./S1_v4_inout.sh      # или один сценарий
тест что проверяет
S1 IPv4, вход и выход: испорченный маршрут 192.168.200.0/24 уводит ответ в enp0s8, правило возвращает его в первый сегмент
S2 то же для IPv6, префикс fd00:db8:beef::/64
S3 путь пересылки FORWARD и blackhole на ответе
S4 FORWARD и явный SNAT
S5 blackhole на локальном исходящем пути, его обходит патч 0006
S6 veth, TAP с чужим ответом ARP, VXLAN; dummy пропускается задуманно, bond зависит от CONFIG_NET_BONDING
S7 сто одновременных соединений
S8 ячейка переживает удаление таблицы на живом соединении и не действует на новое

Заметки к свидетельствам и к тому, как читать счётчики, — в tests/README.md. Журналы исторических прогонов в это дерево не входят.

Короткий ручной жест для первого сегмента:

ip route replace 192.168.200.0/24 dev enp0s8
ping -c 5 192.168.200.12

Без правила ответ виден на enp0s8. С правилом выше — на enp0s3. Имеет смысл смотреть оба интерфейса через tcpdump: счётчик nftables и ip route get по отдельности картину не закрывают.

Целостность

MANIFEST хранит sha256 исходников, патчей, тестов, статей и этого README. В комментариях там же md5 каждого патча. sha256sum предупреждает о комментариях и проверяет только строки с суммой. Код возврата нулевой, когда все эти строки сошлись:

sha256sum -c MANIFEST

Лицензия

Модуль, заголовок и патчи ядра — GPL-2.0-only, как в строке SPDX-License-Identifier у srcdynroute.c. Текст лицензии — в LICENSE. Правки nftables и libnftnl живут по лицензии тех проектов, к которым они прикладываются.

About

Динамическая маршрутизация по источнику в Linux

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages