Мне тут подкинули задачку: оценить возможности карточек Mellanox ConnectX-6. Эти карты относятся к классу умных (SmartNIC), поэтому могут не просто считать контрольные суммы и сегментировать пакеты, им по силам трансляция IP-адресов (NAT), L4-балансировка и L3-маршрутизация. Обычно эти функции выполняются в ядре Linux на центральном процессоре, но в теории с помощью умной карты их можно перенести в железо, высвободив вычислительные ресурсы CPU.

Я решил сравнить производительность двух сред (процессор общего назначения и специализированный ASIC) и узнать насколько можно ускорить работу сетевого стека. В этой статье сразу расскажу, чего намерил, подробно опишу свои злоключения по сборке стенда, ну а начну с короткого обзора умных карт Mellanox ConnectX. Надеюсь, материал поможет коллегам-архитекторам трезво оценить возможности карты в своих проектах, а инженерам-сетевикам подобрать рабочие конфигурации, обойдя набитые мной шишки.

Ну и кто знает, может кто-то из вас подскажет мне, почему кое-что не взлетело.

Long story short:

DOCA показала ускорение в 16 раз для UDP и в 10 раз для TCP относительно system.

TCP-балансировка: ускорение в 3,5 раза.

UDP NAT и UDP-балансировка: корректной работы в этой конфигурации добиться не удалось.

Результаты относятся к ConnectX-6 Lx (25 Гбит/с) и описанным ниже условиям тестирования. Сначала стенд, затем — подробную установку и конфигурацию для воспроизведения.

Что именно я сравнивал: system и DOCA

Отличительной чертой умных карт от Mellanox является наличие множества специализированных модулей для обработки трафика: шифрование/дешифрация, модуль RDMA-транспорта и модуль обработки пакетов (Accelerated Switching and Packet Processing — ASAP2).  Меня больше всего привлек модуль ASAP2, потому что он может инкапсулировать/декапсулировать VXLAN/Geneve туннели, тегировать VLAN-трафик, отслеживать соединения (conntrack) и содержит аппаратный match-action конвейер. Именно этот модуль позволяет развернуть нужные мне сетевые сущности (NAT, балансировщик и маршрутизатор) прямо на сетевой карте.

Для управления этим аппаратным комплексом компания-производитель создала отдельный фреймворк DOCA. Он состоит из драйверов, библиотек и готовых сервисов. Чтобы сконфигурировать аппаратный конвейер ASAP2 для обработки трафика, можно воспользоваться библиотекой DOCA Flow и написать свой собственный конвейер обработки. Или поступить как я — воспользоваться готовым сервисом OVS-DOCA.

OVS-DOCA — это полнофункциональный Open vSwitch, в который добавлен новый тракт данных (datapath) — DOCA, обрабатывающий пакеты на самой карточке. Сервис также поддерживает и классические тракты:

  • netdev (он же DPDK) — обрабатывает пакеты в пространстве пользователя;

  • system — обрабатывает пакеты в пространстве ядра Linux.

Наглядно: где обрабатываются пакеты, при выборе разных трактов данных в Open vSwitch
Наглядно: где обрабатываются пакеты, при выборе разных трактов данных в Open vSwitch

Mellanox считает DPDK-тракт устаревшим, вместо него официально рекомендуют использовать DOCA. Это только усилило мой интерес: захотелось на практике проверить, как логический маршрутизатор, NAT и балансировщик работают под нагрузкой на базе этой технологии.

Сразу оговорюсь, что в этой работе я сравнивал только system и DOCA. DPDK буду исследовать в следующий раз, заодно и узнаем: такой ли уж он устаревший.

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

Стенд: серверы, сети и финальная схема

Для тестирования у меня было три сервера:

  1. Управляющий — на нем были запущены северная и южная OVN базы.

  2. Устройство под тестированием (DUT он же Device under test) — на нем был запущен Open vSwitch сконфигурированными логическими сетевыми функциями (NAT, балансировщик и маршрутизатор),

  3. Генератор пакетов — отдельный сервер с запущенным Trex. Он эмулировал облако пользователей и облако серверов обработки.

Спецификация у всех серверов одинаковая:

  • CPU: 2 processors x 16 cores x 2 threads;

  • RAM: 768GB;

  • Сетевая карта: Mellanox Technologies MT2894 Family [ConnectX-6 Lx], firmware-version: 26.47.1026, Speed: 25000Mb/s;

  • ОС: SberLinux 9.4;

  • Ядро Linux: 5.14.0-611.47.1.sl9_7^2.x86_64.

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

  • External — внешняя сеть. Тут трафик от пользователя идет к моим сервисам.

  • Overlay — наложенная сеть. Тут трафик от моих сервисов идет к бэкенд серверам.

  • Control — сеть управления. Трафик идет между базами OVN и Open vSwitch.

