
Говорят, хорошая документация обычно начинается с простых примеров и продолжается постепенным наращиванием сложности. Меня зовут Матвей Крапошин, я ведущий системный архитектор Холдинга Т1, и в этом материале расскажу, как OpenSDN (ex‑Tungsten Fabric) пересылает пакеты между виртуальными машинами (или контейнерами) и как установить OpenSDN vRouter Forwarder в минимальной конфигурации и наладить его работу для пересылки пакетов между двумя контейнерами.
Эта статья — своего рода продолжение первого материала «Почему мы выбрали OpenSDN и как контрибьютим в этот проект»[1]. Одна из её целей — рассказать о виртуализации и сетевых технологиях под капотом Т1 Облако, поделиться знаниями и экспертизой с техническим сообществом и способствовать развитию проекта. Напомню, что виртуальная сеть в облаке Cloud Compute компании Т1 Облако построена на основе избранных компонентов проекта Tungsten Fabric, который потом переименовали в OpenSDN. Наработками по OpenSDN пользуются не только Т1 Облако, но и другие российские и зарубежные облачные провайдеры.
Вы сможете лучше понять, как на самом нижнем уровне работает виртуальный коммутатор OpenSDN — средства для работы с программно‑определяемыми сетями. Углублённое знание принципов работы этого модуля позволит лучше понимать причины ошибок, возникающих при настройке OpenSDN (а также Contrail, OpenContrail, Tungsten Fabric), повысить эффективность диагностики возникающих неисправностей и разобраться в нюансах настройки этого средства поддержки программно‑определяемых сетей.
Содержание:
Ниже я продемонстрирую основы организации сетевой связности с использованием OpenSDN vRouter Forwarder. Вы сможете глубже понять базовые концепции компонента OpenSDN dataplane и то, как он программируется компонентами более высокого уровня, в частности, агентом vRouter, контроллером и, в конечном счёте, компонентом настройки (Config).
По сути, шаги, описанные в этой статье, воспроизводят запросы, которые vRouter Agent отправляет к vRouter Forwarder при изменении конфигурации виртуальной сети с помощью механизмов Config API или пользовательского интерфейса OpenSDN. Для повторения этого методического материала не требуется полностью устанавливать весь OpenSDN, что является нетривиальной задачей и требует очень много времени. Вместо этого достаточно установить только один модуль: OpenSDN vRouter Forwarder, который отвечает за коммутацию пакетов.
Понимание того, как компоненты более высокого уровня программируют vRouter Forwarder, поможет легче изменять конфигурацию виртуальной сети OpenSDN, а также анализировать возникающие неисправности и их причины.
Для этого руководства я намеренно выбрал самую простую конфигурацию сети. На схеме ниже (рис. I1) изображены две конечные точки со своими IP‑адресами (10.1.1.11/24 и 10.1.1.22/24), и эти точки взаимодействуют друг с другом через коммутатор, который имитируется с помощью OpenSDN vRouter Forwarder.

С точки зрения операционной системы, у нас будет два контейнера, подключённых к хостовой ОС и к vRouter Forwarder через пары veth (veth1/veth1c и veth2/veth2c), см. рис. I2.

Подготовка
Установите ОС Ubuntu 22 (например, как ВМ).
Установите Docker Engine, следуя инструкции с https://docs.docker.com/engine/install/ubuntu [2].
Обновите программное обеспечение внутри ВМ:
sudo apt update
-
Загрузите стандартный образ Ubuntu с dockerhub.io:
sudo docker pull ubuntu:jammy
-
Запустите контейнер номер 1 (он будет иметь имя cont1) и установите в нём необходимые сетевые утилиты (рис. A1):
sudo docker run --cap-add=NET_ADMIN --name cont1 -ti ubuntu:jammy bash apt update apt install iproute2 iputils-ping netcat git nano vim-tiny -y
-
Запустите контейнер номер 2 (он будет иметь имя cont2) и установите в нём необходимые сетевые утилиты (см. рис. A2):
sudo docker run --cap-add=NET_ADMIN --name cont2 -ti ubuntu:jammy bash apt update apt install iproute2 iputils-ping netcat git nano vim-tiny -y
В результате у вас должно быть запущено два контейнера Ubuntu 22 с именами cont1 и cont2 внутри операционной системы хоста.
Наконец, необходимо загрузить папки с учебными материалами из репозитория GitHub [3] в файловые системы хост‑ОС/ОС Ubuntu, контейнеры cont1 и cont2:
git clone https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial.git tut‑rep
Предполагается, что репозиторий загружается в домашний каталог пользователя хостовой ОС и в корневой каталог (/) контейнеров cont1 и cont2.



