Linux VRF and Namespaces: различия между версиями

Материал из noname.com.ua
Перейти к навигацииПерейти к поиску
Метка: ручная отмена
 
(не показаны 32 промежуточные версии этого же участника)
Строка 10: Строка 10:
 
<BR>
 
<BR>
 
Пример для банального небольшого провайдера на картинке:
 
Пример для банального небольшого провайдера на картинке:
  +
<BR>
 
[[Файл:VRF 1.png]]
 
[[Файл:VRF 1.png]]
 
<BR>
 
<BR>
Строка 29: Строка 30:
   
 
* Для каждой группы клиентов создается свой VRF, своя таблица маршрутизации со своим шлюзом
 
* Для каждой группы клиентов создается свой VRF, своя таблица маршрутизации со своим шлюзом
* Так как шлюзы в разных VRF разные то часть клиентов можно направить через шейпер
+
* Так как шлюзы в разных VRF разные то часть клиентов можно направить через шейпер а другую часть мимо
  +
* На Cisco при желании можно сделать маршрутизацию между VRF - Route Leaking что б клиенты из разных VRF могли общаться друг с другом (в случае интернет-провайдера это сделать можно, в случае с гостевой сетью конечно не желательно)
  +
  +
Это все было банально и все это все и так знают (VRF без MPLS в Cisco называют VRF-Lite)<BR>
  +
<BR>
  +
'''Вопрос как это все сделать используя Linux'''
  +
  +
=<code>VRF</code> в Linux=
  +
Прежде чем говорить о VRF перечислим проблемы которые решает VRF
  +
  +
* Изоляция на уровне L3 - разные VRF имеют независимые таблицы маршрутизации
  +
  +
{{caution|text=
  +
C некоторой натяжкой можно считать VRF реализацией Policy Routing
  +
}}
  +
В Linux есть три способа получить разные таблицы маршрутизации для разных групп source адресов
  +
  +
=<code>ip rule </code>=
  +
Первый, самый старый способ - использовать несколько таблиц маршрутизации
  +
<BR>
  +
Насколько я помню он был "всегда" :) (пишут что с 99 года, те раньше чем я познакомился с Linux)
  +
<BR>
  +
Для этого требуется создать табличку маршрутизации (прописывать в <code>/etc/iproute2/rt_tables</code> ну или зависит от дистрибутива если хочется именования)
  +
<PRE>
  +
echo «123 testtable» >> /etc/iproute2/rt_tables
  +
</PRE>
  +
<PRE>
  +
ip route add default via 192.168.22.254 table testtable
  +
</PRE>
  +
  +
Создаем правило, отправляющее нужные пакеты в нужную таблицу:
  +
<PRE>
  +
ip rule add from 192.168.1.20 table testtable
  +
</PRE>
  +
Или даже вот так
  +
<PRE>
  +
ip rule add iif eth1 table testtable
  +
</PRE>
  +
==Недостатки <code>ip rule </code>==
  +
Это самый неудобный способ, и он не решает следующие проблемы
  +
* Пересечение диапазонов IP адресов. Этой проблемы нет в примере, так как адреса клиентов одинаковые, но при желании делать L3VPN она обязательно возникнет
  +
* Сложность управление и нагромождение ip rule и как следствие линейный поиск по списку с соответвующим падением производительности
  +
* Проблемы с выбором исходящего интерфейса для локального софта
  +
  +
В целом для относительно небольших и не сложных конфигураций это решение можно применять и оно работает.
  +
  +
=<code>ip netns</code>=
  +
Примерно с 2009 года для изоляции сети в Linux используют Network Namespaces
  +
  +
* Полная изоляция (в том числе можно иметь дублирование адресов в разных VRF)
  +
* Гироко применяется, и более-менее документировано
  +
* Используют всякие Докеры, Кубернетесы и прочие контейнеры
  +
  +
  +
Подробно описывать я это все конечно не буду (документации просто полно), остановлючь не недостатках
  +
  +
==Недостатки <code>ip netns</code>==
  +
