Меня зовут Юрий Морозов, я главный архитектор компании «Гарда». В этой статье я расскажу, как можно организовать обработку трафика на высоких скоростях. Необходимость решения данной задачи естественно вытекает из эволюции технологий передачи и обработки данных. За последние годы скорости передачи данных стали на порядок выше, и вместе с ними резко трансформировались требования к производительности систем анализа и фильтрации трафика.

Раньше мы успешно справлялись с нагрузками на скоростях 10G, 40G и даже 100G. Архитектура продуктов постепенно оптимизировалась: улучшалась и функциональность, и эффективность использования ресурсов. При этом изменения оставались по большей части эволюционными: мы просто «крутили ручку» скорости, не перестраивая фундамент.

Однако на отметке в 200G стало ясно, что мы уперлись в стену. Поводом для переосмысления послужил один кейс: требовалось создать интегрированную платформу многоуровневой защиты трафика для высокопроизводительных ЦОДов с входным потоком от 200 до 400G. И тут все начало трещать по швам. Не справлялось буквально все: быстродействие памяти, пропускная способность системной шины. В результате процессоры стали перегреваться, а частота пакетов была на гране дискретизации процессов. Стало ясно, что без радикальной перестройки архитектуры дальше двигаться невозможно.

Кстати, этот кейс лег в основу моего доклада на HighLoad++, с которым я успешно дебютировал в ноябре 2025 года. Здесь я привожу ключевые тезисы доклада.

Проблематика задачи

Как я уже упомянул выше, прежние подходы к обработке трафика уже не справляются, когда речь идет об обработке данных на скоростях, ранее недоступных для софта – таких как 400G.

Актуальные скорости и вызовы (100G – 800G)
Актуальные скорости и вызовы (100G 800G)

Рассмотрим несколько типичных сценариев, где последствия сбоев или некорректной обработки трафика будут наиболее критичны:

  • DDoS-защита: система должна мгновенно определять атаку и перенаправлять весь подозрительный трафик, доставляя к защищаемому ресурсу только легитимные запросы.

  • решения класса IPS/IDS: таким системам недостаточно просто читать заголовки. Они должны реконструировать полные сессии прикладного уровня (например, HTTP, SMB), чтобы сопоставить их с базой сигнатур и обеспечить эффективную сетевую безопасность. Например, при обнаружении угрозы IPS должна активно блокировать или изменять пакеты в потоке, что требует вмешательства в TCP-соединение (например, отправки RST-пакета).

  • биллинговые системы: должны точно измерять объем трафика, генерируемого каждым клиентом, сервисом или устройством, и сохранять эти данные с привязкой ко времени, тарифному плану или другим параметрам. Измерение должно быть непрерывным и юридически точным, так как на этих данных строятся финансовые расчеты.

  • CDN-сервер: на границе сети система должна принимать запросы от пользователей, проверять наличие запрошенного контента в локальном кэше и, если он найден, мгновенно отдавать данные. А если контент отсутствует, она должна запрашивать его у origin-сервера либо перенаправлять запрос. В случае TLS-зашифрованного трафика сервер обязан выполнять TLS-терминацию.

Все эти сценарии требуют интеллектуальной, гибкой и высокопроизводительной обработки. Однако существующие программные методы имеют фундаментальные ограничения. Так, на скорости 400G при размере пакета 64-byte необходимо успевать обрабатывать около 600 млн пакетов в секунду (Mpps). Теоретический предел одного ядра – около 90Mpps, но на практике даже в простых сценариях мы редко выходим за 15-25Mpps. Если же требуется глубокая обработка входных данных, этот показатель падает до 5Mpps.

Простой расчет показывает: для обработки 400G при 5Mpps потребуется 120 ядер, что делает чисто программные решения малоэффективными. Если же нам удастся поднять скорость хотя бы до 25Mpps, то требуется только 24 ядра. Такая цифра выглядит не такой уж и страшной. Таким образом, для решения задачи нам требуется достигнуть максимальной производительности на каждом ядре.

Сравнение методов обработки трафика