Выкрутился я тем, что реализовал три сети с помощью VLAN тэгов, получились:

  • VLAN 10 — сеть управления.

  • VLAN 20 — внешняя сеть.

  • VLAN 30 — наложенная сеть.

Финальная схема стенда: на генераторе работает OVS, который добавляет и снимает VLAN/Geneve-заголовки. На DUT используются tap-порты, а Trex на генераторе подключен через VF.

К такой схеме тестового стенда я пришел в итоге
К такой схеме тестового стенда я пришел в итоге

Для тестирования Trex требует два интерфейса: один для сети клиентов (генерация трафика с HTTP-запросами), второй интерфейс для сети бэкенд-серверов (обработка клиентских запросов). Между этими двумя сетями ставится сетевая функция, которую надо протестировать, при этом сетевая функция получает трафик с разнообразными src/dst IP. Это позволяет максимально реалистично протестировать логические сетевые сущности. В моей схеме, трафик из пользовательской сети до DUT идет по VLAN 20, а от DUT до сети бэкенд серверов через VLAN 30 + Geneve. Добавление VLAN и Geneve заголовков лежало полностью на плечах OVS. Trex генерировал чистый IP-трафик.

Выглядит избыточно? Штош, сейчас поясню...

Почему стенд устроен именно так

It's a long story, guys

Как я пытался подружить Trex с Geneve

В начале план был надежен как швейцарские часы:

  1. Генератор с одного интерфейса отправляет HTTP-запросы, завернутые в VLAN 20.

  2. Запрос попадает на интерфейс внешней сети DUT, затем в Open vSwitch, где выполняется сетевая функция (трансляция, маршрутизация или балансировка).

  3. После трансформации пакет инкапсулируется в Geneve, помещается в VLAN 20 и отправляется через второй интерфейс DUT-сервера к генератору.

  4. Трафик попадает на второй интерфейс Trex.

  5. Trex снимает 20 VLAN-тег и декапсулирует Geneve.

  6. Trex эмулирует web-сервер и отвечает на HTTP-запрос.

  7. Наконец, пакет летит в обратную сторону по тому же пути.

Выглядеть все это должно было изящно и незамысловато:

Но в процессе развертывания стенда выяснилось, что у Trex нет готовых профилей для работы с Geneve. А мне нужен был Geneve поверх VLAN, что усложняло задачу. Я прекрасно понимал, что на сервере установлены умные карты, и такую задачу, как добавление заголовков VLAN+GENEVE, можно организовать прямо на них. Я взял Open vSwitch из пакета DOCA и сконфигурировал два интерфейса для Trex. После этого от Trex требовалось только генерация IP-трафика, а добавление VLAN и Geneve заголовков легло на Open vSwitch. Поэтому после манипуляций схема вышла уже следующей:

На сервер генератора добавлен Open vSwitch и сеть управления
На сервер генератора добавлен Open vSwitch и сеть управления

Можно было, конечно, и без наложенной сети (overlay) организовать тестирование, но очень уж хотелось поставить карточку в максимально неудобное положение и проверить ее возможности по работе с Geneve. Я приступил к реализации схемы на OVS.

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

Больше всего меня беспокоило то, что физический интерфейс карточки нужно было полностью отдать в DOCA-OVS. А это userspace-приложение, которое забирает весь трафик из карточки, минуя ядро Linux. При этом на хосте был еще один потребитель, ожидавший пакеты с того же физического интерфейса — ovn-controller. Ему требовалось соединиться с базами OVN и считать конфигурацию через сеть управления (VLAN 10).

Другими словами, у Linux должен был быть сетевой интерфейс с назначенным IP-адресом из этой сети, при этом все приходящие на него данные должны заворачиваться в VLAN 10. Тогда ovn-controller сможет открыть обычный Linux-socket и работать с ним в стандартном режиме.

Короче: сетевую карту нужно было использовать и в ядре Linux, и в userspace-приложении. Но как это сделать?

Первое, что пришло в голову, — создать на карте два виртуальных интерфейса (VF) и поручить самой карте фильтровать пакеты по VLAN. При этом тегированный трафик будет уходить в ядро Linux, а весь остальной в OVS.

