Изоляция трафика в Linux

Изоляция трафика Linux

Изоляция трафика разных сетей в пределах одной операционной системы распространена повсеместно. Для конечных пользователей она незаметна — в этом ее цель. Но знаете ли вы, какие средства для разных уровней изоляции предоставляет ядро Linux и как ими воспользоваться самому, не рассчитывая на интегрированные средства управления вроде Docker? Давайте попрубуем разобраться.

Можно по-разому использовать изоляцию частей сетевого стека. Провайдеры предоставляют своим клиентам виртуальные выделенные линии и вынуждены как-то обходить конфликты адресов пользовательских сетей, чтобы агрегировать их трафик на одном определенном устройстве. Админы и программисты изолируют приложения в контейнерах, чтобы не запускать отдельную копию всей оперционной системы в виртуальной машине, и контейнер взаимодействует с миром через виртуальный интерфейс, хотя ядро у всех контейнеров общее.

В этой статье предполагается, что у вас свежее ядро — 4.8 или новее.

Множественные таблицы маршрутизации

Эта возможность известна всем, кто когда-либо делал балансировку между двумя провайдерами. Кроме таблицы маршрутизации по умолчанию, в Linux можно создать до 255 независимых друг от друга таблиц.

Таблица по умолчанию называется main, и ей присвоен номер 254. Именно в нее попадают маршруты, если добавить их с помощью ip route add или route без дополнительных параметров. Еще пара таблиц зарезервирована, а с остальными можно делать что захотите.

Какая таблица для какого трафика будет использоваться, определяется правилами, вы можете создать их с помощью ip rule add. Вывод ip rule show отобразит правила по умолчанию:

Символьные имена таблиц вы найдете (и перенастроите) в файле /etc/iproute2/rt_tables. У правила про таблицу main (254) такой низкий приоритет, чтобы пользовательские таблицы с меньшими номерами обрабатывались до нее. Как видите, никакого особого статуса у main на самом деле нет, это правило вообще можно было бы удалить или переопределить.

Таблица local, которая фигурирует в правиле 0, содержит специальные маршруты для локальных и широковещательных адресов. Например, присвоение адреса 192.0.2.1/24 интерфейсу eth0 создает вот такие маршруты в этой таблице:

Ядро самостоятельно управляет этой таблицей. О не стоит знать, но трогать в ней ничего не следует.

РЕКОМЕНДУЕМ:
Советы и трюки для настройки сети в Linux

Создание своих таблиц

Новые таблицы создаются автоматически, если добавить в них маршрут. Указать таблицу можно опцией table:

Чтобы увидеть маршруты в новой таблице, нужно добавить ту же опцию к ip route show.

В iproute2 все команды можно сокращать. Например: ip ro sh t 200.

Сами по себе таблицы не приносят никакой пользы — нужны правила, которые направят в них трафик.

Создание правил

Правила создаются командой ip rule add. Наиболее распространенный вариант применения — source-based routing, к примеру отправка трафика разных сетей через различных провайдеров.

Предположим, что у нас есть локальная сеть 10.0.0.0/24 и мы хотим отправить ее трафик в интернет через маршрутизатор 192.0.2.10, а не основной маршрут по умолчанию. Это можно сделать командами

За детальными инструкциями по работе с несколькими провайдерами можно обратиться к классическим lartc.org HOWTO или policyrouting.org, а за справкой по синтаксису — к man ip-rule или моему руководству.

Классификация пакетов по меткам netfilter

Факт первый: возможности классификации трафика можно серьезно расширить с помощью опции fwmark. Сама команда ip rule add поддерживает не очень много собственно сетевых опций: from/ to (адрес источника и назначений), iif/ oif (входной и выходной интерфейс), ipproto (протокол), sport/ dport (порт источника и назначения), tos (значение Type of Service).

К счастью, netfilter может добавлять к пакетам метки, можно ссылаться на них в опции fwmark и создавать правила даже для самых экзотических ситуаций.

Главное — знать порядок обработки пакетов в ядре и добавлять правила для установки меток в цепочку, которая обрабатывается до маршрутизации пакетов: например, mangle OUTPUT для локального трафика и mangle FORWARD для трафика из других сетей.