Для обработки трафика сегодня применяются подходы на основе программных и аппаратные решений. Выбор во многом определяется балансом цены, гибкости и производительности.

Программные методы

Первый способ получения доступа к пакетам – программный сокет AF_PACKET. Он был введен в ядро Linux в 1996 году. И это была первая попытка ускорить обработку трафика.

Но AF_PACKET, в отличие от стандартного стека, клонирует буфера sk_buff сразу после начальной L2-обработки в функции sock_recv() и далее пакеты попадают в пользовательское пространство. Важно отметить, что клонирование происходит параллельно стандартной обработке в ядре. Это объясняет, почему в таких утилитах как tcpdump или wireshark мы видим пакеты, которые позже были блокированы в ip-tables.

С учетом накладных расходов на копирование пакетов и системные вызовы производительность AF_PACKET составляет примерно полмиллиона пакетов в секунду, что в целом немного.

Cхема обработки пакетов с помощью AF_PACKET
Cхема обработки пакетов с помощью AF_PACKET

Следующий вариант – PF_RING (разновидность сетевого сокета). Рост скорости захвата пакетов достигается за счет возможности обработки пакетов в ядре и переключения между kernel- и userspace. Однако использование PF_RING предполагает копирование пакетов. Задержка обработки в этом случае составляет примерно 50µs, а скорость 2-5Mpps. Это уже заметно быстрее, но все же еще не идеально.

Cхема обработки пакетов с помощью PF_RING
Cхема обработки пакетов с помощью PF_RING

Третий инструмент для программной обработки трафика, который хотелось упомянуть, – это DPDK (Data Plane Development Kit): Open Source фреймворк, предназначенный для создания высокопроизводительных сетевых приложений.

Ключевым элементом архитектуры DPDK являются PMD-драйверы (Poll Mode Drivers), которые могут работать полностью в пользовательском пространстве без прерываний. Именно этот подход позволяет исключить ядро из пути обработки трафика, обеспечив тем самым максимальную производительность.

При запуске DPDK-приложение захватывает сетевой интерфейс (NIC) через драйвер Userspace I/O (UIO). В результате userspace-процесс может управлять регистрами устройства и получать прямой доступ к его памяти.

Для хранения пакетов DPDK выделяет huge pages. Это минимизирует количество TLB-промахов и ускоряет доступ к данным. Входящие пакеты NIC записывает напрямую в ring buffers, размещенные в hugepage-памяти.

При этом никакие аппаратные прерывания не генерируются. Вместо этого приложение циклически опрашивает регистры NIC через PMD, проверяя наличие новых пакетов. Как только пакет обнаружен, PMD извлекает его без копирования, и передает указатель на данные напрямую в приложение.

Все происходит в одном адресном пространстве: выделение памяти, маршрутизация, фильтрация, инкапсуляция. Эти операции реализуются в userspace с использованием DPDK-библиотек.

Производительность впечатляющая: на одном ядре современного CPU можно обрабатывать 20-30 млн коротких пакетов в секунду. При применении продвинутых методов оптимизации этот показатель можно увеличить до 40 млн пакетов в секунду на ядро. В простых сценариях, таких как L2 forwarding, задержка от приема до отправки составляет менее 5µs.

DPDK также поддерживает многопроцессорную обработку, lock-free очереди, привязку потоков к ядрам (CPU affinity) и NUMA-локальность – то есть все, что необходимо для максимально эффективного использования современных аппаратных платформ.

Аппаратные методы

Из аппаратных методов стоит выделить FPGA и SmartNIC.

FPGA – одно из самых производительных, но и самых дорогих решений. Хотя FPGA и позволяют реализовать кастомные алгоритмы обработки трафика на уровне железа, но они требуют специализированной разработки от вендоров и плохо подходят для гибких или быстро меняющихся алгоритмов.

Более практичный вариант – SmartNIC: это сетевая карта с функциями полноценного вычислительного ускорителя на PCI-плате. SmartNIC оснащена собственными процессорами, многоядерными ARM-ядрами, FPGA или специализированными аппаратными блоками. И способна обрабатывать трафик до того, как тот попадает в основной CPU сервера, эффективно разгружая хост от сетевых задач.