Для этого в Open vSwitch пришлось создать два моста: underlay и br-int. Мост underlay — для физической сети и br-int для сетевой логики. Оба они управляются с помощью Openflow-правил с той лишь разницей, что в underlay всегда прописывается общее правило: в секции match прописанно «все пакеты», в секции action — NORMAL. Это правило делает мост традиционным с заучиванием всех MAC-адресов. Мост br-int тоже управляется Openflow-правилами, но правила он получает из ovn-controller и их там могут быть тысячи, в зависимости от реализуемой логики.

В мост underlay я добавил следующие интерфейсы:

  • Физический интерфейс (ens3f0np0). Это обязательный шаг, без него Open vSwitch не будет сгружать свои правила на карточку. В опции я поставил trunk=10,20,30, чтобы он пропускал только тегированный трафик с заданными VLANами.

  • Представителя (representor) виртуальной функции 0 (VF 0 REP) подсоединил в мост с тэгом 10.

  • Представителя виртуальной функции 1 (VF 1 REP) подсоединил в мост с тэгом 30.

Что такое представитель и зачем он нужен, я расскажу чуть позже, а сейчас продолжим конфигурировать сервер. Назначаем IP-адреса VF 0 и VF 1 интерфейсам: 10.30.0.3/24 и 10.10.0.3/24 соответственно.

Конфигурация Open vSwitch #1
Конфигурация Open vSwitch #1

С такой конфигурацией, весь трафик с 10 и 30 VLAN-тэгами (control and overlay networks), пошел в ядро Linux, а весь остальной трафик остался на мосте underlay. При этом про VLAN-тэги знала только сетевая карточка, а Linux работал с виртуальными функциями как с обычными интерфейсами. Трафик с 20 VLANом оставался в Open vSwitch и далее обрабатывался c помощью Openflow правил на br-int.

Почему на DUT — internal-порты, а на генераторе — VF

Схема получилась рабочая, но избыточная. В процессе исследований я нашел еще один способ вывода трафика из userspace в ядро Linux. Для этого не надо использовать VF — можно просто с помощью ovs-vsctl add-port добавить порт в мост и поставить этому порту тип internal. При добавлении internal-порта Open vSwitch создает в Linux обычный tap-интерфейс с таким же названием, как и наш новый порт. Мне осталось только перевести этот порт в состояние up и назначить IP-адрес.

Потом я проверил, какие flow были установлены в карточку как offloaded. Обнаружил там и VLAN-теги c tap-интерфейсов, и даже правила Geneve-туннелирования. Сравнил скорость работы с предыдущей реализацией на SR-IOV и был приятно удивлен: на tap-интерфейсах Open vSwitch работает на 7%  быстрее. В итоге я поднял DUT на tap’ах, а генератор на SR-IOV (так как на генераторе был потребитель трафика Trex и ему лучше проводить трафик по аппаратным интерфейсам). В DUT пользовательский трафик не покидал карточку, поэтому ему подошли tap-интерфейсы. Таким образом я и пришел к той схеме стенда, что обозначил в самом начале.

А теперь детально расскажу, как я ее настраивал.

Настройка: через тернии к DOCA

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

Преодолеваем особенности национальных дистрибутивов

Так как SberLinux использует RPM, для установки DOCA я выбрал пакеты семейства rhel.

Скачиваем пакет DOCA и устанавливаем его

wget https://www.mellanox.com/downloads/DOCA/DOCA_v3.3.0/host/doca-host-3.3.0-088000_26.01_rhel9.x86_64.rpm

sudo rpm -Uvh doca-host-3.3.0-088000_26.01_rhel9.x86_64.rpm

Тут у меня возник вопрос: а причем тут host в названии doca? Оказывается, DOCA выпускается в двух вариантах:

  • host — для x86 процессора,

  • bluefield bundle — для arm комплекса на Bluefield карточке.

Приятно то, что кодовая база для ConnectX и Bluefield одинаковая. Немного поковыряв по верхам данный вопрос, я пришел к выводу, что ConnectX — это, по сути, урезанный Bluefield без ARM-части. Поправьте, если ошибаюсь, о разнице между ConnectX (SmartNic) и Bluefield(DPU) читал здесь и здесь.

В системе не хватило пакета jsoncpp. Поставил:

sudo yum install jsoncpp

Далее команды из руководства:

sudo yum makecache
sudo yum install -y doca-openvswitch
sudo yum install -y doca-networking
sudo yum install -y doca-extra
sudo yum install kernel
sudo reboot

Теперь компилируем модули DOCA для нашего ядра.

sudo /opt/mellanox/doca/tools/doca-kernel-support

И… И тут на, казалось бы, банальном шаге, я застрял на два дня и много ИИ-токенов.