Я просто оставлю цитаты со своими комментариями: <code>Network Namespace as a VRF? Just say No</code>
  +
  +
* Сложно делать Route Leaking (когда нужно часть или весь трафик пускать между VRF). Понадобится "виртуальный патчкорд" <code>veth</code> между VRF. Сделать конечно можно, масштабируемость только очень сомнительная.
  +
* Слишком полная изоляция
  +
* VRF решает задачу изоляции FIB (при этом не стоит путать FIB и таблицу маршрутизации, FIB содержит лучшие маршруты, тогда как таблица маршрутизации - все маршруты, в том числе полученные от протоколов динамической маршрутизации но не используемые прямо в данный момент как неоптимальные)
  +
* Неудобно работать - такая простая задача как просмотр всех интерфейсов требует сначала <code>ip netns</code> а потом смотреть отдельно по каждому неймспейсу
  +
* В целом - управление сетью с netns вместо vrf становится слишком сложный - тот же процесс lldp должен будет запустить столько своих экземпляров сколько есть VRF и это же касается BGPв, и (кроме того что нужно будет написать сои расширения для всех демонов) еще и требует "пропихивания"в апстрим.
  +
  +
{{#spoiler:show=Оригинал|
  +
<PRE>
  +
Network namespaces were designed to provide a complete isolation of the entire network stack — devices, network addresses, neighbor and routing tables, protocol ports and sockets.
  +
Everything networking related is local to a network namespace, and tasks within Linux can be attached to only one network namespace at a time.
  +
</PRE>
  +
  +
<PRE>
  +
VRF on the other hand is a network layer feature and as such should really only impact FIB lookups.
  +
While the isolation provided by a namespace includes the route tables, a network namespace is much more than that, and the ‘more than that’ is the overhead that has a significant impact on the software architecture and the usability and scalability of deploying VRF as a network namespace.
  +
</PRE>
  +
  +
  +
<PRE>
  +
Let’s walk through a few a simple examples to highlight what I mean about impact on the software architecture and operational model of a network namespace as VRF.
  +
  +
Because a process in Linux is limited to a single network namespace, it will only see network devices, addresses, routes, even networking related data under /proc and /sys local to that namespace. For example, if you run ‘ip link list’ to list network devices, it will only show the devices in the namespace in which it is run. This command is just listing network interfaces, not transmitting packets via the network layer or using network layer sockets. Yet, using a namespace as a VRF impacts the view of all network resources by all processes.
  +
</PRE>
  +
  +
<PRE>
  +
A NOS is more than just routing protocols and programming hardware. You need supporting programs such as lldpd for verifying network connectivity, system monitoring tools such as snmpd, tcollector and collectd and debugging tools like ‘netstat’, ‘ip’, ‘ss’. Using a namespace as a VRF has an adverse impact on all of them. An lldpd instance can only use the network devices in the namespace in which lldpd runs. Therefore, if you use a network namespace for a VRF, you have to run a separate instance of lldpd for each VRF. Deploying N-VRFs means starting N-instances of lldpd. VRF is a layer 3 feature, yet the network namespace choice means users have to run multiple instances of L2 applications.
  +
</PRE>
  +
  +
<PRE>
  +
That limitation applies to system monitoring applications such as snmpd, tcollector and collectd as well. For these tools to list and provide data about the networking configuration, statistics or sockets in a VRF they need a separate instance per VRF. N-VRFs means N-instances of the applications with an additional complication of how the data from the instances are aggregated and made available via the management interface.
  +
</PRE>
  +
  +
<PRE>
  +
And these examples are just infrastructure for a network OS. How a VRF is implemented is expected to impact network layer routing protocols such as quagga/bgp. Modifying them to handle the implementation of a VRF is required. But even here the choice is either running N-versions of bgpd or modifying bgpd to open a listen socket for each VRF which means N-listen sockets. As N scales into the 100’s or 1000’s this is wasted resources spinning up all of these processes or opening listen sockets for each VRF.
  +
</PRE>
  +
  +
<PRE>
  +
Yes, you could modify each of the code bases to be namespace aware. For example, as a VRF (network namespaces) is created and destroyed the applications either open socket local to the namespace, spawn a thread for the namespace or just add it to a list of namespaces to poll. But there are practical limitations with this approach – for example the need to modify each code base and work with each of the communities to get those changes accepted. In addition, it still does not resolve the N-VRF scalability issue as there is still 1 socket or 1 thread per VRF or the complexity of switching in and out namespaces. Furthermore, what if a network namespace is created for something other than a VRF? For example, a container is created to run an application in isolation, or you want to create a virtual switch with a subset of network devices AND within the virtual switch you want to deploy VRFs? Now you need a mechanism to discriminate which namespaces are VRFs. This option gets complicated quick.
  +
</PRE>
  +
  +
<PRE>
  +
And then there is consideration of how the VRF implementation maps to hardware for offload of packet forwarding.
  +
</PRE>
  +
  +
<PRE>
  +
I could go on, but hopefully you get my point: The devil is in the details. Many people and many companies have tried using network namespaces for VRFs, and it has proven time and time again to be a square peg for a round hole. You can make it fit with enough sweat and tears, but it really is forcing a design that was not meant to be. Network namespaces are great for containers where you want strict isolation in the networking stack. Namespaces are also a great way to create virtual switches, carving up a single physical switch into multiple smaller and distinct logical switches. But network namespaces are a horrible solution when you just want layer 3 isolation.
  +
</PRE>
  +
}}
  +
  +