Устанавливаем vRouter Forwarder и утилиты
Теперь подробно опишу технические этапы сборки и запуска vRouter Forwarder вручную, посколькуобычно этот процесс выполняется автоматически.
Установите компилятор gcc и другие инструменты для компиляции и загрузки модуля vRouter Forwarder в хостовой ОС:
sudo apt install gcc make dkms ‑y
Установите соответствующие исходные коды ядра (5.15) и заголовочные файлы и перезагрузите хост‑ОС.
-
Загрузите из Dockerhub образ, необходимый для сборки в хостовой ОС OpenSDN vRouter Forwarder:
sudo docker pull opensdn/opensdn‑vrouter‑kernel‑build‑init
Удалите другие ядра из хостовой ОС, например, с помощью apt autoremove.
Если хостовая ОС работает внутри VirtualBox, то установите дополнения vbox.
Перезагрузите хост‑систему, чтобы загрузить установленное ядро 5.15. Если исходные коды текущей версии ядра Linux не были установлены, попробуйте установить их вручную:
sudo apt install linux‑headers‑uname ‑r
-
Скомпилируйте модуль OpenSDN vRouter Forwarder, запустив образ, загруженный на шаге 3, в хост‑ОС:
sudo docker run ‑mount type=bind,src=/usr/src,dst=/usr/src ‑mount type=bind,src=/lib/modules,dst=/lib/modules opensdn/opensdn‑vrouter‑kernel‑build‑init:latest
-
Процесс сборки и установки должен выводить на экран информацию о ходе выполнения. Файл vrouter.ko должен находиться в каталоге /lib/modules/$(uname ‑r)/updates/dkms файловой хост‑системы (см. рис. B1). Установите скомпилированный модуль vRouter Forwarder в память с помощью modprobe в хост‑системе:
sudo modprobe vrouter
Проверьте, установлен ли модуль vrouter в хост‑ОС:
lsmod | grep vrouter
-
Загрузите образ opensdn‑tools в хост‑ОС:
sudo docker pull opensdn/opensdn‑tools
-
Запустите opensdn‑tools в отдельном терминале хостовой ОС:
sudo docker run ‑privileged ‑pid host ‑net host ‑name opensdn‑tools ‑ti opensdn/opensdn‑tools:latest
-
Скопируйте папку репозитория руководства из хостовой ОС в корневую папку (/) контейнера opensdn‑tools:
tar cfz tut‑rep.tgz tut‑rep && sudo docker cp./tut‑rep.tgz opensdn‑tools:/tut‑rep.tgz && sudo docker exec ‑ti opensdn‑tools tar xfz tut‑rep.tgz
-
Запустите контейнеры cont1 и cont2 (в отдельных окнах терминала или вкладках) в хост‑ОС:
sudo docker start cont1 sudo docker start cont2
и затем запустите интерпретатор bash в отдельных терминалах:
для cont1:
sudo docker exec ‑ti cont1 bash
для cont2:
sudo docker exec ‑ti cont2 bash

Меняем конфигурацию интерфейсов контейнеров
Теперь добавим виртуальные интерфейсы для обеспечения связи между контейнерами.
По умолчанию виртуальные сетевые bridge‑интерфейсы, уже добавленные системой Docker в контейнеры, используются для внешнего взаимодействия с сетью, поэтому лучше их не изменять. Именно поэтому добавим в каждый контейнер дополнительную пару виртуальных интерфейсов, а затем подключим их к vRouter Forwarder для обеспечения передачи пакетов между контейнерами cont1 и cont2 с помощью OpenSDN. Для создания этих пар будем использовать сетевые пространства имён Linux.
Поскольку каждый контейнер работает в своём сетевом пространстве имён, мы можем создать пару виртуальных интерфейсов Linux (veth) и переместить один интерфейс из этой пары в сетевое пространство имён контейнера. Таким образом, мы установим сетевое соединение между средами контейнера и хостовой ОС.
Все изменения конфигурации хранятся в папке scripts [5] на GitHub. Скрипт make‑veth [6] создаёт новую виртуальную пару, перемещает один виртуальный интерфейс в указанный контейнер и назначает ему указанный адрес:
veth_name=$1 #veth1
vethc_name=$2 #veth1c
cont_name=$3 #cont1
vethc_ip=$4
cont_pid=`docker container inspect $cont_name --format '{{ .State.Pid }}'`
echo "veth=$veth_name,vethc=$vethc_name,cont=$cont_name,pid=$cont_pid"
ip link add $veth_name type veth peer name $vethc_name
ip link set $vethc_name netns $cont_pid
ip link set dev $veth_name up
docker exec -ti $cont_name ip link set dev $vethc_name up
docker exec -ti $cont_name ip addr add $vethc_ip dev $vethc_name
Сначала мы создаём виртуальную пару для контейнера cont1 на хостовой ОС:
sudo bash tut-rep/scripts/make-veth veth1 veth1c cont1 10.1.1.11/24
Затем мы создаём виртуальную пару для контейнера cont2:
sudo bash tut-rep/scripts/make-veth veth2 veth2c cont2 10.1.1.22/24
После этих шагов мы увидим два новых интерфейса в хостовой ОС (рис. C1) и один новый интерфейс в контейнерах cont1 и cont2 (рис. C2 и рис. C3 соответственно). Интерфейсы veth1 и veth2 находятся в хостовой ОС, а интерфейсы veth1c и veth2c — в контейнерах cont1 и cont2 соответственно.
Теперь нам нужно настроить OpenSDN vRouter Forwarder, чтобы он перехватывал пакеты из интерфейсов veth1 и veth2 и передавал их. Это делается с помощью утилиты vif из пакета OpenSDN opensdn‑tools. Её можно использовать для просмотра списка интерфейсов, подключённых к OpenSDN vRouter Forwarder, а также для подключения виртуальных или физических сетевых интерфейсов ОС хоста. Для запуска этой утилиты я подготовил специальный скрипт:
veth_name=$1 #veth1
veth_mac=00:00:5e:00:01:00
cont_name=opensdn-tools
docker exec -ti $cont_name vif --add $veth_name --mac $veth_mac --vrf 0 --type virtual --transport virtual
Предполагается, что контейнер с пакетом opensdn‑tools запущен и работает под именем opensdn‑tools. Если это так, то нужно ввести имя хостовой ОС:
sudo bash tut-rep/scripts/make-vif veth1
sudo bash tut-rep/scripts/make-vif veth2
Результат этой операции можно проверить с помощью утилиты vif внутри контейнера opensdn‑tools: vif ‑list
Утилита возвращает список подключённых к vRouter Forwarder интерфейсов, среди которых мы должны увидеть записи для veth1 и veth2, рис. C4.