О чем может говорить сообщение Illegal char '^'? Безобидно ли оно, или сборка не удалась? Увы, оказывается, такой символ в названии версии ядра ломает обновление модулей ядра на rpmbuild.

Для собратьев по несчастью фикс под катом.

Чтобы исправить это, надо поменять одну строку в /opt/mellanox/doca/tools/doca-kernel-support файле на 173 линии:

vi  /opt/mellanox/doca/tools/doca-kernel-support +173

и меняем строкуKVERS_PACK = " ${KVERS //[ _- ]/ . } "

на KVERS_PACK ="${KVERS//[\^_-]/. }"

Следующая попытка компиляции модулей ядра завершилась удачно. В конце скрипт проинформировал меня какие команды надо дальше выполнять и… Я промучался с исправлением злополучного символа еще и в названиях репозиториев:

[doca-kernel-5.14.0-611.47.1.sl9_7.2.x86_64]
name = DOCA kernel (kernel 5.14.0-611.47.1.sl9_7.2.x86_64)
baseurl = file:///usr/share/doca-host-26.01-1.0.0.0/Modules/5.14.0-611.47.1.sl9_7^2.x86_64
enabled = 1
gpgcheck = 0 

Выполнил еще раз

dnf makecache

На этот раз без ошибок.

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

dnf install doca-ofed-userspace
dnf install --disablerepo=doca doca-kernel-5.14.0.611.47.1.sl9.7.2.x86.64  

Теперь загружаем драйвера и перезапускаем MST согласно руководству с сайта производителя.

sudo /etc/init.d/openibd restart
sudo mst restart

Сервис, конечно же, упал с ошибкой, но на этот раз система явно попросила обновить firmware карточек.

Обновляем firmware

Чтобы свежая DOCA запустилась, нужно обновить прошивку карты. Стоит учитывать, что карточки одной модели (например, ConnectX-6 LX) могут иметь несколько линеек, и у каждой своя прошивка. Чтобы найти подходящую, нужно узнать PSID карточки с помощью ethtool.

$ ethtool -i ens3np0f0

Ищем запись вида LNV0000000XXX (это OEM карточка для Lenovo) или MT_0000000XXX. Firmware для Lenovo лежит здесь, для остальных здесь. Больше деталей в инструкциии.

У меня была карточка Lenovo, так что я скачал firmware, распаковал его и запустил:

$ ./mlxfwmanager_LES_25B_DOCA_3.2.0_build3

Приложение само нашло все сетевые карты и обновило их. Теперь можно попробовать запустить сервисы еще раз:

$ sudo /etc/init.d/openibd restart

$ sudo mst restart

Обновляем аргументы ядра

DOCA-тракт — это модифицированный DPDK, поэтому он также требует hugepages и изолированные ядра. Я выделил 32 ГБ hugepages и ядра со второго по 15-е включительно (вместе с их siblings). Ядра изолировал с избытком, чтобы иметь запас мощности CPU. В тестах я использовал меньше ядер, но у меня была возможность поменять настройки Open vSwitch и Trex, чтобы они воспользовались этими ядер. Siblings изолировать надо обязательно, без этого производительность у меня падала на 15%.

# выделяем 32 GB hugepages
sudo grubby --update-kernel=ALL --args='intel_iommu=on iommu=pt default_hugepagesz=1G hugepagesz=1G hugepages=32 hugepagesz=2M hugepages=0'
# изолируем 2-15,34-47 CPUs 
sudo grubby --update-kernel=ALL --args="nohz_full=2-15,34-47 rcu_nocbs=2-15,34-47"
sudo reboot

Запускаем Open vSwitch и ovn

Конфигурировать логические сетевые сущности в Open vSwitch можно двумя способами. Можно вручную настроить сетевую логику правилами OpenFlow, либо поднять OVN и доверить эту задачу ему. OVN уже содержит такие сущности, как NAT, балансировщик и маршрутизатор. Я выбрал второй вариант, и тут выяснилось, что OVN не входит в пакет поставки DOCA. OVN пришлось собрать вручную.

Для запуска OVS я выполнил:

$ sudo /usr/share/openvswitch/scripts/ovs-ctl start --system-id=random

Для сборки OVN нужны исходники OVS. Их можно найти в хранилище DOCA, но они лежат только в папке для  платформы Ubuntu, а вот в папке для RHEL исходников нет. Скачиваем нужную версию OVN и собираем его согласно официальной инструкции.