=<code>ip link add vrf-1</code>=
  +
Тут все относительно несложно
  +
  +
Приведу тестовую схему:
  +
<BR>
  +
<BR>
  +
[[Файл:VRF 2.drawio.png]]
  +
<BR>
  +
([[Файл:VRF 2.drawio]])
  +
  +
На ней есть
  +
* CE1 - роутер, имитирующий клиента ( реализован с помощью network namespace)
  +
* PE1 - виртуальный интерфейс, соединенный с CE1 и добавленный к vrf-Red
  +
* Глобальная таблица маршрутизации (в общем случае это еще один VRF, просто в него добавлены все интерфейсы по-умолчанию)
  +
* внешний интерфейс
  +
==Настройка==
  +
  +
===CE1 - "виртуальный клиентский роутер"===
  +
В реальной жизни вместо интерфейса veth был бы физический интерфейс, но для тестирования сойдет и так. А вместо CE1 было б отдельное устройство
  +
Создать network namespace ce1
  +
<PRE>
  +
ip netns add ce1
  +
</PRE>
  +
  +
  +
  +
Добавить в него интерфейс ce1
  +
<PRE>
  +
ip link set ce1 netns ce1
  +
</PRE>
  +
Интерфес типа veth должен быть предварительно создан, например используя netplan
  +
<PRE>
  +
network:
  +
version: 2
  +
virtual-ethernets:
  +
pe1:
  +
peer: ce1
  +
ce1:
  +
peer: pe1
  +
dhcp4: false
  +
dhcp6: false
  +
addresses: ["192.168.98.1/24"]
  +
</PRE>
  +
Дальнейшие шаги уже требуется выполнять внутри network namespace ce1
  +
<BR>
  +
"Поднять" интерфейсы lo и ce1
  +
<PRE>
  +
ip netns exec ce1 ip link set up dev lo
  +
</PRE>
  +
  +
<PRE>
  +
ip netns exec ce1 ip link set up dev ce1
  +
<PRE>
  +
Назначить адрес на интерфейс ce1
  +
<PRE>
  +
ip netns exec ce1 ip addr add 192.168.98.1/24 dev ce1
  +
</PRE>
  +
  +
Добавить на виртуальном "клиентском роутере" шлюз "в провайдера"
  +
<PRE>
  +
ip netns exec ce1 ip ro add 0.0.0.0/0 via 192.168.98.101
  +
</PRE>
  +
  +