SmartNIC давно переросла уровень «умной сетевой карточки». Сегодня она больше напоминает специализированный компьютер.

Типичные сценарии применения SmartNIC включают разгрузку задач виртуализации (обработка VXLAN), построение NAT, маршрутизацию, TLS-терминацию, а также реализацию функций межсетевого экрана.

Пример SmartNIC
Пример SmartNIC

На приведенном ниже рисунке показаны четыре модели SmartNIC. Одним из лидеров рынка является NVIDIA Bluefield-3 DPU, которая имеет 16 ARM-ядер, поддерживает до 400G и работает под управлением собственной ОС (DOCA).

Большинство передовых решений Smart NIC сейчас может быть сложно приобрести в РФ. Поэтому программные методы остаются основой большинства решений – при условии, что они построены правильно.

Сравнение программных и аппаратных методов обработки трафика
Сравнение программных и аппаратных методов обработки трафика

Возможности C++ для оптимизации скорости обработки трафика

Почему мы говорим, что для обработки трафика используем C++, если наиболее высокопроизводительные приложения зачастую пишутся на чистом C или Rust?

На практике большинство решений для оптимизации скорости обработки трафика можно реализовать именно на C++. И это не совпадение. Ниже я приведу три ключевых аргумента «за».

Zero-Cost Abstraction (абстракция без стоимости). Посмотрим на стандартные STL-контейнеры, которые предоставляют высокоуровневый и выразительный интерфейс, но во время сборки компилятор превращает их в код, почти неотличимый от того, что вы написали бы с помощью указателей и циклов. Благодаря чему можно писать понятный, легко поддерживаемый код и быть уверенным, что он работает на скорости, близкой к скорости железа.

На скрине показан вызов виртуальной функции по указателю. После компиляции эта строка превратится в одну инструкцию процессора с проверкой ячейки на установленный бит с косвенной адресацией данных. Таким образом, стоимость абстракции нулевая.

Inline-ассемблер с интринсиками. Часто для ускорения обработки трафика используют специфичные инструкции процессора, недоступные через стандартные языковые конструкции. В C++ для этого предусмотрены два основных механизма: встроенный ассемблер и интринсики.

Inline-ассемблер позволяет вставлять фрагменты кода непосредственно в тело функции на C++. Однако более практичным и широко используемым подходом являются интринсики – это функции, предоставляемые компилятором. Они напрямую транслируются в одну или несколько специфичных машинных инструкций. Например, код функции mmloadu_si128, представленный на скрине ниже, загружает первые 16 байт пакета (включая Ethernet-заголовок) в вектор.

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

Связь с DPDK и PF_RING. Использование C++ для написания обработки трафика имеет преимущество при интеграции с такими фреймворками, как DPDK и PF_RING. Эти библиотеки реализованы на C и предоставляют низкоуровневые API, ориентированные на прямой доступ к аппаратным ресурсам, что позволяет разработчику использовать всю мощь языка. При этом ему не нужно переходить на другие языки или использовать слои абстракции, которые могут вносить накладные расходы.

Функции из DPDK и PF_RING доступны через C-интерфейсы, но их можно использовать из кода C++ напрямую. На примере ниже показана обертка вокруг rte_mbuf из DPDK, которая автоматически освобождает память пакета при выходе из области видимости, исключая утечки и реализуя тем самым идиому RAII.

Как еще можно оптимизировать управление памятью и обеспечить многопоточность

Чтобы достичь предела производительности, недостаточно просто использовать DPDK и C++. Нужно понимать, как работает система в целом.

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

Проблему можно решить через пулы памяти. Вот как это работает.

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

Huge pages. Другой механизм оптимизации – huge pages. Ключевую роль в обращении к памяти играет Translation Lookaside Buffer (TLB), или кэш процессора, ускоряющий трансляцию виртуальных адресов в физические. При каждом обращении к памяти процессор сначала проверяет TLB: при попадании (hit) трансляция занимает один такт, при промахе (miss) – запускается медленный обход таблиц страниц, что уже может насчитывать несколько десятков тактов.