# устанавливаем компилятор и зависимости прямо на сервере где я собрался запускать OVN.
sudo dnf install -y gcc gcc-c++ make autoconf automake libtool openssl-devel libcap-ng-devel unbound-devel python3-devel python3-pip python3-setuptools
# Не уверен, что libatomic нужен всем платформам, но SberLinux его запросил.
sudo dnf install libatomic
# создаем папку для исходников
mkdir /opt/git
cd /opt/git

Для постройки OVN я использовал официальную версию OVS из github, при этом подобрал максимально близкую версию к DOCA-OVS. Исходники DOCA-OVS, которые лежат в папке для платформы Ubuntu на сайте производителя, я нашел слишком поздно, когда все было построено.

# clone ovn and ovs
git clone https://github.com/ovn-org/ovn.git
git clone https://github.com/openvswitch/ovs.git
# Собираем ovs. Устанавливать его не надо, т.к. уже стоит OVS-DOCA. Нужны только бинарники для компиляции OVN.
cd ovs
git checkout v3.3.8
./boot.sh
mkdir _build
cd _build
../configure
../configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var
make
# компилируем и устанавливаем OVN
cd /opt/git/ovn/
git checkout v24.03.8
./boot.sh
./configure --prefix=/usr --with-ovs-source=/opt/git/ovs --with-ovs-build=/opt/git/ovs/_build
make
sudo make install
# Запускаем контролер на генераторе и на DUT
/usr/share/ovn/scripts/ovn-ctl start_controller
# Запускаем северную и южную базу и northd на control node. 
OVN_LOGDIR="/var/run/ovn" OVN_RUNDIR="/var/run/ovn" OVN_DBDIR="/var/run/ovn" sudo -E /usr/share/ovn/scripts/ovn-ctl --db-sb-create-insecure-remote=yes --db-nb-schema=/opt/git/ovn/ovn/ovn-nb.ovsschema --db-sb-schema=/opt/git/ovn/ovn/ovn-sb.ovsschema restart_northd

Настраиваем eswitch и виртуальные функции (VF) на генераторе

Чтобы задействовать аппаратное ускорение, карту нужно перевести в режим switchdev. В этом режиме появляется возможность выполнять правила OVS на карте. Например, можно сконфигурировать SR-IOV устройства, чтобы они получали трафик только с определенным VLAN.

В Mellanox подумали и о том, что SR-IOV устройства могут быть переданы в виртуальную машину или новый network namespace в Linux. В этом случае устройство исчезает из корневого пространства сетевых устройств и Open vSwitch может потерять доступ к нему. Для этого каждое SR-IOV устройство имеет два интерфейса в Linux:

  • Представитель (он же representor) — точка конфигурации, основной трафик через него не ходит, его можно добавить в OVS.

  • Непосредственно SR-IOV устройство (оно же Virtual Function) — виртуальное сетевое устройство, через которое ходит трафик.

На схеме ниже пример конфигурации, где одна VF отдана виртуальной машине, а другая просто имеет IP-адрес на хосте и доступна всем приложениям хоста. При этом на хост трафик приходит из 10 VLAN, а в виртуальную машину пакеты попадают по правилам openflow в br-int. Вся эта конфигурация скрыта от потребителей интерфейсов — она реализована внутри OVS и внутри карточки. Для приложений это обычное SR-IOV устройство.

Начинаем настройку. Чтобы перевести устройство в режим switchdev, нужно, чтобы не было активных SR-IOV устройств, иначе система будет ругаться на то, что устройство занято.

echo 0 > /sys/class/net/ens3f0np0/device/sriov_numvfs

Потом находим адрес устройства на PCI-шине.

ip -d link show ens3f0np0

Адрес устройства выглядит вот так ‘parentdev 0000:32:00.0’.

Теперь можно поменять режим карточки.

devlink dev eswitch set pci/0000:32:00.0 mode switchdev

После этого я поставил нужное мне количество SR-IOV устройств.

echo 8 > /sys/class/net/ens3f0np0/device/sriov_numvfs

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

ip l set ens3f0np0 vf 0 mac 50:54:00:00:00:06
ip l set ens3f0np0 vf 1 mac b2:23:54:c4:4c:cf 

Второй мак хранится на самом SR-IOV интерфейсе и настраивается так:

ip l set ens3f0v0 address 50:54:00:00:00:06
ip l set ens3f0v1 address b2:23:54:c4:4c:cf 

MAC на физической функции использует сам eswitch для коммутации пакетов. Маки на SR-IOV устройствах зачитывает Trex и генерирует пакеты с таким SRC. Чтобы это выяснить, у меня ушла где-то неделя, причем то, что они отличаются, я сразу увидел в выводе команды ip link, но не сразу это принял.