На этом настройка CE1 завершена
  +
  +
===vrf-Red===
  +
Создать vrf, назначить идентификатор таблицы маршрутизации (1001 в примере)
  +
<PRE>
  +
ip link add vrf-Red type vrf table 1001
  +
</PRE>
  +
Если проверить то сразу после создания можно увидеть интерфейс особого типа - vrf
  +
<PRE>
  +
ip link show vrf-Red
  +
</PRE>
  +
<PRE>
  +
9: vrf-Red: <NOARP,MASTER,UP,LOWER_UP> mtu 65575 qdisc noqueue state UP mode DEFAULT group default qlen 1000
  +
link/ether ce:f3:16:b7:a0:34 brd ff:ff:ff:ff:ff:ff promiscuity 0 allmulti 0 minmtu 1280 maxmtu 65575
  +
vrf table 1001 addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536
  +
</PRE>
  +
  +
Добавить интерфейс pe1 к vrf-Red
  +
<PRE>
  +
ip link set dev pe1 master vrf-Red
  +
</PRE>
  +
  +
"Поднять" оба интерфейса
  +
<PRE>
  +
ip link set up dev pe1
  +
</PRE>
  +
  +
<PRE>
  +
ip link set up dev vrf-Red
  +
</PRE>
  +
Назначить адрес на интерфейс "в сторону клиента" - pe1
  +
<PRE>
  +
ip addr add 192.168.98.101/24 dev pe1
  +
</PRE>
  +
  +
После выполнения этих команд, можно проверить ping из виртуального клиентского роутера СЕ1 на его шлюз:
  +
<PRE>
  +
ip netns exec ce1 ping 192.168.98.101
  +
PING 192.168.98.101 (192.168.98.101) 56(84) bytes of data.
  +
64 bytes from 192.168.98.101: icmp_seq=1 ttl=64 time=0.036 ms
  +
</PRE>
  +
  +
==Route Leaking==
  +
Пока все идет по плану, но в vrf-Red есть только один маршрут, и нет маршрута по-умолчанию:
  +
<PRE>
  +
ip ro show vrf vrf-Red
  +
192.168.98.0/24 dev pe1 proto kernel scope link src 192.168.98.101
  +
</PRE>
  +
<big>'''Добавить маршрут через Route Leaking ведуший в другой VRF (глобальный!)'''</big>
  +
<BR>
  +
Это не совсем обычнй маршрут по-тому что указывается <code>dev external</code>, при этом интерфейс с именем <code>external</code> находится в глобальном VRF
  +
<PRE>
  +
ip route add 0.0.0.0/0 via 192.168.22.254 vrf vrf-Red dev external
  +
</PRE>
  +
  +
<code>dev external</code> это не "магическое" слово, просто так переименован один из физических интерфейсов.
  +
<PRE>
  +
ip -d link show dev external
  +
4: external: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
  +
link/ether 00:99:00:00:00:03 brd ff:ff:ff:ff:ff:ff promiscuity 0 allmulti 0 minmtu 68 maxmtu 65535 addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536 parentbus virtio parentdev virtio2
  +
altname enp0s5
  +
</PRE>
   
 
=Ссылки=
 
=Ссылки=

Текущая версия на 18:04, 30 апреля 2025

Network Namespaces или VRF

Эта статья задумывается как полуперевод-полукомпиляция и возможно с добавлением собственного опыта

Зачем нужен VRF

VRF это способ "виртуализировать" сеть, и иметь несколько полностью или частично независимых экземпляров сетевого стека.
Пример для банального небольшого провайдера на картинке:
VRF 1.png
Что тут нарисовано

  • Есть 2 группу клиентов
    • Беспроводные (картинка с 2 антенами это точка доступа). Им нужно строго ограничивать скорость - гонять через выделенный роутер который делает шейпинг. Если их не шейпить, то один клиент сможет занять всю полосу в эфире, остальные на той же базовой станции работать не смогут.
    • Проводные, включены на скорости порта. Гонять их трафик через шейпер не только безсмысленно но и вредно - шейпер плохо работает на скоростях выше 50Мбит