Например, для хранения 1Gb поступающих данных при стандартных 4-килобайтных страницах, требуется более 200 тыс. записей в TLB, тогда как реальный TLB содержит всего несколько сотен записей. В результате TLB постоянно испытывает промахи.

Для решения проблемы TLB-промахов используют huge pages. В реальных тестах с DPDK на Intel Xeon Gold переход на huge pages размером 2MB сократил число промахов TLB в 10 раз.

Изоляция ядер и Thread Affinity. Кэши L1/L2 работают в 10 раз быстрее стандартной памяти. Если планировщик операционной системы мигрирует поток с ядра на ядро, кэш сбрасывается. Чтобы этого избежать, мы:

  • исключаем ядра из управления планировщиком;

  • жестко привязываем поток к ядру.

Таким образом, миграции потоков не происходит, кэш L1 и L2 всегда остается «горячим», а мы получаем стабильную производительность.

NUMA-архитектура. Современные серверы построены по NUMA-архитектуре: ядра, память и сетевые карты сгруппированы в ноды. Обмен между составными частями одной ноды происходит в разы быстрее, чем обмен с такими же компонентами другой ноды.

Все это, соответственно, тормозит производительность. Для решения этой проблемы используется жесткая NUMA-локальность: когда поток привязан к ядру в ноде, память выделяется в той же ноде, сетевая карта принадлежит этой ноде. Резервирование huge pages происходит отдельно для каждого узла, чтобы ОС не выделила их «в другом месте».

При соблюдении этих правил данные свободно циркулируют между памятью, сетевым интерфейсом и CPU без единого межсокетного перехода. Таким образом, мы достигаем максимальной производительности.

Lock-free очереди. Традиционные очереди с mutex или spinlock создают узкие места. Когда несколько потоков конкурируют за доступ, они либо блокируются (в случае mutex), либо крутятся вхолостую (в случае spinlock). В результате потоки простаивают, ожидая доступа.

Проблема решается с помощью lock-free очереди, в которой вместо традиционных блокировок используются атомарные операции, в первую очередь Compare-And-Swap (CAS). При добавлении или извлечении элемента поток атомарно обновляет указатели очереди. Если несколько потоков одновременно пытаются изменить одни и те же данные и возникает конфликт, операция просто повторяется – но никто при этом не блокируется. Благодаря этому даже в случае, если один из потоков «застрянет» (например, из-за приостановки планировщиком), остальные продолжают работать без задержек. Такой подход критически важен для обеспечения предсказуемой и стабильной производительности в высоконагруженных системах.

DMA (Direct Memory Access). Копирование сетевых пакетов из буфера сетевой карты в память приложения силами CPU – неэффективная трата вычислительных ресурсов.

Проблему можно решить за счет использования сетевых карт с поддержкой прямого доступа к памяти (DMA), которые могут записывать пакеты напрямую в оперативную память, минуя CPU.

Фреймворки вроде DPDK заранее выделяют пул буферов и передают их физические адреса сетевой карте. При получении пакета NIC, используя механизм DMA, размещает его в свободном буфере и лишь затем уведомляет CPU. В результате процессор освобождается от копирования и может целиком сосредоточиться на обработке трафика.

Неочевидные бутылочные горлышки на пути к росту скорости обработки трафика

У нас в лаборатории использовался сервер на базе двух процессоров Intel Xeon Gold 5516 с 256GB оперативной памяти. Для сетевой части применялись адаптеры Mellanox ConnectX-6 Dx. Она поддерживает до 200G, обеспечивает задержку менее 1µs и пропускную способность до 215Mpps. Среди ее возможностей – TLS offload (до 1 млн сессий), поддержка виртуализации через ASAP2, гибкое разделение трафика на очереди, а также выполнение XDP-программ прямо на сетевой карте.

Характеристики нашей аппаратной платформы
Характеристики нашей аппаратной платформы

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