Конфигурируем маршрутную информацию
OpenSDN vRouter Forwarder использует протокол Netlink для получения запросов и отправки ответов. Запросы конфигурации можно записывать в виде XML‑файлов, после чего утилита vrcli на основе Sandesh считывает XML, декодирует его содержимое, преобразует его в управляющие сообщения и отправляет их в vRouter Forwarder. Все запросы из этого руководства находятся в папке xml_reqs [7]. Vrcli может работать как с vRouter, работающим в режиме DPDK, так и с модулем ядра Linux. Для работы в модуле ядра Linux необходимо указать ключ ‑vr_kmode
при вызове утилиты.
Перед началом работы с vRouter Forwarder необходимо указать ему на инициализацию важных структур в памяти, таких как таблицы базы данных пересылки (FIB), таблицы VRF и т. д. Хотя vRouter Forwarder может работать с большими страницами, эта функциональность не используется в этом руководстве, а память инициализируем с помощью передачи пустого списка файлов, на которые она отображается. Соответствующий запрос хранится в файле set_hugepages_conf.xml [8].
Мы вызываем запрос из контейнера opensdn‑tools с помощью команды vrcli
следующим образом (в каталоге, в который был клонирован репозиторий руководства):
vrcli ‑vr_kmode ‑send_sandesh_req tut‑rep/xml_reqs/set_hugepages_conf.xml
В качестве ответа мы должны увидеть:
Running Sandesh request...
Обычно это означает, что запрос, вызвавший такое сообщение, был принят и успешно обработан.
Если от vrcli получен ответ с ошибкой, это означает, что есть проблема с выделением памяти. Ее можно устранить с помощью анализа файла /proc/buddyinfo, который показывает количество свободных блоков оперативной памяти заданного размера.
Для решения проблемы можно использовать два подхода: а) перезагрузка хостовой ОС с последующей загрузкой модуля (modprobe vrouter) и выполнение команды vrcli
с запросом set_hugepages_conf.xml или б) увеличение объёма оперативной памяти хостовой ОС (8 ГБ должно сработать практически во всех случаях).
Мы можем проверить, что последняя операция (инициализация памяти) прошла успешно, вызвав следующую команду внутри контейнера opensdn‑tools:
rt ‑dump 0 ‑family bridge
Если сообщений об ошибках нет, а вместо этого отображаются пустые таблицы маршрутизации, это означает, что память vRouter Forwarder успешно инициализирована (см. рис. D1).

Далее необходимо настроить таблицу VRF, предназначенную для хранения маршрутной информации о пакетах, проходящих между cont1 и cont2. В плоскости данных OpenSDN сетевой трафик коммутируется между конечными точками (ВМ, контейнерами и т. д.) гипервизора в соответствии с изолированными таблицами маршрутизации и пересылки, хранящимися в виде таблицы VRF с собственным идентификатором.
Каждый vRouter Forwarder может иметь до 65 536 таблиц VRF с идентификаторами от 0 до 65 535. Каждый виртуальный или физический интерфейс, подключённый к vRouter Forwarder, должен быть связан с определённой таблицей VRF.
В этом руководстве используется таблица VRF с номером 0 (0). Для создания этого элемента в vRouter Forwarder мы используем запрос, хранящийся в файле set_vrf.xml [9]. Запрос (vr_vrf_req) отправляется в vRouter Forwarder следующим образом:
vrcli ‑vr_kmode ‑send_sandesh_req tut‑rep/xml_reqs/set_vrf.xml
С помощью утилиты vrftable мы можем проверить, что таблица VRF 0 была создана:
vrftable ‑dump
Перед настройкой маршрутной информации необходимо обновить конфигурацию виртуальных интерфейсов в vRouter Forwarder. Это достигается вызовом запроса vr_interface_req, хранящегося в файлах set_vif1_ip.xml [10] и set_vif2_ip.xml [11] для контейнеров cont1 и cont2 соответственно.
Однако перед выполнением этих запросов их необходимо изменить, а именно:
поле
<vifr_idx></vifr_idx>
должно содержать фактический индекс настраиваемого виртуального интерфейса из хостовой ОС;поле
<vifr_ip></vifr_ip>
должно содержать IPv4-адрес этого интерфейса;поле
<vifr_nh_id></vifr_nh_id>
должно содержать идентификатор следующего перехода, подключённого к этому интерфейсу.
Мы берем vifr_idx
из вывода команды ip a
: это число перед двоеточием, предшествующее имени соответствующего интерфейса (veth1 или veth2) (см. рис. C1).
Значение адреса IPv4 передаётся в vr_interface_req как четырёхбайтовое целое число, поэтому мы должны преобразовать IP‑адреса из сетевой конфигурации, представленной ранее:
10.1.1.11 преобразуется в 11*256*256*256 + 1*256*256 + 1*256 + 10 или 184615178;
10.1.1.22 преобразуется в 22*256*256*256 + 1*256*256 + 1*256 + 10 или 369164554.
Что касается идентификатора следующего перехода (nexthop) (поле vifr_nh_id
), то здесь необходимо согласовать эти значения, поскольку на данном этапе настройки в vRouter Forwarder сейчас нет следующих переходов, и эти номера произвольны (то есть могут не подчиняться никаким правилам). Выберем номер следующего перехода 1 для виртуального интерфейса 1 (veth1) и номер следующего перехода 2 для виртуального интерфейса 2 (veth2).
Затем мы вызываем наши запросы в контейнере opensdn‑tools:
vrcli ‑vr_kmode ‑send_sandesh_req tut‑rep/xml_reqs/set_vif1_ip.xml
vrcli ‑vr_kmode ‑send_sandesh_req tut‑rep/xml_reqs/set_vif2_ip.xml
Если запросы принимаются OpenSDN vRouter Forwarder, то мы должны увидеть снова:
Running Sandesh request...
Затем мы можем использовать утилиту vif для проверки конфигурации нашего интерфейса в OpenSDN vRouter Forwarder (см., например, рис. D2): vif ‑list

