Предисловия тоже не будет. Погнали.
Давайте сделаем сеть без коммутаторов и маршрутизаторов
Вот есть у нас два хоста-клиента и есть один Linux. Хосты из одной подссети. Надо чтобы пообщаться смогли они друг с дружкой. В общем, свитчик D-link тупой сделать надо из Линукса:

Ну тут всё просто. Устанавливаем bridge-utils, создаём мостик, добавляем интерфейсы в мост, не забываем его поднять и вуаля:
apt install bridge-utils brctl addbr Bridge1 brctl addif Bridge1 ens4 brctl addif Bridge1 ens5 ip link set up ens4 ip link set up ens5 ip link set up dev Bridge1

Пинги пошли между хостами. Ура, мы сделал самый тупой в мире свитч ). Даже маки видны:
root@ubuntu:~# brctl showmacs Bridge1
port no mac addr is local? ageing timer
2 00:50:79:66:68:32 no 84.50
1 00:50:79:66:68:33 no 84.50
1 50:4d:8c:00:35:01 yes 0.00
1 50:4d:8c:00:35:01 yes 0.00
2 50:4d:8c:00:35:02 yes 0.00
2 50:4d:8c:00:35:02 yes 0.00
Вот то, что "is not local" - это наши хосты.
Давайте теперь научим свитч понимать Vlan-ы, а второй Linux сделаем роутером простеньким.
Трафик от хоста будет заходить в "access" порт, метиться 802.1q тегом, уходить в "транк", а там, повыше, маршрутизироваться.

Теперь на Linux switch-е нам нужно два моста. По одному на каждый VLAN:
# Убиваем старый мост ip link set down Bridge1 brctl delbr Bridge1 # Добавляем парочку новых brctl addbr brVlan10 brctl addbr brVlan20 ip link set up brVlan10 ip link set up brVlan20 brctl addif brVlan10 ens4 brctl addif brVlan20 ens5 # Создаём интерфейсы типа Vlan на нашем "аплинке" ip link add link ens6 name Vlan10 type vlan id 10 ip link add link ens6 name Vlan20 type vlan id 20 ip link set up dev ens6 # пихаем в мосты brctl addif brVlan10 Vlan10 brctl addif brVlan20 Vlan20
Ок, теперь, если на первом хосте попробуем попингать шлюз и посмотрим что на ens6, увидим логичные арп-запросы, но помеченные dot1.q тегом номер 10:

Ответов нет - ответить некому. Настроим Linux-роутер, который выше стоит:
# Тут подымем пару интерфейсов типа влан и повесим адреса ip link add link ens4 name SVI10 type vlan id 10 ip link add link ens4 name SVI20 type vlan id 20 ip link set up ens4 ip addr add 192.168.0.254/24 dev SVI10 ip addr add 172.16.0.254/24 dev SVI20
Теперь можем попингать отсюда наши хосты:

Но вот хосты попингать друг друга не могут, хотя наш новоиспечённый шлюз доступен:


Так мы же функцию роутинга забыли включить на Linux-роутере! Фиксим:
root@ubuntu:~# sysctl net.ipv4.ip_forward net.ipv4.ip_forward = 0 root@ubuntu:~# sysctl -w net.ipv4.ip_forward=1 net.ipv4.ip_forward = 1
Ну вот и пинги пошли

В общем, научились делать свитч и научились делать роутер из Линукса!
А теперь давайте выйдем в самый маленький в мире Интернет вот по такой схеме:

На схему добавим настоящий хардварный маршрутизатор, настроим P2P линк до Linux-роутера. Ну и сделаем то же самое со стороны Linux-а:
# Маршрутизатор обычный True-Hardware-router-0#show run int et1 interface Ethernet1 description Linux-router no switchport ip address 10.0.0.1/31 # Маршрутизатор необычный ip addr add 10.0.0.0/31 dev ens5 ip link set up dev ens5
Пинги ходят между роутерами, но не ходят в Интернет из Линукса :(

Потому что Линукс понятия не имеет как добраться в этот наш Интернет - он ему и не нужон. Но мы ему поможем - добавим статический маршрут в Интернет через железный маршрутизатор:
ip r add 7.7.7.7/32 via 10.0.0.1 dev ens5
Теперь кайф:

Но мы ж не для Linux-а Интернет-то делали. Никому не нужна сетевая инфраструктура в вакууме - нам важно предоставить сервис конечным хостам. А что там сейчас?

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

... а ответов нет :(
Если потрбалштутить дальше увидим, что пакет даже в Интернет уходит, и даже возвращается! А вот железный роутер не отправляет его обратно в нашу Linux-based локалку. Опять эти треклятые маршруты - он просто не знает где живут наши клиентские сети - маршрутизацию-то мы не настроили. Ну ок, добавим немного статики:
True-Hardware-router-0(config)#ip route 192.168.0.0/24 10.0.0.0 True-Hardware-router-0(config)#ip route 172.16.0.0/24 10.0.0.0
Теперь лучше:

Ну чтож, маршрутизацию мы наладили, но когда сеть становится большой - пилить ручками статические маршруты становится уныло, да и не безопасно с точки зрения стабильности сети. Нужен, конечно, какой-то IaC подход - когда статические маршруты по сети будет разливать бездумная машина, но пока современное сообщество к такому не готово, и доверяет эту задачу динамическим протоколам маршрутизации. Но для таких протоколов, нужен какой-то control plane - какой-то демон (или много демонов), который будет общаться с рядом стоящими маршрутизаторами и обмениваться с ними полезной информацией, а ещё лучше потом и в data plane маршрутики программировать.
Встречайте, FRR!
Практика важнее теории, поэтому давайте просто установим FRR командой
apt install frr
и для чистоты эксперимента грохнем наши статические маршруты, которые мы ручками настроили:
#Linux root@Linux-simple-router:~# ip r del 7.7.7.7/32 via 10.0.0.1 dev ens5 # Железяка: True-Hardware-router-0(config)#no ip route 172.16.0.0/24 10.0.0.0 True-Hardware-router-0(config)#no ip route 192.168.0.0/24 10.0.0.0
А теперь настроим между двумя маршрутизаторам самый модный протокол динамической маршрутизации - RIP
В /etc/frr/daemons на линуксе включим RIP и перезапустим frr
sudo sed -i 's/^ripd=no/ripd=yes/' /etc/frr/daemons sudo systemctl restart frr
Далее заходим через vtysh в командную строку frr-а, и там нас ждёт приятный cisco-like cli, через который мы и настроем RIP:
Linux-simple-router# show run ripd Building configuration... ... router rip network 172.16.0.0/24 network 192.168.0.0/24 network 10.0.0.0/31 exit ! end
Провернём зеркальную операцию с обратной стороны:
True-Hardware-router-0#show run | sec router.rip router rip network 7.7.7.0/24 network 10.0.0.0/31 no shutdown
Глядим - маршруты пришли, видны в таблице маршрутизации в FRR-е:
Linux-simple-router# show ip ro rip R>* 7.7.7.0/24 [120/2] via 10.0.0.1, ens5, weight 1, 04:46:17
...и даже в ядре Linux-а

Показывать связанность от хоста в "Интернет" не буду - верим мне тут на слово. Да и это уже не важно. А важно то, что мы с помощью FRR-а наполнили таблицу маршрутизации нашего хоста маршрутами из внешних источников.
Что же такое этот наш "FRR"? Пару-тройку фактов приведу. Основное можно прочитать в Гугле, в любой модной LLM или в конце концов на сайте https://frrouting.org/ :
Набор протоколов маршрутизации для Linux (и кажется других Unix тоже)
Форк проекта Quagga (от 2017-го), которая сама является форком проекта Zebra (от 2003-го), которая была создана когда-то в 90-е...
Поддержка всяких разных протоколов:
Простых и скучных, типа RIP, OSPF, ISIS, BGP, LDP
Сложных и весёлых, типа NHRP, MP-BGP (в т.ч. EVPN), Babel и даже сигналку для сервисов поверх SRv6 умеет
Основные контрибуторы проекта NVIDIA, Dell, Microsoft. Такие Network OS как Кумулус (https://www.nvidia.com/en-eu/networking/ethernet-switching/cumulus-linux/) и Соник (https://sonicfoundation.dev/) имеют под капотом FRR
Вот и вся теория про FRR. Дальше небольшое отступление про то, как это концептуально работает, и погнали дальше усложнять нашу сеть.
Небольшое напоминание, что у любого современного маршруторизатора есть несколько "плейнов" - нам важны Control Plane и Data Plane. Если очень грубо,то Датаплейн - занимается перекладыванием пакетов из одного интерфейса в другой. А правила, по которым это происходит, ему (датаплейну) доносит контролплейн. Контролплейн - мозги, датаплейн - руки\ноги\сердце. Вот картинка от Ивана:

А вот ещё от руки:

Всякие протоколы трудятся и делают свои таблички независимо. Потом приходит какой-то арбитр и по каким-то критериям делает одну единую табличку, выбирая кто более лучший - формирует Routing Information Base (RIB). А дальше всё это программируется в хардварный чип и получается Forwarding Information Base. Ну это база, я просто напоминаю.
С другой стороны у Linux-а есть User Space и Kernel Space. Дальше картинка-напоминалка (Я не знаю где её первоисточник, встречал в Интернете без указания авторства. Если кто знает автора и место где она засветилась первый раз - напишите, добавлю копирайт):

Если очень грубо - то в Kernel Space работает операционная система, и что важно для нас - форвардинг трафика (это, конечно, в общем случае, потому что есть известные кейсы где обработку трафика переносим в User Space - типа dpdk и всё такое)
А в User Space работают приложения. Ну например FRR и его демоны.
Собирая всю мозаику воедино получаем такое вот:

ControlPlane - в юзерспейсе, DataPlane - в кернелспейсе.
Остаётся мутный пункт с тем, как же маршруты из юзерспейса запихать в ядро. Ну а если приглядеться есть какой-то непонятный netlink между ними. Netlink, если грубо, это некий интерфейс для общения между UserSpace и KernelSpace. Со своим "протоколом", то бишь стандартом общения, что очевидно, потому что было бы странно, если бы было бы иначе.
Наверное, лучшей литературой по тому, как устроен Netlink является kernel.org, конечно (https://docs.kernel.org/userspace-api/netlink/intro.html), ну и описание библиотеки для работы с этими самым Netlink-ом - https://www.infradead.org/~tgr/libnl/doc/core.html . Но там много матана - моя не понимать совсем ничего :( А лучшей русскоязычной статьёй, что я нашёл является вот эта - https://habr.com/ru/articles/121254/ - матана там не меньше, но он хотя бы на русском и с комментариями. В общем, история, в первую очередь, программистская.
Ну а для сетевиков есть дамп трафика, который можно посмотреть в ваершарке. Давайте пока перестанем анонсировать маршрут из "Интернета", включим модуль ядра, чтобы подглядывать за netlink-ом и подглядим за ним:
# выключаем аплинк в сторону Интернета True-Hardware-router-0(config)#interface Ethernet2 True-Hardware-router-0(config-if-Et2)#shutdown ### root@Linux-simple-router:~# modprobe nlmon root@Linux-simple-router:~# ip link add nlmon0 type nlmon root@Linux-simple-router:~# ip link set nlmon0 up
После этого у нас в системе появился интерфейс nlmon0 и, включив анонс сети обратно, мы можем посмотреть, что нам нём происходит обычным советским tcpdump-ом:

Ничего, конечно, не понятно, нужен ваершарк:

Тут тоже много всякого. Нам нужно отфильтровать по "netlink-route.rt_family == 2" - то есть смотрим анонсы IPv4 маршрутов. А не, там тоже много всего получится. Мы ищем что-нибудь про сеть 7.7.7.0, давайте превратим её в 16-ричное число и попробуем поискать в поле "Data" атрибута netlink-сообщения - "netlink.attr_data == 07.07.07.00":

Выделил на картинке то, что может быть важно - есть маршрут на сеть, есть маска маршрута, есть некстхоп, есть исходящий интерфейс - что ещё нужно для счастливого маршрута?
Ок, с теорией вроде бы разобрались. Этого должно хватить на пару лет эксплуатации. Погнали сделаем что-нибудь повеселее!
Оверлеи с помощью FRR. MPLS
Как все знают, MPLS сам по себе не может являться целью. MPLS нужен для сервисов. Настроим самый типовой кейс - L3 VPN, вот с такой топологией:

Есть пару vrf-aware роутеров, в которые втыкаются клиенты (Provider Edge - PE) и ядро сети, которое про эти VRF-ы не знает, а просто обеспечит нам транспорт от PE к PE. Ну а сам пользовательский трафик мы обернём MPLS меточками и затуннелируем.
Для начала нужно, конечно, сказать Linux-ам, что бы они теперь превратились в MPLS-aware маршрутизаторы:
# Включаем опции ядра modprobe mpls_router modprobe mpls_iptunnel modprobe mpls_gso # Включаем обработку MPLS меток на интерфейсах: sysctl -w net.mpls.conf.ens4.input=1 sysctl -w net.mpls.conf.ens5.input=1 sysctl -w net.mpls.conf.ens6.input=1 # Сколько меток сможем использовать. Мильёна должно хватить сегодня sysctl -w net.mpls.platform_labels=1048575
IP-линки между маршрутизаторами настраивать мы уже умеем, главное ещё не забыть про "sysctl -w net.ipv4.ip_forward=1" везде.
Теперь осталось наделать красных и зелёных vrf-ов на наших PE-шках:
Красный хост слева настраиваем простенько - IP да шлюз, остальные делаем по аналогии:

Интерфейсы на PE слева пока не настроены:

Поместим их в VRF-ы и повесим адреса!
# Делаем vrf и привязываем его к выделенной таблице маршрутизации sudo ip link add vrf-Red type vrf table 1001 sudo ip link set dev vrf-Red up # Запихиваем наш интерфейс в этот vrf, включаем его и вешаем адрес sudo ip link set dev ens6 master vrf-Red sudo ip addr add 192.168.0.254/24 dev ens6 sudo ip link set dev ens6 up
Теперь можно посмотреть состояние vrf-а, какие интерфейсы к нему привязаны и его таблицу маршрутизации

Аналогично делаем для зелёного vrf-а, не забывая, что нам надо привязать его к другой таблице маршрутизации для изоляции трафика:
sudo ip link add vrf-Green type vrf table 1002 sudo ip link set dev vrf-Green up sudo ip link set dev ens7 master vrf-Green sudo ip addr add 172.16.0.254/24 dev ens7 sudo ip link set dev ens7 up
Теперь хосты могут пингать свои шлюзы, но не могу пингать друг друга, так как находятся в разных VRF-ах:


На правом PE делаем всё по аналогии, но добавим немного энтропии, используя другие таблицы маршрутизации и другие имена VRF, чтобы показать что это всё значимо лишь локально:
# Один vrf: sudo ip link add vrf-R type vrf table 108 sudo ip link set dev vrf-R up sudo ip link set dev ens5 master vrf-R sudo ip addr add 192.168.1.254/24 dev ens5 sudo ip link set dev ens5 up # И второй: sudo ip link add vrf-G type vrf table 109 sudo ip link set dev vrf-G up sudo ip link set dev ens6 master vrf-G sudo ip addr add 172.16.1.254/24 dev ens6 sudo ip link set dev ens6 up
Ну и можно проверить доступность хостов прям попингав их из vrf-ов:

Что ещё нужно сделать, перед тем как приступить к настройке MPLS-ной обвязки через FRR - добавить на всех линукс-маршрутизаторах по новому Loopback интерфейсу - его и будем использовать для всех служебных протоколов (не забываем включить на интерфейсе работу с MPLS-ом)
ip link add name lo1 type dummy ip link set dev lo1 up sysctl -w net.mpls.conf.lo1.input=1
Ок, кажется всё что надо было сделать внутри Linux-а сделано. Дальше работаем исключительно с FRR-ом. Ещё раз напомню схему:

План такой:
1) Настраиваем единый OSFP домен, чтобы обменятся адресацией лупбеков
2) Настраиваем MPLS LDP, чтобы обменяться транспортными метками
3) Когда левый PE сможет достучаться до правого используя MPLS - добавим немного MP-BGP магии для обмена сервисными MPLS метками и VRF-ными маршрутами
4) ???
5) PROFIT!
Настройку первых двух пунктов буду показывать на примере связки PE-LEFT <--> P2 - все остальные связанности настраиваются аналогично.
# Заходим в консоль FRR-а: root@Linux-PE-Left-1:~# vtysh Hello, this is FRRouting (version 8.1). Copyright 1996-2005 Kunihiro Ishiguro, et al. # Все конфигурационные действия делаем из режима конфигурации: Linux-PE-Left-1# conf t # Настраиваем адреса на лупбеках: Linux-PE-Left-1(config)# int lo1 Linux-PE-Left-1(config-if)# ip address 1.1.1.1/32
На этом моменте на левом PE проверяем таблицу маршрутизации чтобы убедиться, что мы не видим маршрутов на P2, а видим только connected сети:

Дальше настраиваем OSPF, включаем его на лупбеке новом и на интерфейсах в сторону других роутеров:
#### Левый PE: Linux-PE-Left-1# conf t Linux-PE-Left-1(config)# router ospf Linux-PE-Left-1(config-router)# network 1.1.1.1/32 area 0 Linux-PE-Left-1(config-router)# network 10.1.2.0/31 area 0 Linux-PE-Left-1(config-router)# network 10.1.3.0/31 area 0 #### P2: Linux-P-2(config)# router ospf ospfd is not running Linux-P-2(config-router)#
Такс...на P2 кажется что-то пошло не так - osfpd там не запущен (странно, что на PE левом такой ошибки не было, скорее всего я настроил там демон раньше - ну Бог мне судья). В общем, исправляемся - включаем osfpd, ldpd и bgpd сразу (хоть он нужен не везде) путём правки файла /etc/frr/daemons:
sudo sed -i 's/^ospfd=no/ospfd=yes/' /etc/frr/daemons sudo sed -i 's/^ldpd=no/ldpd=yes/' /etc/frr/daemons sudo sed -i 's/^bgpd=no/bgpd=yes/' /etc/frr/daemons # рестартим FRR sudo systemctl restart frr
и смотрим в статусе что у нас все нужные демоны - "Зелёные - Ок":