Далее перевел устройства в состояние UP:

ip l set ens3f0v0 up
ip l set ens3f0v1 up

Но даже после установки двух MAC система периодически переставала работать. И это была не единственная проблема: я проверил логи dmesg и ovs-vswitchd.log. и обнаружил, что с периодичностью в одну минуту повторяются сообщения:

# dmesg -T:
[Sat Jul 14 18:26:30 2026] mlx5_core 0000:32:00.0 mlx5_2: Port: 0 Link DOWN
[Sat Jul 14 18:26:30 2026] mlx5_core 0000:32:00.0 mlx5_2: Port: 0 Link ACTIVE
[Sat Jul 14 18:26:30 2026] mlx5_core 0000:32:00.1 ens3f0v0: Link up
[Sat Jul 14 18:26:30 2026] mlx5_core 0000:32:00.2 ens3f0v1: Link down
[Sat Jul 14 18:26:30 2026] mlx5_core 0000:32:00.0 mlx5_2: Port: 1 Link DOWN
[Sat Jul 14 18:26:30 2026] mlx5_core 0000:32:00.0 mlx5_2: Port: 1 Link ACTIVE

# cat ovs-vswitchd.log 
2026-07-14T10:12:45.425Z|00025|backtrace(pmd-c11/id:7)|ERR|lib/vlog.c:1297 backtrace:
ovs-vswitchd(log_backtrace_at+0x52) [0x56452690b952]
ovs-vswitchd(vlog_abort_valist+0x27) [0x564526812037]
ovs-vswitchd(+0xaea0fa) [0x5645268120fa]
ovs-vswitchd(ovs_assert_failure+0x3b) [0x564526802b4b]
ovs-vswitchd(+0xb47f7a) [0x56452686ff7a]
ovs-vswitchd(+0xb583ba) [0x5645268803ba]
ovs-vswitchd(+0xb482a2) [0x5645268702a2]
ovs-vswitchd(+0xb4e6f9) [0x5645268766f9]
ovs-vswitchd(+0xa8dcff) [0x5645267b5cff]
/lib64/libc.so.6(+0x8b2ea) [0x7fb92cc8b2ea]
/lib64/libc.so.6(+0x1103d0) [0x7fb92cd103d0]
2026-07-14T10:12:45.425Z|00026|util(pmd-c11/id:7)|EMER|lib/dpif-doca.c:5521: assertion pmd->n_output_batches > 0 failed in dp_doca_pmd_flush_output_on_port()
2026-07-14T10:12:46.535Z|00030|vlog(monitor)|INFO|opened log file /var/log/openvswitch/ovs-vswitchd.log
2026-06-24T10:12:46.535Z|00031|daemon_unix(monitor)|ERR|10 crashes: pid 9866 died, killed (Aborted), core dumped, restarting

Я уже порядком подустал и сгрузил в логи в нашего внутреннего ИИ-агента. Одной из его гипотез было, что всему виной конфликт userspace-драйверов и NetworkManager’a. Значит если перестать отслеживать эти интерфейсы, проблема должна решиться?

vi /etc/NetworkManager/conf.d/99-disable-vf.conf

добавляем эти строки:

"""

[keyfile]

unmanaged-devices=interface-name:ens3f0*

"""

Перезапускаем менеджер.

systemctl restart NetworkManager

Проверяем, что устройства находятся в unmanaged состоянии — nmcli d.

Пример правильного устройства, с которым все заработало:

ens3f0npf0vf0   ethernet   unmanaged

Настраиваем OVS на генераторе

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

  • underlay — сюда я добавил все интерфейсы с vlan-тэгом;

  • br-int — интеграционный мост.

Чтобы использовать новый тракт DOCA, во время создания мостов я его явно указал datapath_type=doca.

ovs-vsctl add-br br-int
ovs-vsctl  set Bridge br-int datapath_type=doca
ovs-vsctl add-br underlay
ovs-vsctl set Bridge underlay datapath_type=doca -- set bridge underlay fail-mode=standalone
ovs-vsctl set open . external-ids:ovn-bridge=br-int

ovs-vsctl set Open_vSwitch . other_config:default-datapath-type=doca 
ovs-vsctl set open . other_config:hw-offload-ct-size=1M
ovs-vsctl --no-wait set Open_vSwitch . other_config:doca-init=true

Чтобы вся наша сетевая логика из OVS переехала в карту, указываем hw-offload=true.

ovs-vsctl set Open_vSwitch . other_config:hw-offload=true

Подсоединяемся к OVN базам, замените 10.10.0.4 на mgt ip control хоста.