Наши интерфейсы ссылаются на следующие переходы 1 и 2, но они отсутствуют в нашей конфигурации, и это можно проверить с помощью утилиты nh: nh ‑list
Для добавления следующего перехода в таблицу пересылки OpenSDN vRouter используются запросы vr_nexthop_req. Примеры этих запросов для следующих переходов, связанных с интерфейсами veth1 и veth2, хранятся в файлах set_cont1_br_nh.xml [12] и set_cont2_br_nh.xml [13] соответственно.
Чтобы настроить эти шаблоны для локальной конфигурации, необходимо исправить следующие поля:
<nhr_encap_oif_id></nhr_encap_oif_id>
— хранение меток интерфейсов veth1 или veth2, связанных с заданным следующим переходом;<nhr_encap></nhr_encap>
— хранение локального (контейнера или виртуальной машины) MAC‑адреса интерфейса, с которым мы связываем следующий переход.
Для метки интерфейса (ID) заменяем значение «0»:
<nhr_encap_oif_id>0</nhr_encap_oif_id>
на фактический идентификатор интерфейса veth1 или veth2 из вывода команды vif
(контейнер opensdn‑tools) (см., например, рис. D2): vif ‑list
Массив, хранящий поле nhr_encap
, содержит шесть элементов, представляющих собой байты MAC‑адреса связанного виртуального интерфейса (в нашем случае — veth1c или veth2c). В шаблонах эти элементы обозначены цифрами от 1 до 6:
<element>1</element>
<element>2</element>
<element>3</element>
<element>4</element>
<element>5</element>
<element>6</element>
Существует скрипт devmac2list [14], который упрощает извлечение этих элементов из интерфейса (для его использования репозиторий должен быть клонирован в контейнеры cont1 и cont2). Если репозиторий был клонирован в папку /tut‑rep контейнеров cont1 и cont2, то мы можем выполнить следующие команды из хостовой ОС:
sudo docker exec ‑ti cont1 bash /tut‑rep/scripts/devmac2list veth1c
sudo docker exec ‑ti cont2 bash /tut‑rep/scripts/devmac2list veth2c
Вывод этих команд должен быть аналогичен рис. D3. Обратите внимание, что здесь мы используем veth1c и veth2c вместо veth1 и veth2, поскольку нам нужны MAC‑адреса интерфейсов контейнеров, а не хостовой ОС. Кроме того, хотя мы берём значения из контейнеров cont1 и cont2, мы изменяем XML‑файлы с запросами внутри контейнера opensdn‑tools.
Полученные значения можно скопировать в запросы, хранящиеся в файлах set_cont1_br_nh_req.xml и set_cont2_br_nh_req.xml в контейнере opensdn‑tools. Затем мы запускаем эти запросы для добавления следующих узлов в vRouter Forwarder (контейнер opensdn‑tools):
vrcli ‑vr_kmode ‑send_sandesh_req tut‑rep/xml_reqs/set_cont1_br_nh.xml
vrcli ‑vr_kmode ‑send_sandesh_req tut‑rep/xml_reqs/set_cont2_br_nh.xml
Теперь мы можем проверить, что следующие переходы были добавлены, введя в контейнере opensdn‑tools: nh ‑list
Вывод команды выше должен быть аналогичен рис. D4. Из этого вывода можно убедиться, что с нашими следующими переходами (nexthop) связаны правильные интерфейсы. А также можно сравнить строки MAC‑адресов в следующих переходах 1 и 2 с MAC‑адресами veth1c и veth2c (их можно получить с помощью команды ip
внутри соответствующих контейнеров).
Многие запросы на создание или изменение vRouter Forwarder также используют различные флаги и параметры для задания режимов работы. Значения этих флагов можно найти здесь: https://github.com/OpenSDN‑io/tf‑vrouter/blob/master/utils/pylib/constants.py [15]