Изначально мы стремились достичь производительности 50G на один поток, разделив весь входящий трафик на 8 потоков. На серверах инспекции применялась каскадная обработка: часть правил – на первом этапе, следующая – на втором и так далее. Всего было три таких ступени. Целевой производительностью одного каскадного потока составляла 40G.

Таким образом, весь трафик должен был поступать на серверы инспекции в виде 10 потоков. Серверы работали в режиме Active-Active: в штатном режиме каждый из них использовался на 50% от максимума, а при отказе одного – второй мгновенно переходил на 100% загрузки.

Общая схема выглядела так:

BR → Network fabric → Load Balancer → Inspect Server -> Network fabric -> BR

Соответственно маркировались типы трафика: Input traffic, Balanced traffic, Inspected traffic, Control line.

Это означает, что все входящие пакеты с помощью алгоритма перераспределяются на один из выходных интерфейсов. В нашей схеме трафик на входе – 400G. Далее на балансировщике по RSS поток делится на 8 потоков по 50G. После балансировки балансировщик разделяет трафик на 10 потоков по 40G.

Сервер инспекции скорость не меняет. И на выходе border router или «пограничное устройство» снова все сливает в один поток 400G.

Первая проблема: низкая производительность на ядро

Замеры показали, что производительность на одно ядро – всего 17G, что явно недостаточно для решения нашей задачи.

Что можно с этим сделать? Если заглянем внутрь балансировщика, мы увидим сетевые интерфейсы. С физического уровня пакет попадает во внутренний буфер сетевой карточки. Программа XDP считывает заголовок пришедшего пакета и в соответствии с алгоритмом выбирает интерфейс, куда будет направлен пакет, одновременно изменяя его заголовок. После этого происходит копирование пакета из буфера одного сетевого интерфейса в другой с использованием команды XDP_REDIRECT.

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

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

Общий принцип работы схемы on-stick следующий. Весь трафик с помощью VLAN разделяется на несколько типов: Input, Balanced, Inspected, Control traffic. На входе пограничный маршрутизатор (Border Router) присваивает всему трафику метку Input Traffic. Затем балансировщик помечает поступивший на него трафик как Balanced Traffic. Далее, когда трафик достигает серверов, он получает метку Inspected Traffic. На выходе балансировщик удаляет VLAN-тег из заголовка и отправляет пакеты обратно в выходную сеть.

Посмотрим, что изменилось в сетевом интерфейсе.

Мы видим: пакеты также попадают с физического уровня во внутренний буфер сетевой карты. XDP, работающая в адресном пространстве сетевого интерфейса, как и в предыдущем варианте, считывает заголовок пришедшего пакета, определяет целевой сервер и изменяет заголовок пакета, указывая в нем выбранный сервер, а затем вызывает команду XDP_TX. Эта команда не копирует пакет, а лишь передает указатель на уже существующий буфер драйверу того же самого NIC. Внутренняя логика сетевой карты отправляет пакет обратно в сеть без участия CPU и шины PCI. В результате мы получаем минимальную задержку и повышаем производительность.

Цифры на графике говорят сами за себя.

Скорость выросла на коротких пакетах до 31G, а на пакетах размером 192B мы полностью утилизировали производительность 50G интерфейса.

В целом, это отличный результат, который позволил нам полностью разбалансировать трафик на 8 ядрах. При этом нагрузка на CPU была минимальной, что позволило использовать достаточно слабый сервер для балансировки.

Вторая проблема: недостаточная производительность в обработке

Перейдем теперь к инспектирующему серверу. Замеры показали, что обработка на одном ядре 15G. Нам этого было явно недостаточно.

В предыдущих разделах статьи я уже неоднократно отмечал, что DPDK активно использует ключевые техники оптимизации. Эти приемы реализованы внутри библиотек DPDK, но логично применять те же принципы и в собственном коде, построенном на них.

На следующем скриншоте показана типичная задача разбора сетевых заголовков входящих пакетов. Функция __decode_pipeline принимает массив указателей на пакеты и их количество. В цикле выполняется обработка каждого пакета: определяются 5-tuple, длина заголовка, L3-протокол и так далее.