ovs-vsctl set open . external_ids:ovn-remote="tcp:10.10.0.4:6642"
ovs-vsctl set open . external_ids:ovn-nb="tcp:10.10.0.4:6641"
# 10.30.0.3 это ip генератора в overlay сети (vlan 30)
ovs-vsctl set open . external_ids:ovn-encap-ip="10.30.0.3"
ovs-vsctl set open . external_ids:ovn-encap-type=geneve
# сопоставляем физическую сеть и мост
ovs-vsctl set open . external_ids:ovn-bridge-mappings="physnet1:underlay"

В underlay-мост я воткнул:

  1. Физическую функцию (ens3f0np0) — это обязательное требование DOCA-тракта.

  2. Репрезентера виртуальной функции (ens3f0npf0vf0) — порт, представляющий генератор в external сети (vlan 20). IP этому порту я назначил в конфиге Trex.

  3. Mgt, overlay порты с типом internal. В данном случае OVS создает TAP-интерфейсы в системе, и на них можно назначить IP. При этом правила добавления VLAN-тэгов все равно сгружаются на сетевую карту.

ovs-vsctl add-port underlay ens3f0np0 -- set Interface ens3f0np0 type=doca -- set port ens3f0np0 vlan_mode=trunk trunk=10,20,30
ovs-vsctl add-port underlay ens3f0npf0vf0 tag=20 -- set Interface ens3f0npf0vf0 type=doca
ovs-vsctl add-port underlay mgt tag=10 -- set interface mgt type=internal 
ovs-vsctl add-port underlay overlay tag=30 -- set interface overlay type=internal 
ip l set dev mgt up
ip link set dev overlay up
ip a a dev mgt 10.10.0.3/24
ip add add dev overlay 10.30.0.3/24

# br-int
# привязываем пользовательский порт port1 к vf 1. port1 это логический порт, который я потом создам в OVN.
ovs-vsctl add-port br-int ens3f0npf0vf1 -- set Interface ens3f0npf0vf1 type=doca -- set interface ens3f0npf0vf1 external_ids:iface-id=port1

# привязываем 2 – 7 ядра для Poll Mode драйверов (PMD) на генераторе. 
# одно ядро может генерировать 8Mpps. C шестью ядрами потенциальная мощность генератора получилась 48 Mpps.
ovs-vsctl set Open_vSwitch . other_config:pmd-cpu-mask="0xfc"
ovs-vsctl set Interface ens3f0np0 options:n_rxq=6
ovs-vsctl set interface ens3f0np0 other_config:pmd-rxq-affinity="0:2,1:3,2:4,3:5,4:6,5:7"
# проверяем назначенные ядра
ovs-appctl dpif-doca/pmd-rxq-show

Конфигурация выше применима к генератору пакетов, а на DUT она немного отличается. DUT был настроен с n_rxq=1 для физического порта, чтобы понять, какую производительность можно получить с одного ядра.

Создаем логическую топологию

Для тестирования нам понадобятся обычный логический маршрутизатор и минимальная обвязка: логическая сеть пользователя и логическая внешняя сеть. Сеть пользователя имеет два порта:

  • uplink к маршрутизатору;

  • пользовательский порт, за которым будет запущен trex генератор. Ставим IP 192.168.0.3. За ним trex будет генерировать пакеты из сети 16.0.0.0/8.

Внешняя сеть тоже имеет два порта:

  • downlink к маршрутизатору с тегом внешней сети VLAN 20;

  • внешний порт, адрес которого выставлен как unknown: весь трафик с неизвестным получателем будет уходить на него.

На маршрутизаторе я добавил правила маршрутизации из генерируемой внешней сети (48.0.0.0/8) в генерируемую пользовательскую сеть (16.0.0.0/8).

# cоздаем внешний порт с адресом unknown для связи с внешним миром 
ovn-nbctl lsp-add external internet
ovn-nbctl lsp-set-type internet localnet
ovn-nbctl lsp-set-addresses internet unknown
ovn-nbctl lsp-set-options internet network_name=physnet1
# cоздаем порт во внешней сети с 20 VLAN ID для линка с маршрутизатором.
ovn-nbctl lsp-add external downlink "" 20
ovn-nbctl lsp-set-type downlink router
ovn-nbctl lsp-set-addresses downlink router
ovn-nbctl lsp-set-options downlink router-port=ext_gw gateway-chassis=<chassis id> networks="10.20.0.0/24"
# создаем сеть пользователя
ovn-nbctl ls-add sw0
ovn-nbctl lsp-add sw0 port1
ovn-nbctl lsp-set-addresses port1 "50:54:00:00:00:01 192.168.0.3"
# создаем порт для связи с логическим маршрутизатором
ovn-nbctl lsp-add sw0 uplink
ovn-nbctl lsp-set-type uplink router
ovn-nbctl lsp-set-addresses uplink router
ovn-nbctl lsp-set-options uplink router-port=p1
# логический маршрутизатор с установленным gateway chassis
ovn-nbctl lr-add lr0
ovn-nbctl lrp-add lr0 p1 00:00:00:00:ff:01 192.168.0.1/24
ovn-nbctl lrp-add lr0 ext_gw 00:00:00:01:ff:01 10.20.0.2/24
ovn-nbctl lrp-set-gateway-chassis gw  <chassis id. see in 'ovn-sbctl show'>