Хотя наши интерфейсы теперь связаны со следующими переходами (nexthop), у vRouter Forwarder всё ещё недостаточно информации для передачи пакетов между veth1c и veth2c, поскольку отсутствует связь между заголовком пакета и интерфейсом, принимающим этот пакет. Этот пробел заполняется таблицами пересылки.
Во‑первых, нам нужны записи L2-таблицы пересылки (маршруты) для обеспечения передачи Ethernet‑кадров через vRouter Forwarder. Маршрут — это, по сути, структура, содержащая префиксный адрес (MAC, IPv4 или IPv6) и ссылку на следующий переход, определяющий дальнейшие действия с пакетом, внешний заголовок которого совпадает с префиксным адресом маршрута.
Перед добавлением маршрута необходимо связать наши следующие переходы (nexthop) с метками MPLS, поскольку OpenSDN — это программно‑определяемая сеть (SDN) на основе MPLS, требующая именно такого типа адресации для связи в пределах одного широковещательного домена. Метки MPLS вводятся с помощью запросов vr_mpls_req. Пример запроса хранится в файлах set_mpls1.xml [16] и set_mpls2.xml [17]. Как видно из содержимого файла, запрос довольно прост: он просто связывает номер следующего перехода с номером метки MPLS.
Чтобы создать метки MPLS для наших следующих переходов 1 и 2, необходимо запустить запрос файла set_mpls.xml из контейнера opensdn‑tools:
vrcli ‑vr_kmode ‑send_sandesh_req tut‑rep/xml_reqs/set_mpls1.xml
Результаты выполнения команды можно проверить с помощью утилиты mpls в контейнере opensdn‑tools (см. пример на рис. D5): mpls ‑dump

Записи маршрутов L2 добавляем через запросы vr_route_req. Запрос должен содержать:
идентификатор таблицы VRF (0 в нашем примере), определённый в поле
<rtr_vrf_id></rtr_vrf_id>
;тип семейства (7, который, по сути, является значением константы AF_BRIDGE), определённый в поле
<rtr_family></rtr_family>
;метку следующего перехода (1 или 2 для этих маршрутов L2), определённую в
<rtr_nh_id></rtr_nh_id>
;и префикс, который определяется MAC‑адресом контейнера или интерфейса виртуальной машины (в этом руководстве veth1c или veth2c) и задаётся в поле
<rtr_mac></rtr_mac>
.
Примеры этого запроса, добавляющего маршруты L2 к интерфейсам veth1c и veth2c, хранятся в файлах set_cont1_br_rt.xml [18] и set_cont2_br_rt.xml [19].
Значения MAC‑адресов интерфейсов veth1c и veth2c можно получить с помощью скрипта devmac2list, выполнив следующие команды из хостовой ОС:
sudo docker exec ‑ti cont1 bash /tut‑rep/scripts/devmac2list veth1c
sudo docker exec ‑ti cont1 bash /tut‑rep/scripts/devmac2list veth2c
Вывод каждой команды необходимо подставить в поле rtr_mac
файлов set_cont1_br_rt.xml и set_cont2_br_rt.xml внутри контейнера opensdn‑tools (вместо строк <element>1</element>
...<element>6</element>
). Затем выполняем команду vrcli
внутри opensdn‑tools:
vrcli ‑vr_kmode ‑send_sandesh_req tut‑rep/xml_reqs/set_cont1_br_rt.xml
vrcli ‑vr_kmode ‑send_sandesh_req tut‑rep/xml_reqs/set_cont2_br_rt.xml
Добавленные маршруты можно проверить с помощью команды rt
внутри контейнера opensdn‑tools (здесь 0 соответствует метке таблицы VRF):
rt ‑dump 0 ‑family bridge
Вывод вышеприведённой команды должен быть аналогичен рис. D6. Хотя текущая конфигурация vRouter Forwarder позволяет передавать L2-пакеты между контейнерами cont1 и cont2, это малопригодно, поскольку обычно мы работаем с пакетами L3, и поэтому для использования ping нам нужны следующие узлы L3 и маршруты L3. Однако даже эта очень простая конфигурация демонстрирует общую организацию данных внутри vRouter Forwarder. А именно, у нас есть несколько взаимосвязанных таблиц, отвечающих за передачу пакетов между виртуальными машинами или контейнерами (см. рис. D7):
таблица VRF, определяющая список таблиц VRF с изолированной информацией о маршрутизации;
таблица интерфейсов, определяющая связь между интерфейсом (подключённым к виртуальной машине или контейнеру) с таблицей VRF и со следующим переходом;
таблица nexthops, определяющая список nexthops (следующих переходов);
таблицы L2- и L3-маршрутов, связанные с таблицей VRF, определяющей, какие следующие переходы должны применяться к пакетам в зависимости от их внешних заголовков.


На следующем этапе добавляем маршруты L3 для передачи пакетов L3 через OpenSDN vRouter Forwarder. Как и в случае с маршрутами L2, соответствующие следующие переходы (nexthop) нужно добавить заранее. Примеры запросов на добавление следующих переходов L3 хранятся в файлах set_cont1_inet_nh.xml [20] и set_cont2_inet_nh.xml [21]. Для следующих переходов L3, связанных с интерфейсами veth1c и veth2c, выбраны метки 11 и 22 (см. рис. D7). Шаблоны также требуют изменения: указываем MAC‑адреса интерфейса в поле nhr_encap
(процедура аналогична использованной ранее для следующих переходов L2) и соответствующие метки интерфейсов nhr_encap_oif_id (такие же, как для следующих переходов L2). Наконец, следующие переходы (nexthop) добавляем в контейнер opensdn‑tools с помощью команд:
vrcli ‑vr_kmode ‑send_sandesh_req tut‑rep/xml_reqs/set_cont1_inet_nh.xml
vrcli ‑vr_kmode ‑send_sandesh_req tut‑rep/xml_reqs/set_cont2_inet_nh.xml
После успешного завершения список следующих переходов должен обновиться, и это можно проверить с помощью команды nh
внутри контейнера opensdn‑tools:
nh ‑list
Приблизительный вывод команды представлен на рис. D8.