Донастроим таки OSFP на P2. Делаем вот такой конфиг:
router ospf network 2.2.2.2/32 area 0 network 10.1.2.0/31 area 0 network 10.2.4.0/31 area 0 exit # Не забываем сделать "wr" ибо при рестарте FRR-а все пропадёт Linux-P-2# wr Note: this version of vtysh never writes vtysh.conf Building Configuration... Integrated configuration saved to /etc/frr/frr.conf [OK]
Если в этот момент посмотреть в дамп трафика между PE-Left и P2 увидим там приятные сообщения про OSFP - сессии установились, LSA разлетелись:

Роутеры подружились друг с другом:


Таблицы маршрутизации наполнились. Лупбеки разлетелись, роутеры могут пингать друг друга по лупбекам:



Раскатываем эту шляпу (настройки OSFP) на всех остальных роутерах и дальше приступаем к настройке LDP:
# LDP настраиваем максимально просто # настраиваем router-id, включаем на интерфейсах нам нужных # + указываем какой адрес использовать для построения TCP: mpls ldp router-id 1.1.1.1 address-family ipv4 discovery transport-address 1.1.1.1 interface ens4 interface ens5
То же самое на P2, и смотрим дамп:

Соседства поднялись:


Меточки настроились:

Раскатываем теперь и эту шляпу (LDP) по всем маршрутизаторам. И теперь если мы попингаем с лупбека левого PE (1.1.1.1) лупбек правого PE (5.5.5.5) при этом посмотрим дамп трафика где нибудь между P2 и P4 увидим что трафик оборачивается в MPLS меточку:

Как будто бы всё работает. Но ещё раз - сам по себе MPLS никого профита нам не даёт, нам нужны сервисы!
Для сервисов нужен BGP в адресном семействе VPNv4. BGP будет строится между левым и правым PE:

# Делаем на левом PE router bgp 65001 bgp router-id 1.1.1.1 neighbor 5.5.5.5 remote-as 65001 ! address-family vpnv4 unicast neighbor 5.5.5.5 activate neighbor 5.5.5.5 send-community extended exit-address-family ############################# # Делаем на правом: router bgp 65001 bgp router-id 5.5.5.5 neighbor 1.1.1.1 remote-as 65001 neighbor 1.1.1.1 update-source 5.5.5.5 ! address-family ipv4 vpn neighbor 1.1.1.1 activate exit-address-family exit
BGP поднялись на обоих PE-шках, но пока никто никому ничего не анонсирует:

А в Ipv4 AF мы и не планируем ничего анонсировать. Можно вообще сессию убить:
# == PE Left: == router bgp 65001 address-family ipv4 unicast no neighbor 5.5.5.5 activate exit-address-family # == PE Right: == router bgp 65001 address-family ipv4 unicast no neighbor 1.1.1.1 activate exit-address-family
Теперь собственно осталось добавить анонсы маршрутов из VRF-ов.
Сейчас таблицы маршрутизации в vrf-ах вот такие вот. Видны лишь локальные connected маршруты:


Доподнастроим наш BGP процесс примерно вот таким образом:
# Настраиваем PE левый # Для зелёного vrf-а: router bgp 65001 vrf vrf-Green address-family ipv4 unicast redistribute connected label vpn export auto rd vpn export 1.1.1.1:2 rt vpn both 65001:2 export vpn import vpn exit-address-family exit # и красного: router bgp 65001 vrf vrf-Red address-family ipv4 unicast redistribute connected label vpn export auto rd vpn export 1.1.1.1:3 rt vpn both 65001:3 export vpn import vpn exit-address-family exit
Не забываем следить за всеми RD\RT делами. Зачем всё это нужно, конечно, читать вот тут - https://linkmeup.gitbook.io/sdsm/11.-mpls-l3vpn/5.-faq
После зеркальной настройки с другой стороны (RT лучше сделать одинаковым для каждого vrf-а, а RD - уникальным для связки маршрутизатор+vrf), мы должны увидеть что в VPN4 AF начали отправляться и приниматься какие-то маршруты, и не просто так а отмаркированные каким-то коммунити и меточками:

И маршрутики успешно встали в таблички маршрутизации соответствющих vrf-чиков:


С точки зрения ядра мы видим маршруты в vrf-е тоже и даже написано какие меточки надо добавить где:

Эти меточки можно расшифровать так:

Ну и теперь проверим вот эту вот связанность в красном vrf-е,например:


И, если встанем где-нибудь ваершаком между P2 и P4, увидим красивое:

Ну всё, хватит с нас MPLS-а.
Оверлеи с помощью FRR. EVPN\VXLAN и немного магии.
А теперь моднейший EVPN\VXLAN с задачей сделать L2VPN. Нужно собрать вот такую топологию:

Условия три:
1) Все сетевые устройства - линукс хосты с FRR-ом.
2) Между сетевыми устройствами только IP маршрутизация - никаких VLAN-ов мы не растягиваем.
3) Надо предоставить L2 связанность между зелёными хостами, чтобы они думали что они в одном броадкаст домене как-будто.
Но мне это всё очень лень делать руками. Очень. Поэтому я прибегну к помощи обычного советского YAML-а. Да, попробую собрать всю лабу с помощью одного маленького YAML-а.
Делать я буду это с помощью проекта netlab tools - https://netlab.tools/
Как его устанавливать, пользоваться и вообще зачем он нужОн я подробно писал в своей вот этой крутой статье - https://t.me/NetArchNotes/14 . Потому лучше прочесть сначала её, чтобы понимать что дальше будет происходить - ибо иначе это реально может казаться магией.
Итак, я захожу на хост, где у меня уже установлен netlab tools + containerlab ( https://containerlab.dev/ - это "провайдер" контейнеризированных сетевых устройств для netlab), делаю там пустую папку и делаю там новый пустой файл topology.yml:
roman@netlab-test:~$ roman@netlab-test:~$ mkdir Magic_EVPN_LAB roman@netlab-test:~$ cd Magic_EVPN_LAB/ roman@netlab-test:~/Magic_EVPN_LAB$ touch topology.yml roman@netlab-test:~/Magic_EVPN_LAB$ cat topology.yml
Файл пустой, в него я вставлю вот такой yaml с попутными комментариями:
--- # Указываем containerlab в качестве провайдера виртуализации provider: clab # По умолчанию все устройства будут FRR-ы defaults.device: frr # Подключаем плагин "fabric" - эта обвязка автоматически соберёт нам Leaf-Spine топологию plugin: [ fabric ] # нам нужно два спайна и три лифа fabric.spines: 2 fabric.leafs: 3 # ASN для BGP по умолчанию будет такой bgp.as: 65000 # описываем общие параметры для групп groups: # если объектов в группе нет - создём их автоматом _auto_create: True # какие модули буду использованы на лифах # нам нужен osfp для андерлей, bgp для оверлея # EVPN конечно, сам vxlan и vlan-ы чтобы разместить в хосты в разных вланах leafs: module: [ospf,bgp,evpn,vxlan,vlan] # какие модули будут использованы на спайнах # так же указываем что спайны будут выполнять функцию route-reflector-ов для bgp spines: module: [ospf,bgp,evpn] bgp.rr: True # Сделаем четыре хоста из линуксов hosts: members: [GreenHost1, GreenHost2, RedHost1, RedHost2] device: linux # Самый тупой режим настройки vlan-ов - просто бриджуем всё что в одном влане # без всякого роутинга vlan.mode: bridge # тут мы во-первых "втыкаем" хосты в лифы # во вторых - на созданные линки вешаем VLAN-ы - Зелёный и Красный, как на схеме) vlans: Green: links: [GreenHost1-L1,GreenHost2-L3] Red: links: [RedHost1-L2, RedHost2-L3]
Всё)
Дальше просто запускаем это воооот такой командочкой - "sudo netlab up" , и смотрим на магию. Пару скринов того, что происходит:


Отрабатывает вся машинерия, которая создаёт и запускает все необходимые контейнеры (FRR как сетевые устройства + обычные alpine как хосты), собирает это всё в нужную топологию, создаёт все необходимые бриджы на хосте, пробегается по хостам ансиблом и разливает все необходимые конфиги.
Смотрим, что получилось:

Лифы и спайны с FRR-ом и хосты c линуксами. Можно посмотреть как это выглядит в графическом виде, создаём графическую топологию:

Содержимое вот:
graph { bgcolor="transparent" node [shape=box, style="rounded,filled" fontname=Verdana] edge [fontname=Verdana labelfontsize=10 labeldistance=1.5] subgraph cluster_AS_65000 { bgcolor="#e8e8e8" fontname=Verdana margin=16 label="AS 65000" "L1" [ label="L1 [frr]\n10.0.0.1/32" fillcolor="#ff9f01" margin="0.3,0.1" ] "L2" [ label="L2 [frr]\n10.0.0.2/32" fillcolor="#ff9f01" margin="0.3,0.1" ] "L3" [ label="L3 [frr]\n10.0.0.3/32" fillcolor="#ff9f01" margin="0.3,0.1" ] "S1" [ label="S1 [frr]\n10.0.0.4/32" fillcolor="#ff9f01" margin="0.3,0.1" ] "S2" [ label="S2 [frr]\n10.0.0.5/32" fillcolor="#ff9f01" margin="0.3,0.1" ] } "L1" -- "S1" [ ] "L1" -- "S2" [ ] "L2" -- "S1" [ ] "L2" -- "S2" [ ] "L3" -- "S1" [ ] "L3" -- "S2" [ ] "Magic_EVPN_7" [style=filled fillcolor="#d1bfab" fontsize=11 margin="0.3,0.1" label="172.16.0.0/24"] "GreenHost1" -- "Magic_EVPN_7" [ ] "L1" -- "Magic_EVPN_7" [ ] "Magic_EVPN_8" [style=filled fillcolor="#d1bfab" fontsize=11 margin="0.3,0.1" label="172.16.0.0/24"] "GreenHost2" -- "Magic_EVPN_8" [ ] "L3" -- "Magic_EVPN_8" [ ] "Magic_EVPN_9" [style=filled fillcolor="#d1bfab" fontsize=11 margin="0.3,0.1" label="172.16.1.0/24"] "RedHost1" -- "Magic_EVPN_9" [ ] "L2" -- "Magic_EVPN_9" [ ] "Magic_EVPN_10" [style=filled fillcolor="#d1bfab" fontsize=11 margin="0.3,0.1" label="172.16.1.0/24"] "RedHost2" -- "Magic_EVPN_10" [ ] "L3" -- "Magic_EVPN_10" [ ] }
Парсить это в голове сложно, давайте сходим например сюда -https://dreampuf.github.io/GraphvizOnline и превратим эти буковки с цифорками в картиночку:

Давайте попробуем провалиться в shell Зелёного Хоста номер 1 и попингать Зелёный Хост номер 2:

Так, в шелл то попали, но что пингать - не понятно, но зато мы теперь знаем IP нашего GreenHost1 - 172.16.0.6 и его mac-адрес - aa:c1:ab:20:fd:4b. Давайте с другой стороны зайдём:

Вуаля, пинги ходят, сосед виден, как родной. А что там на лифе 1, например?
Идём в shell, попадаем в Linux, заходим уже привычным путём в консоль FRR-а и глядим что там нетлаб нам наконфигурил (в этот раз листинг вставлю текстом, так уж и быть):
GreenHost2:/# exit roman@netlab-test:~/Magic_EVPN_LAB$ sudo netlab connect L1 Connecting to container clab-Magic_EVPN_L-L1, starting bash Use vtysh to connect to FRR daemon L1(bash)# L1(bash)# vtysh Hello, this is FRRouting (version 10.0.1_git). Copyright 1996-2005 Kunihiro Ishiguro, et al. 2025/02/26 16:30:19 [YDG3W-JND95] FD Limit set: 1048576 is stupidly large. Is this what you intended? Consider using --limit-fds also limiting size to 100000 L1# L1# L1# L1# show run Building configuration... Current configuration: ! frr version 10.0.1_git frr defaults datacenter hostname L1 no ipv6 forwarding service integrated-vtysh-config ! vrf mgmt exit-vrf ! interface eth1 description L1 -> S1 ip address 10.1.0.1/30 ip ospf area 0.0.0.0 ip ospf network point-to-point exit ! interface eth2 description L1 -> S2 ip address 10.1.0.5/30 ip ospf area 0.0.0.0 ip ospf network point-to-point exit ! interface eth3 description [Access VLAN Green] L1 -> GreenHost1 exit ! interface lo ip address 10.0.0.1/32 ip ospf area 0.0.0.0 exit ! interface vlan1000 description VLAN Green (1000) -> [GreenHost1,GreenHost2,L3] exit ! router bgp 65000 bgp router-id 10.0.0.1 no bgp default ipv4-unicast bgp bestpath as-path multipath-relax neighbor 10.0.0.4 remote-as 65000 neighbor 10.0.0.4 description S1 neighbor 10.0.0.4 update-source lo neighbor 10.0.0.5 remote-as 65000 neighbor 10.0.0.5 description S2 neighbor 10.0.0.5 update-source lo ! address-family ipv4 unicast network 10.0.0.1/32 neighbor 10.0.0.4 activate neighbor 10.0.0.4 next-hop-self neighbor 10.0.0.5 activate neighbor 10.0.0.5 next-hop-self exit-address-family ! address-family l2vpn evpn neighbor 10.0.0.4 activate neighbor 10.0.0.4 soft-reconfiguration inbound neighbor 10.0.0.5 activate neighbor 10.0.0.5 soft-reconfiguration inbound advertise-all-vni vni 101000 rd 10.0.0.1:1000 route-target import 65000:1000 route-target export 65000:1000 exit-vni advertise-svi-ip advertise ipv4 unicast exit-address-family exit ! router ospf ospf router-id 10.0.0.1 timers throttle spf 10 50 500 timers throttle lsa all 100 timers lsa min-arrival 100 exit ! end
Контейнер, куда мы провалились - это Linux с запущенным там FRR-ом. Там настроены обычные L3 линки до спайнов. До них же настроен OSFP:

OSFP нужен для того, что бы обменяться лупбеками - чтобы все лифы могли видеть друг друга и спайны. Потом строятся BGP EVPN сессии до этих самых спайнов:

Каждый лиф делает ЭТО с каждым спайном, и отдаёт ему свои локальные EVPN маршруты, получая в замен маршруты от других лифов. Именно за счёт этого, в конечном итоге, достигается связанность по L2 между хостами:

Как, почему и зачем работает EVPN\VXLAN хорошо рассказывают на курсе https://otus.ru/lessons/network_design/
Вот на всякий случай ещё конфиг FRR-а со спайна, чтобы уж наконец-то ВСЁ СТАЛО ПОНЯТНО:
L1(bash)# exit logout roman@netlab-test:~/Magic_EVPN_LAB$ sudo netlab connect S1 Connecting to container clab-Magic_EVPN_L-S1, starting bash Use vtysh to connect to FRR daemon S1(bash)# vtysh Hello, this is FRRouting (version 10.0.1_git). Copyright 1996-2005 Kunihiro Ishiguro, et al. 2025/02/26 16:43:11 [YDG3W-JND95] FD Limit set: 1048576 is stupidly large. Is this what you intended? Consider using --limit-fds also limiting size to 100000 S1# show run Building configuration... Current configuration: ! frr version 10.0.1_git frr defaults datacenter hostname S1 no ipv6 forwarding service integrated-vtysh-config ! vrf mgmt exit-vrf ! interface eth1 description S1 -> L1 ip address 10.1.0.2/30 ip ospf area 0.0.0.0 ip ospf network point-to-point exit ! interface eth2 description S1 -> L2 ip address 10.1.0.10/30 ip ospf area 0.0.0.0 ip ospf network point-to-point exit ! interface eth3 description S1 -> L3 ip address 10.1.0.18/30 ip ospf area 0.0.0.0 ip ospf network point-to-point exit ! interface lo ip address 10.0.0.4/32 ip ospf area 0.0.0.0 exit ! router bgp 65000 bgp router-id 10.0.0.4 no bgp default ipv4-unicast bgp cluster-id 10.0.0.4 bgp bestpath as-path multipath-relax neighbor 10.0.0.1 remote-as 65000 neighbor 10.0.0.1 description L1 neighbor 10.0.0.1 update-source lo neighbor 10.0.0.2 remote-as 65000 neighbor 10.0.0.2 description L2 neighbor 10.0.0.2 update-source lo neighbor 10.0.0.3 remote-as 65000 neighbor 10.0.0.3 description L3 neighbor 10.0.0.3 update-source lo neighbor 10.0.0.5 remote-as 65000 neighbor 10.0.0.5 description S2 neighbor 10.0.0.5 update-source lo ! address-family ipv4 unicast network 10.0.0.4/32 neighbor 10.0.0.1 activate neighbor 10.0.0.1 route-reflector-client neighbor 10.0.0.1 next-hop-self neighbor 10.0.0.2 activate neighbor 10.0.0.2 route-reflector-client neighbor 10.0.0.2 next-hop-self neighbor 10.0.0.3 activate neighbor 10.0.0.3 route-reflector-client neighbor 10.0.0.3 next-hop-self neighbor 10.0.0.5 activate neighbor 10.0.0.5 next-hop-self exit-address-family ! address-family l2vpn evpn neighbor 10.0.0.1 activate neighbor 10.0.0.1 route-reflector-client neighbor 10.0.0.1 soft-reconfiguration inbound neighbor 10.0.0.2 activate neighbor 10.0.0.2 route-reflector-client neighbor 10.0.0.2 soft-reconfiguration inbound neighbor 10.0.0.3 activate neighbor 10.0.0.3 route-reflector-client neighbor 10.0.0.3 soft-reconfiguration inbound neighbor 10.0.0.5 activate neighbor 10.0.0.5 soft-reconfiguration inbound advertise-all-vni advertise-svi-ip advertise ipv4 unicast exit-address-family exit ! router ospf ospf router-id 10.0.0.4 timers throttle spf 10 50 500 timers throttle lsa all 100 timers lsa min-arrival 100 exit ! end
С точки зрения датаплейна - трафик от GreeHost1 попадает на eth3 интерфейс хоста L1 (Leaf-1), где он бриджуется с помощью моста Vlan1000 с интерфейсом vxlan101000:

А вот что можно узнать про этот самый Vxlan101000:

Тут много всего, но из интересного для нас:
vxlan id - 101000
local 10.0.0.1: Локальный IP-адрес, используемый для инкапсуляции VXLAN
nolearning - обычно это указывает на то, что мы отказались от Flood&Learn подхода в пользу какого-то control plane - в нашем случае, очевидно, EVPN
Немного инфы по заполненности MAC-таблицы:

Видно, что пару маков пришло с удалённого VTEP-а - 10.0.0.3 (это наш третий лиф). Эти маки пришли в виде route type 2 маршрутов EVPN-а. Вот они (для уменьшения вывода я выключил EVPN сессию до второго спайна):

Мак "ВСЕ_НУЛИ" в выводе выше показывает, куда мы можем слать BUM-трафик. Обычно это рождается третьим типом EVPN-маршрутов, который как бэ намекает нашему роутеру куда слать BUM трафик для конкретного VNI, если что:

Ну и что бы это всё работало на уровне ядра, нужно включить поддержку vxlan как такового и поддержку энкапсуляции:
modprobe vxlan modprobe ip6_udp_tunnel modprobe udp_tunnel
В общем, вот так вот работает EVPN с VXLAN-ом на FRR :-)
Выводы
FRR - это хорошо. И ещё я пишу тут - https://t.me/NetArchNotes