И вот теперь наконец-то, наше окружение настроено и готово к запуску тестов.

Тестирование DOCA vs System: сравнение производительности

Я проводил три теста: маршрутизация, NAT, Load balancer. Тестировал только два тракта: DOCA и system. Тесты DOCA проводились только с одной RX очередью, чтобы удобно было сравнивать с system-трактом. Размеры пакетов выбирал как можно меньше от 64 до 100 байт, чтобы померить производительность системы в PPS (packets per second). Более подробно о методологии тестирования с помощью Trex, можно посмотреть в моей предыдущей статье.

Маршрутизация

Чтобы проверить скорость маршрутизации, я использовал всего два статических маршрута на логическом маршрутизаторе lr0:

ovn-nbctl lr-route-add lr0 48.0.0.0/16 10.20.0.3
ovn-nbctl lr-route-add lr0 16.0.0.0/16 192.168.0.3

Результаты получились очень оптимистичные.

UDP-трафик обрабатывается на DOCA в 16 раз быстрей, чем kernel Open vSwitch. Для TCP-трафика отрыв меньше — в 10 раз DOCA быстрей.

NAT

Для тестирования NAT я добавил правило и запустил трафик на 18.0.0.188:

ovn-nbctl lr-nat-add lr0 dnat_and_snat 18.0.0.188 16.0.0.188

Результат оказался не таким впечатляющим. При этом UDP-трафик DOCA вообще отказалась обрабатывать: даже при минимальной нагрузке в 100 пакетов в секунду терялось 20% трафика. Не могу сказать, что пациент однозначно мертв, но признаков жизни тоже не заметно. Напишите, может быть я чего-то не учел?

Load Balancer

Для тестирования балансировщика я поднял три пользовательских логических порта и подсоединил их к VF сетевой карты. Также пришлось запустить два приложения Trex, так как у него нет возможности сконфигурировать разное количество CPU-ядер для разных портов. Я предполагал, что на клиентском порту будет нагрузка больше, чем на бэках, так как он один пытается нагрузить сразу три бэка. Поэтому запустил один Trex для клиентского порта и отдал ему 4 CPU и второй Trex c тремя портами по одному CPU для каждого порта.

Добавил следующее правило на логический маршрутизатор:

ovn-nbctl lb-add lb-web 18.0.0.172:80 192.168.0.3:80,192.168.0.4:80,192.168.0.5:80 tcp
ovn-nbctl lb-add lb-udp 18.0.0.172:8080 192.168.0.3:8080,192.168.0.4:8080,192.168.0.5:8080 udp

TCP трафик прошел быстрей через DOCA в 3,5 раза. А вот UDP вообще не прошел — та же проблема, что и с NAT.

Выводы: когда SmartNIC оправдывает ожидания, а когда нет

Завершилась первая часть исследований. В ней я отлаживал методологию тестирования, дружил Trex с Geneve, SberLinux с DOCA и в итоге проверял основные возможности system-тракта и DOCA на ConnectX-6 Lx (25 Гбит/с). DOCA показала себя перспективно в маршрутизации и ускорила TCP-балансировку. С UDP NAT и UDP-балансировкой в этом стенде возникли проблемы, причину которых пока не локализовали. Если вы смелый, ловкий, умелый — можете сделать свои предположения в комментариях.

Следующим этапом станет исследование DPDK-тракта и DOCA на адаптере ConnectX-6 Dx (100 Гбит/с). Я возлагаю на него большие надежды: производитель заявляет, что аппаратный conntrack работает в 19 раз быстрее программного. Да, тесты проводились на Bluefield, но ConnectX-6 DX — это старшая модель в линейке SmartNIC, и я ожидаю, что она оснащена аналогичным модулем ASAP². Если интересно узнать, насколько это близко или далеко от реальности — дайте знать в комментариях, и я опубликую результаты.