Наконец, маршруты L3 добавляем с помощью запросов, аналогичных тем, что использовали для маршрутов L2. Примеры запросов хранятся в файлах set_cont1_inet_rt.xml [22] и set_cont2_inet_rt.xml [23]. Эти запросы маршрутизации создают новые маршруты в таблице маршрутизации IPv4 inet таблицы VRF 0 для префиксов 10.1.1.11/32 и 10.1.1.22/32 в соответствии с исходной конфигурацией сети (см. рис. I1). Маршрут с префиксом 10.1.1.11/32 указывает на следующий переход 11, а маршрут с префиксом 10.1.1.22/32 указывает на следующий переход 22: это означает, что всякий раз, когда пакет с IP‑адресом назначения во внешнем заголовке, равным 10.1.1.11/32 или 10.1.1.22/32, поступает в vRouter Forwarder через интерфейсы, связанные с таблицей VRF 0, он будет перенаправлен на соответствующие следующие переходы (11 и 22 соответственно).
Маршруты добавляем в контейнер opensdn‑tools с помощью команд:
vrcli ‑vr_kmode ‑send_sandesh_req tut‑rep/xml_reqs/set_cont1_inet_rt.xml
vrcli ‑vr_kmode ‑send_sandesh_req tut‑rep/xml_reqs/set_cont2_inet_rt.xml
Полученную конфигурацию можно проверить с помощью команды rt
в контейнере opensdn‑tools:
rt ‑get 10.1.1.11/32 ‑vrf 0 ‑family inet
rt ‑get 10.1.1.22/32 ‑vrf 0 ‑family inet
Вывод команд должен быть аналогичен представленному на рис. D9.

Проверяем связность между контейнерами
Вам может показаться, что текущая конфигурация vRouter Forwarder достаточна для передачи пакетов между контейнерами cont1 и cont2. К контейнерам и vRouter Forwarder подключены два интерфейса, а к этим интерфейсам привязаны маршруты L3 и L2, позволяющие передавать пакеты между ними. Конфигурацию можно проверить с помощью утилит ping и tcpdump:
-
запустите tcpdump в хостовой ОС, наблюдая за интерфейсом 1 (veth1):
sudo tcpdump ‑i veth1 ‑vv ‑n
выполните ping применительно ко второму контейнеру (cont2) внутри первого контейнера cont1:
ping ‑n 10.1.1.22
Из выходных данных (см., например, рис. E1) видно, что ICMP‑запросов от 10.1.1.11 к 10.1.1.22 нет, поэтому нет и ответов от последнего. Однако есть много ARP‑запросов на определение MAC‑адреса для IP‑адреса 10.1.1.11. Следовательно, можно сделать вывод, что, возможно, текущая конфигурация vRouter Forwarder не передаёт ARP‑пакеты с cont1 на cont2 и обратно. Причины такого поведения можно выяснить с помощью утилиты dropstats из контейнера opensdn‑tools: dropstats ‑log 0
Эта утилита выводит в консоль последние сообщения об ошибках, созданные vRouter Forwarder при передаче пакетов. Поскольку сообщения об ошибках хранятся в vRouter Forwarder отдельно для каждого процессора, опция ‑log 0
указывает dropstats
собирать данные из всех буферов (для всех процессоров) в один поток вывода. Из примера вывода на рис. E2 видно, что vRouter Forwarder не может найти маршрут L2 для широковещательного MAC‑адреса FF:FF:FF:FF:FF:FF.


Таким образом, нам нужен L2-маршрут с соответствующим следующим переходом, который перенаправляет ARP‑пакеты, предназначенные для FF:FF:FF:FF:FF:FF, на все виртуальные интерфейсы, кроме входящего. Такие следующие переходы в OpenSDN называются составными (Composite), а такие маршруты — многоадресными (multicast).
Примеры запросов хранятся в set_mcast_br_nh.xml [24] и в set_mcast_br_rt.xml [25].
Основное отличие от предыдущего vr_nexthop_req можно увидеть в set_mcast_br_nh.xml [24], и связано оно с двумя дополнительными полями:
<nhr_nh_list></nhr_nh_list>
, список следующих переходов (последующих переходов), связанных с виртуальными интерфейсами;<nhr_label_list></nhr_label_list>
, список меток MPLS, соответствующих следующим переходам из<nhr_nh_list></nhr_nh_list>
.
Применяем команды для запуска этих запросов из контейнера opensdn‑tools:
vrcli ‑vr_kmode ‑send_sandesh_req tut‑rep/xml_reqs/set_mcast_br_nh.xml
vrcli ‑vr_kmode ‑send_sandesh_req tut‑rep/xml_reqs/set_mcast_br_rt.xml
Теперь мы можем проверить, что изменённый список следующих переходов и изменённый список маршрутов L2 (см. рис. E3) соответствуют новым записям.

