Предисловия тоже не будет. Погнали.
Давайте сделаем сеть без коммутаторов и маршрутизаторов
Вот есть у нас два хоста-клиента и есть один 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