Подумаем, как это можно оптимизировать.

Мы знаем, что DPDK эффективно считывает пакеты из пула по несколько штук (batch-processing). Стандартный размер составляет 256 пакетов. Следовательно, и разбор заголовков имеет смысл выполнять не по одному, а сразу по многим пакетам, используя векторизацию или конвейерную обработку.

Фрагмент кода – скалярная обработка
Фрагмент кода – скалярная обработка

Как мы можем обработать сразу несколько пакетов за раз?

Для этого перепишем цикл с использованием векторных SIMD-инструкций (Single Instruction, Multiple Data), чтобы он обрабатывал по 16 пакетов за итерацию.

Принцип работы SIMD-инструкции
Принцип работы SIMD-инструкции

SIMD-инструкция позволяет добиться массового ускорения однотипных операций. Есть несколько типов SIMD-расширений:

  • MMX (MultiMedia eXtension);

  • SSE (Streaming SIMD Extensions);

  • AVX (Advanced Vector Extensions);

  • AVX-512.

Мы использовали расширение AVX-512, так как оно позволяет получить максимальное ускорение (см. рисунок ниже).

Вернемся к нашей задаче. Сначала в AVX-512 регистр загружается младший байт Ethernet Type для 16 пакетов.

Дальше мы загружаем старшую часть Ethernet Type, после чего они объединяются:

Фрагмент векторизованного кода: загрузка eth_low / eth_high
Фрагмент векторизованного кода: загрузка eth_low / eth_high

После этого начинается векторная обработка с использованием масок предикатов:

Фрагмент кода – векторная обработка с масками
Фрагмент кода – векторная обработка с масками

Ключевых особенностей использования AVX-512 в этом коде – две:

  • обработка 16 пакетов за итерацию;

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

Нам удалось ускорить обработку в 2,5 раза.

Хороший результат, но вы спросите: почему не в 16 раз?

Дело в том, что не всю логику удалось векторизовать. Кроме того, остались накладные расходы на управление масками и обработку «хвоста». Тем не менее даже прироста в 2,5 раза хватило, чтобы эффективно распределить 400G трафика между 8 ядрами и успешно решить поставленную задачу.

Заключение

Подведем итоги и посмотрим на общую картину:

Полная схема балансировки
Полная схема балансировки

Помимо уже рассмотренных сценариев обработки пакетов, существуют и другие варианты. Например, для пакетов, не попавших ни в одну из групп, используется команда XDP_DROP, которая сбрасывает пакет из буфера. Также применяется команда XDP_PASS, предназначенная для передачи управляющих пакетов в сетевой стек Linux с последующей обработкой в пользовательском пространстве.

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

Поэтому настройка системы требовала от нас максимальной оптимизации на всех уровнях (в том числе точного распределения нагрузки между ядрами). Этого удалось достичь за счет профилирования и привязки потоков к конкретным ядрам, таким образом, чтобы критические участки кода и данные постоянно оставались в кэше процессора. В итоге мы получили дополнительный прирост производительности порядка 10% и достигли целевой производительности в 40G.

Для полной утилизации 400G потребовалось 10 потоков.

Наш опыт показал, что обработка 400G в софте – отнюдь не мечта, а достижимая реальность, если подойти к задаче с умом. Стоит помнить, что даже самые простые участки кода скрывают значительный потенциал для оптимизации. Ключ к успеху – системный подход: нужно смотреть не только на алгоритм, но и на ОС, память, ядра, NUMA, сетевую карту.

Комментарии (2)


  1. rPman
    16.01.2026 15:04

    Я правильно понимаю, у пользователя этих устройств есть какой то механизм настройки условий, по которым будет проводиться этот анализ? есть какой то список базовых элементов условий, из которых это строится, например разбор протокола (например выше заявлен анализ smb трафика) захардкожен или он описан на каком то 'птичьем языке', который пользователь описывает в настройках?


  1. Lev3250
    16.01.2026 15:04

    То есть что-то подобное и используют в dpi РНК? (Или будут использовать)