Порядок обработки пакетов отлично показан на диаграмме.

В качестве глупого, но показательного примера мы отправим ICMP-пакеты размером больше ста байт через шлюз 192.0.2.20. Для простоты тестирования мы сделаем, чтобы правило применялось к локальным пакетам, поэтому метку мы ставим в цепочке mangle OUTPUT:

Чтобы протестировать в реальности, замените 192.0.2.10 на какой-нибудь хост из вашей сети — ядро Linux не добавляет маршруты через заведомо недостижимый шлюз.

Классификация по идентификаторам пользователей

Факт второй: правила можно применять к отдельным пользователям. Возможных применений два. Первое — имитировать VRF или netns, запустив процесс от имени пользователя, к которому применяется правило. Так делать не стоит, в новых ядрах есть намного лучшие решения.

Но эта возможность может помочь в отладке правил. Проверить, как работают маршруты, может быть не так просто, особенно если нет доступа ко всем хостам. В таком случае можно применить правило к пользователю и тестировать от его имени. Таким же способом можно завернуть весь трафик пользователя в VPN-туннель, не трогая системный трафик. Допустим, у нас поднят OpenVPN с интерфейсом tun0 и есть пользователь test с id=1000. Отправим весь его трафик через VPN:

Как видите, возможные применения множественных таблиц и правил весьма широки. Но они не позволяют разрешить конфликт адресов между сегментами сети или полностью изолировать трафик процесса. Для этих целей нужно использовать VRF или network namespaces. Начнем с VRF.

РЕКОМЕНДУЕМ:
Туннели в Linux

VRF

VRF (Virtual Routing and Forwarding) наиболее популярен среди провайдеров, которым нужно подключить независимых клиентов к одному физическому устройству. С его помощью можно ассоциировать сетевые интерфейсы с отдельными таблицами маршрутизации. Если думаете, что того же эффекта можно достигнуть опцией iif/oif в ip rule add, то спешу огорчить вас — это не совсем так.

С «простыми» таблицами и правилами вы можете добавить свои маршруты в отдельную таблицу, но служебные маршруты ядра — local и broadcast, которые требуются для правильной работы с адресами интерфейса, остаются в таблице по умолчанию. Это и делает невозможным подключение клиентов с одинаковыми или пересекающимися сетями к одному маршрутизатору.

Каждый раз, когда мы присваиваем интерфейсу адрес, ядро создает уже описанные выше local- и broadcast-маршруты в таблице local и маршрут к сети в таблице main. Например, присвоение интерфейсу eth0 адреса 192.0.2.100/24 создает маршрут вида

Эти маршруты называют connected routes. Если бы их не было, создать маршрут вроде ip route add default via 192.0.2.1 было бы невозможно: адрес 192.0.2.1 считался бы недостижимым из-за отсутствия явного маршрута к 192.0.2.0/24.

VRF позволяет разрешить конфликты адресов, перенеся в отдельную таблицу все ассоциированные с сетевым интерфейсом маршруты — как пользовательские, так и служебные.

Создание VRF

Для примера создадим VRF с названием customer1 и привяжем его к таблице маршрутизации 50:

Он выглядит как виртуальная сетевая карта. Все сетевые интерфейсы ядро создает в выключенном состоянии, поэтому без второй команды не обойтись: перед использованием его нужно включить. Просмотреть VRF вы можете командой ip vrf show:

Присваиваем VRF сетевые интерфейсы

В VRF можно добавить любые интерфейсы, как физические, так и виртуальные (к примеру, туннели). Для демонстрации мы создадим интерфейс типа dummy (аналог loopback) и присвоим ему адрес 10.133.0.1/24:

Каждый раз, когда мы присваиваем интерфейсу адрес, в таблице main появляется маршрут к его сети:

Теперь привяжем наш интерфейс к VRF customer1:

Картина в ip vrf show не изменится — к сожалению, эта команда не показывает принадлежность интерфейсов VRF. Все интерфейсы определенного VRF можно увидеть в выводе ip link show vrf $VRF:

Если выполнить ip ro sh 10.133.0.0/24, вы увидите, что маршрут к этой сети исчез. Ядро перенесло его и все остальные служебные маршруты для этой сети в таблицу 50:

Если удалить интерфейс из VRF командой sudo ip link set dum1 nomaster, маршруты вернутся обратно.

РЕКОМЕНДУЕМ:
Этапы загрузки Linux

Выполнение команды внутри VRF

VRF изолирует именно сети, а не приложения. Один и тот же процесс может подключиться к нескольким разным VRF. Работу с VRF с точки зрения разработчика приложений мы не будем затрагивать в этой статье.

Знать про существование VRF нужно очень узкому классу программ: в основном демонам протоколов маршрутизации и различным реализациям VPN. Любую программу очень легко запустить внутри любого VRF с помощью команды ip vrf exec $VRF $COMMAND.

К примеру, выполним ping к адресу самого dum1. Из VRF по умолчанию он не сработает, поскольку его маршруты были удалены из основной таблицы:

Но внутри VRF customer1 все работает:

Данным способом можно выполнить любую команду (включая другую команду ip). Это существенно упрощает отладку и тестирование настроек VRF по сравнению с простыми таблицами и правилами.

Сетевые пространства имен (network namespaces)

VRF обеспечивает изоляцию только на сетевом уровне. Канальный уровень остается общим для всех VRF, и, к примеру, sudo ip vrf exec customer1 ip link list покажет нам все интерфейсы: и принадлежащий нашему VRF dum1, и все остальные. Это может быть как преимуществом, так и недостатком.

Если VRF недостаточно, можно привлечь сетевые пространства имен (netns) — самый глубокий уровень изоляции. Именно его используют контейнеры вроде LXC. Они создают полную копию сетевого стека, и процессы в одном пространстве имен не могут увидеть ничего из других — даже физические сетевые интерфейсы.

Создание пространств имен

Создать новое пространство имен можно командой ip netns add, а просмотреть список существующих — командой ip netns show.

Для простоты тестирования в пределах отдельно взятой машины создадим еще один dummy-интерфейс dum2 и присвоим его нашему пространству имен:

Если в случае с VRF мы могли увидеть наш dum1 в обычном выводе ip link list, то с пространствами имен дела обстоят иначе: dum2 исчезнет оттуда и увидеть его можно будет только изнутри mynamespace. Давайте убедимся в этом.

Выполнение команды

Выполнить команду внутри пространства имен можно с помощью ip netns exec $NAMESPACE $COMMAND. Если выполнить данным способом ip link list внутри mynamspace, мы увидим dum2 и копию интерфейса lo (loopback) — и ничего больше.

В свежих версиях iproute2 (в моей 5.0.0 точно) есть более короткий способ — ip -n $NAMESPACE:

Следует отметить, что при передаче интерфейса в другое пространство имен он всегда будет выключен и с него будут удалены все адреса, поэтому в скриптах инициализации и подобном поднимать интерфейс и настраивать его нужно уже после ip link set ... netns.

Взаимодействие с другими пространствами имен

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

Устройство veth1 мы оставим в пространстве имен по умолчанию, а veth2 добавим в mynamespace, после чего включим оба интерфейса (они, как обычно, создаются выключенными):

Мы присвоим адреса обоим интерфейсам, и взаимодействие между пространствами имен будет организовано так же, как маршрутизация трафика между разными хостами. Используем сеть 10.136.0.0/30 для связи между ними:

Добавим адрес интерфейсу dum2 и статический маршрут к его сети из основного пространства имен.

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

Существует несколько проектов для интеграции с systemd, позволяющих упростить изоляцию приложений, например systemd-named-netns.

РЕКОМЕНДУЕМ:
Погружение в x86-64 SystemV ABI

Выводы

Как видите, ядро Linux предоставляет механизмы изоляции сетевого трафика на все случаи жизни — от минимальной до полной. Надеюсь, эта статья поможет вам выбрать нужный для ваших задач и эффективно им воспользоваться.

Понравилась статья? Поделиться с друзьями:
Добавить комментарий