Icon-caution.gif

Вместо шейпера тут может быть например файрволл для гостевой сети, суть от этого не меняется

  • Роутер/L3 свитч куда включены эти клиенты
  • Шейпер, пограничный роутер и "интернет" добавлены для наглядности


(тут я не касаюсь L3VPN и вообще VPN ни в каком виде)

Задача описанная выше решается просто на любом более-менее современном L3 коммутаторе

  • Для каждой группы клиентов создается свой VRF, своя таблица маршрутизации со своим шлюзом
  • Так как шлюзы в разных VRF разные то часть клиентов можно направить через шейпер а другую часть мимо
  • На Cisco при желании можно сделать маршрутизацию между VRF - Route Leaking что б клиенты из разных VRF могли общаться друг с другом (в случае интернет-провайдера это сделать можно, в случае с гостевой сетью конечно не желательно)

Это все было банально и все это все и так знают (VRF без MPLS в Cisco называют VRF-Lite)

Вопрос как это все сделать используя Linux

VRF в Linux

Прежде чем говорить о VRF перечислим проблемы которые решает VRF

  • Изоляция на уровне L3 - разные VRF имеют независимые таблицы маршрутизации

Icon-caution.gif

C некоторой натяжкой можно считать VRF реализацией Policy Routing

В Linux есть три способа получить разные таблицы маршрутизации для разных групп source адресов

ip rule

Первый, самый старый способ - использовать несколько таблиц маршрутизации
Насколько я помню он был "всегда" :) (пишут что с 99 года, те раньше чем я познакомился с Linux)
Для этого требуется создать табличку маршрутизации (прописывать в /etc/iproute2/rt_tables ну или зависит от дистрибутива если хочется именования)

echo «123 testtable» >> /etc/iproute2/rt_tables
ip route add default via 192.168.22.254 table testtable

Создаем правило, отправляющее нужные пакеты в нужную таблицу:

ip rule add from 192.168.1.20 table testtable 

Или даже вот так

ip rule add iif eth1 table testtable

Недостатки ip rule

Это самый неудобный способ, и он не решает следующие проблемы

  • Пересечение диапазонов IP адресов. Этой проблемы нет в примере, так как адреса клиентов одинаковые, но при желании делать L3VPN она обязательно возникнет
  • Сложность управление и нагромождение ip rule и как следствие линейный поиск по списку с соответвующим падением производительности
  • Проблемы с выбором исходящего интерфейса для локального софта

В целом для относительно небольших и не сложных конфигураций это решение можно применять и оно работает.

ip netns

Примерно с 2009 года для изоляции сети в Linux используют Network Namespaces

  • Полная изоляция (в том числе можно иметь дублирование адресов в разных VRF)
  • Гироко применяется, и более-менее документировано
  • Используют всякие Докеры, Кубернетесы и прочие контейнеры


Подробно описывать я это все конечно не буду (документации просто полно), остановлючь не недостатках

Недостатки ip netns

Я просто оставлю цитаты со своими комментариями: Network Namespace as a VRF? Just say No

  • Сложно делать Route Leaking (когда нужно часть или весь трафик пускать между VRF). Понадобится "виртуальный патчкорд" veth между VRF. Сделать конечно можно, масштабируемость только очень сомнительная.
  • Слишком полная изоляция
  • VRF решает задачу изоляции FIB (при этом не стоит путать FIB и таблицу маршрутизации, FIB содержит лучшие маршруты, тогда как таблица маршрутизации - все маршруты, в том числе полученные от протоколов динамической маршрутизации но не используемые прямо в данный момент как неоптимальные)
  • Неудобно работать - такая простая задача как просмотр всех интерфейсов требует сначала ip netns а потом смотреть отдельно по каждому неймспейсу
  • В целом - управление сетью с netns вместо vrf становится слишком сложный - тот же процесс lldp должен будет запустить столько своих экземпляров сколько есть VRF и это же касается BGPв, и (кроме того что нужно будет написать сои расширения для всех демонов) еще и требует "пропихивания"в апстрим.