Теперь команда ping
с 10.1.1.11 на 10.1.1.22 и в обратном направлении должна работать корректно (см. рис. E4).
Наконец, рекомендуется проверить UDP‑соединение с помощью утилиты nc:
запустить UDP‑сервер в cont1:
nc -4 ‑u ‑l 10.1.1.11 50 000
запустить UDP‑клиент в cont2:
nc -4 ‑u 10.1.1.11 50 000
-
попробуйте вводить сообщения в одном окне nc (в приглашении nc), чтобы увидеть их появление в другом окне nc.
Рис. E4: Пример вывода «ping» в cont1, показывающий успешное соединение ICMP между 10.1.1.11 и 10.1.1.22
Если вы обнаружили какую‑либо ошибку или неточность в тексте, пожалуйста, сообщите о них в комментариях к статье.
Основные термины, используемые в статье
Возможно, это самая важная глава, поскольку она вводит термины, используемые во всех плоскостях OpenSDN и обязательные для успешного администрирования, использования и улучшения виртуальных сетей и приложений OpenSDN.
Виртуальный интерфейс (VIF) — это пара подключённых интерфейсов, один из которых указывает на виртуальную машину или контейнер, а другой — на хостовую операционную систему, которая управляет виртуальными ресурсами (ВМ и другими). Перед использованием vRouter Forwarder необходимо подключить виртуальный интерфейс с помощью утилиты vif или запроса Sandesh.
Таблица VRF предоставляет уникальную изолированную среду для хранения таблиц маршрутизации (L3) и пересылки (L2) для коммутации пакетов между виртуальными интерфейсами, связанными с заданным идентификатором (числом). Благодаря этому принципу изоляции IP‑ или MAC‑адреса из двух разных таблиц VRF на сервере пересылки vRouter могут пересекаться.
Маршрут — это запись в таблице маршрутизации (L3) или пересылки (L2), имеющая префикс (MAC‑адрес или IP‑адрес), который однозначно идентифицирует её, и указатель на следующий переход, предназначенный для управления коммутацией пакетов в vRouter Forwarder.
Следующий переход (nexthop) определяет дальнейшее действие, выполняемое над пакетом, внешний заголовок которого совпадает с префиксным адресом маршрута, указывающего на этот следующий переход. В этом случае говорят, что пакет достигает следующего перехода.
Метка MPLS — это целое число, используемое для непосредственной идентификации следующего перехода вместо маршрута с префиксом.
Маршруты могут быть трех типов:
AF_BRIDGE: пакеты пересылаются с использованием заголовка Ethernet;
IPv4 (AF_INET): пакеты пересылаются с использованием заголовка IPv4;
IPv6 (AF_INET6): пакеты пересылаются с использованием заголовка IPv6;
Следующие переходы (nexthops) в OpenSDN dataplane могут иметь такие популярные типы (согласно обозначениям, используемым в утилите nh):
drop, который также иногда называют «discard», предназначен для отбрасывания пакетов, попадающих на следующий узел этого типа;
encap отправляет пакет, достигающий этого следующего перехода, на виртуальный интерфейс, связанный с этим следующим переходом;
tunnel отправляет пакет, достигающий этого следующего перехода, на хост с IP‑адресом нижнего уровня, указанным в следующем переходе;
composite объединяет несколько следующих переходов (Encap или Tunnel) в список для организации многоадресной коммутации (для адресации L2) или балансировки нагрузки L3;
VRF‑трансляция переносит пакет из одной таблицы VRF в другую.
Составные следующие переходы могут иметь различные подтипы:
pure encap: все компоненты составного списка следующих переходов имеют тип encap;
pure tunnel: все компоненты составного списка следующих переходов имеют тип tunnel;
mixed (смешанный тип): компоненты имеют разные типы (encap или tunnel).
Утилиты OpenSDN dataplane
Следующие инструменты OpenSDN (из образа opensdn‑tools) можно использовать для обработки данных и проверки их состояния:
vrcli отправляет запросы, указанные в XML‑файлах, в vRouter Forwarder для изменения его состояния;
vif перечисляет виртуальные и физические интерфейсы, подключённые к vRouter Forwarder;
vrftable выводит список таблиц VRF, доступных на сервере пересылки vRouter;
nh выдает список следующих переходов, созданных в модуле пересылки vRouter Forwarder;
mpls возвращает список доступных меток MPLS;
rt выводит все маршруты L3 и L2 из таблиц маршрутов vRouter Forwarder;
dropstats выводит сообщения об ошибках, возникающих при передаче пакетов в vRouter Forwarder.
Заключение
В статье поэтапно показано, как настраивать модуль OpenSDN vRouter Forwader для пересылки пакетов между виртуальными интерфейсами и привязанными к ним контейнерами или виртуальными машинами. В «нормальной» базовой комплектации вся эта работа выполняется автоматически модулем OpenSDN vRouter Agent, входящим в компонент виртуальный коммутатор‑маршрутизатор. Тем не менее, при настройке виртуальных сетей с помощью OpenSDN понимание как разработчиком, так и сетевым инженером принципов работы нижнего уровня (плоскости данных) этой программы является в определённом смысле базовым, помогая при локализации проблем и связывании вносимых изменений на верхнем уровне с состоянием плоскости данных.
В следующей статье покажу, как создавать потоки в OpenSDN vRouter Forwarder и управлять ими. Объясню и продемонстрирую базовые возможности пересылки пакетов на основе потоков в OpenSDN (flow based forwarding).
Автор благодарит коллег: Елену Зизганову — за идею создания утилиты vrcli для ручного манипулирования состоянием модулем пересылки OpenSDN vRouter Forwarder; Елизавету Титаренко — за помощь при подготовке материала к публикации.
Список источников
Источники
[1] Habr.com: Почему мы выбрали OpenSDN и как контрибьютим в этот проект. URL: https://habr.com/ru/companies/T1Holding/articles/885942/ (дата обращения: 19.08.2025)
[2] Docker.com: Install Docker Engine on Ubuntu. URL: https://docs.docker.com/engine/install/ubuntu/ (дата обращения: 19.08.2025)
[3] Github.com: OpenSDN forwarder basic tutorial. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial (дата обращения: 19.08.2025)
[4] Ubuntuhandbook.org: How to Install Original GA Kernel 5.15 in Ubuntu 22.04. URL: https://ubuntuhandbook.org/index.php/2023/11/install‑ga‑kernel-5-15-ubuntu-22-04/ (дата обращения: 19.08.2025)
[5] Github.com: OpenSDN forwarder basic tutorial / scripts. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/tree/main/scripts (дата обращения: 19.08.2025)
[6] Github.com: OpenSDN forwarder basic tutorial / scripts / make‑veth. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/scripts/make‑veth (дата обращения: 19.08.2025)
[7] Github.com: OpenSDN forwarder basic tutorial / xml_reqs. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/tree/main/xml_reqs (дата обращения: 19.08.2025)
[8] Github.com: OpenSDN forwarder basic tutorial / xml_reqs / set_hugepages_conf.xml. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/xml_reqs/set_hugepages_conf.xml (дата обращения: 19.08.2025)
[9] Github.com: OpenSDN forwarder basic tutorial / xml_reqs / set_vrf.xml. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/xml_reqs/set_vrf.xml (дата обращения: 22.08.2025)
[10] Github.com: OpenSDN forwarder basic tutorial / xml_reqs / set_vif1_ip.xml. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/xml_reqs/set_vif1_ip.xml (дата обращения: 22.08.2025)
[11] Github.com: OpenSDN forwarder basic tutorial / xml_reqs / set_vif2_ip.xml. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/xml_reqs/set_vif2_ip.xml (дата обращения: 22.08.2025)
[12] Github.com: OpenSDN forwarder basic tutorial / xml_reqs / set_cont1_br_nh.xml. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/xml_reqs/set_cont1_br_nh.xml (дата обращения: 22.08.2025)
[13] Github.com: OpenSDN forwarder basic tutorial / xml_reqs / set_cont2_br_nh.xml. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/xml_reqs/set_cont2_br_nh.xml (дата обращения: 22.08.2025)
[14] Github.com: OpenSDN forwarder basic tutorial /scripts / devmac2list. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/scripts/devmac2list (дата обращения: 22.08.2025)
[15] Github.com: OpenSDN‑io/tf‑vrouter /utils /pylib /constants.py. URL: https://github.com/OpenSDN‑io/tf‑vrouter/blob/master/utils/pylib/constants.py (дата обращения: 22.08.2025)
[16] Github.com: OpenSDN forwarder basic tutorial /xml_reqs /set_mpls1.xml. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/xml_reqs/set_mpls1.xml (дата обращения: 22.08.2025)
[17] Github.com: OpenSDN forwarder basic tutorial /xml_reqs/set_mpls2.xml. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/xml_reqs/set_mpls2.xml (дата обращения: 22.08.2025)
[18] Github.com: OpenSDN forwarder basic tutorial /xml_reqs/set_cont1_br_rt.xml. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/xml_reqs/set_cont1_br_rt.xml (дата обращения: 22.08.2025)
[19] Github.com: OpenSDN forwarder basic tutorial / xml_reqs/ set_cont2_br_rt.xml. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/xml_reqs/set_cont2_br_rt.xml (дата обращения: 22.08.2025)
[20] Github.com: OpenSDN forwarder basic tutorial / xml_reqs /set_cont1_inet_nh.xml. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/xml_reqs/set_cont1_inet_nh.xml (дата обращения: 22.08.2025)
[21] Github.com: OpenSDN forwarder basic tutorial / xml_reqs / set_cont2_inet_nh.xml. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/xml_reqs/set_cont2_inet_nh.xml (дата обращения: 22.08.2025)
[22] Github.com: OpenSDN forwarder basic tutorial / xml_reqs / set_cont1_inet_rt.xml. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/xml_reqs/set_cont1_inet_rt.xml (дата обращения: 22.08.2025)
[23] Github.com: OpenSDN forwarder basic tutorial / xml_reqs / set_cont2_inet_rt.xml. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/xml_reqs/set_cont2_inet_rt.xml (дата обращения: 22.08.2025)
[24] Github.com: OpenSDN forwarder basic tutorial / xml_reqs / set_mcast_br_nh.xml. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/xml_reqs/set_mcast_br_nh.xml (дата обращения: 22.08.2025)
[25] Github.com: OpenSDN forwarder basic tutorial / xml_reqs/ set_mcast_br_rt.xml. URL: https://github.com/mkraposhin/opensdn‑forwarder‑basic‑tutorial/blob/main/xml_reqs/set_mcast_br_rt.xml (дата обращения: 22.08.2025)