ip link add vrf-1

Тут все относительно несложно

Приведу тестовую схему:

VRF 2.drawio.png
(Файл:VRF 2.drawio)

На ней есть

  • CE1 - роутер, имитирующий клиента ( реализован с помощью network namespace)
  • PE1 - виртуальный интерфейс, соединенный с CE1 и добавленный к vrf-Red
  • Глобальная таблица маршрутизации (в общем случае это еще один VRF, просто в него добавлены все интерфейсы по-умолчанию)
  • внешний интерфейс

Настройка

CE1 - "виртуальный клиентский роутер"

В реальной жизни вместо интерфейса veth был бы физический интерфейс, но для тестирования сойдет и так. А вместо CE1 было б отдельное устройство Создать network namespace ce1

ip netns add ce1


Добавить в него интерфейс ce1

ip link set ce1 netns ce1

Интерфес типа veth должен быть предварительно создан, например используя netplan

network:
    version: 2
    virtual-ethernets:
        pe1:
            peer: ce1
        ce1:
            peer: pe1
            dhcp4: false
            dhcp6: false
            addresses: ["192.168.98.1/24"]

Дальнейшие шаги уже требуется выполнять внутри network namespace ce1
"Поднять" интерфейсы lo и ce1

ip netns exec ce1 ip link set up dev lo
ip netns exec ce1 ip link set up dev ce1
<PRE>
Назначить адрес на интерфейс ce1
<PRE>
ip netns exec ce1 ip addr add 192.168.98.1/24 dev ce1

Добавить на виртуальном "клиентском роутере" шлюз "в провайдера"

ip netns exec ce1 ip ro add 0.0.0.0/0 via 192.168.98.101

На этом настройка CE1 завершена

vrf-Red

Создать vrf, назначить идентификатор таблицы маршрутизации (1001 в примере)

ip link add vrf-Red type vrf table 1001

Если проверить то сразу после создания можно увидеть интерфейс особого типа - vrf

ip link show vrf-Red
9: vrf-Red: <NOARP,MASTER,UP,LOWER_UP> mtu 65575 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether ce:f3:16:b7:a0:34 brd ff:ff:ff:ff:ff:ff promiscuity 0  allmulti 0 minmtu 1280 maxmtu 65575
    vrf table 1001 addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536

Добавить интерфейс pe1 к vrf-Red

ip link set dev pe1 master vrf-Red

"Поднять" оба интерфейса

ip link set up dev pe1
ip link set up dev vrf-Red

Назначить адрес на интерфейс "в сторону клиента" - pe1

ip addr add 192.168.98.101/24 dev pe1

После выполнения этих команд, можно проверить ping из виртуального клиентского роутера СЕ1 на его шлюз:

ip netns exec ce1 ping 192.168.98.101
PING 192.168.98.101 (192.168.98.101) 56(84) bytes of data.
64 bytes from 192.168.98.101: icmp_seq=1 ttl=64 time=0.036 ms

Route Leaking

Пока все идет по плану, но в vrf-Red есть только один маршрут, и нет маршрута по-умолчанию:

ip ro show vrf vrf-Red
192.168.98.0/24 dev pe1 proto kernel scope link src 192.168.98.101

Добавить маршрут через Route Leaking ведуший в другой VRF (глобальный!)
Это не совсем обычнй маршрут по-тому что указывается dev external, при этом интерфейс с именем external находится в глобальном VRF

ip route add  0.0.0.0/0 via 192.168.22.254  vrf vrf-Red  dev external

dev external это не "магическое" слово, просто так переименован один из физических интерфейсов.

ip -d link show dev external
4: external: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 00:99:00:00:00:03 brd ff:ff:ff:ff:ff:ff promiscuity 0  allmulti 0 minmtu 68 maxmtu 65535 addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536 parentbus virtio parentdev virtio2
    altname enp0s5

Ссылки