From dab3274263865f7f2592d204141346911996d41b Mon Sep 17 00:00:00 2001 From: Lukas Sismis Date: Mon, 24 May 2021 20:11:24 +0200 Subject: [PATCH] dpdk: add documentation for the DPDK runmode Briefly present the DPDK runmode through configuration file. --- doc/userguide/configuration/suricata-yaml.rst | 136 ++++++++++++++++++ .../configuration/suricata-yaml/dpdk.png | Bin 0 -> 19781 bytes 2 files changed, 136 insertions(+) create mode 100644 doc/userguide/configuration/suricata-yaml/dpdk.png diff --git a/doc/userguide/configuration/suricata-yaml.rst b/doc/userguide/configuration/suricata-yaml.rst index bbb3453c5c..b6b6a80d93 100644 --- a/doc/userguide/configuration/suricata-yaml.rst +++ b/doc/userguide/configuration/suricata-yaml.rst @@ -1549,6 +1549,142 @@ computers etc.) Packet Acquisition ------------------ +Data Plane Development Kit (DPDK) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`Data Plane Development Kit `_ is a framework for fast packet processing in data plane +applications running on a wide variety of CPU architectures. +DPDK `Environment Abstraction Layer (EAL) `_ +provides a generic interface to low-level resources. It is a unique way how DPDK libraries access +NICs. EAL creates an API for application to access NIC resources from the userspace level. In DPDK, packets +are not retrieved via interrupt handling. Instead, the application +`polls `_ NIC for newly received packets. + +DPDK allows the user space application to directly access memory where NIC stores the packets. +As a result, neither DPDK nor the application copies the packets for the inspection. The application directly +processes packets via passed packet descriptors. + + +.. figure:: suricata-yaml/dpdk.png + :align: center + :alt: DPDK basic architecture + :figclass: align-center + + `High-level overview of DPDK application` + + +Suricata makes use of DPDK for packet acquisition in workers runmode. +The whole DPDK configuration resides in the `dpdk:` node. This node encapsulates +2 main subnodes and those are eal-params and interfaces. + +:: + + dpdk: + eal-params: + proc-type: primary + interfaces: + - interface: 0000:3b:00.0 + threads: auto + promisc: true + multicast: true + checksum-checks: true + checksum-checks-offload: true + mtu: 1500 + mempool-size: 65535 + mempool-cache-size: 257 + rx-descriptors: 1024 + tx-descriptors: 1024 + copy-mode: none + copy-iface: none # or PCIe address of the second interface + + +The node `dpdk.eal-params` consists of `DPDK arguments `_ +that are usually passed through command line. These arguments are used to initialize and configure EAL. +Arguments can be specified in either long or short forms. When specifying the arguments, the dashes are omitted. +Among other settings, this configuration node is able to configure available NICs to Suricata, memory settings or other +parameters related to EAL. + +The node `dpdk.interfaces` wraps a list of interface configurations. Items of the list follows the structure that can +be found in other capture interfaces. The individual items contain the usual configuration options +such as `threads`/`copy-mode`/`checksum-checks` settings. Other capture interfaces, such as AF_PACKET, rely on the user that NICs are appropriately configured. +Configuration through kernel does not apply to applications running under DPDK. The application is solely responsible for the +initialization of NICs it is using. So, before the start of Suricata, NICs that Suricata uses, must undergo the process of initialization. +As a result, there are extra extra configuration options (how NICs can be configured) in the items (interfaces) of the `dpdk.interfaces` list. +At the start of the configuration process, all NIC offloads are disabled to prevent any packet modification. +According to the configuration, checksum validation offload can be enabled to drop invalid packets. +Other offloads can not be currently enabled. +Additionally, the list items of `dpdk.interfaces` contains DPDK specific settings such as `mempool-size` or `rx-descriptors`. +These settings adjust individual parameters of EAL. One of the entries of the `dpdk.interfaces` is the `default` interface. +When loading interface configuration and some entry is missing, the corresponding value of the `default` interface is used. + +The worker threads must be assigned to a specific cores. The configuration module `threading` can be used to set threads affinity. +Worker threads can be pinned to cores in the array configured in `threading.cpu-affinity["worker-cpu-set"]`. +Performance-oriented setups have everything (the NIC, memory and CPU cores interacting with the NIC) based on one NUMA node. +It is therefore required to know layout of the server architecture to get the best results. +The CPU core ids and NUMA locations can be determined for example from the output of `/proc/cpuinfo` where `physical id` described the NUMA number. +The NUMA node to which the NIC is connected to can be determined from the file `/sys/class/net//device/numa_node`. + +:: + + ## Check ids and NUMA location of individual CPU cores + cat /proc/cpuinfo | grep 'physical id\|processor' + + ## Check NUMA node of the NIC + ## cat /sys/class/net//device/numa_node e.g. + cat /sys/class/net/eth1/device/numa_node + +If Suricata has enabled at least 2 (or more) workers, the incoming traffic is load balanced across the worker threads +by Receive Side Scaling (RSS). Internally, DPDK runmode uses +a `symmetric hash (0x6d5a) `_ +that redirects bi-flows to specific workers. + +Before Suricata can be run, it is required to allocate sufficient number of hugepages. Suricata allocates continuous block of memory. +For efficiency, CPU allocates memory in RAM in chunks. These chunks are usually in size of 4096 bytes. DPDK and other memory intensive applications makes use of hugepages. +Hugepages start at the size of 2MB but they can be as large as 1GB. Lower count of pages (memory chunks) allows faster lookup of page entries. +The hugepages need to be allocated on the NUMA node where the NIC and CPU resides. +Otherwise, if the hugepages are allocated only on NUMA node 0 and the NIC is connected to NUMA node 1, then the application will fail to start. +Therefore, it is recommended to first find out to which NUMA node the NIC is connected to and only then allocate hugepages and set CPU cores affinity to the given NUMA node. +If the Suricata deployment is using multiple NICs on different NUMA nodes then hugepages must be allocated on all of those NUMA nodes. + +:: + + ## To check number of allocated hugepages: + grep Huge /proc/meminfo + + ## Allocate hugepages on NUMA node 0: + echo 8192 | sudo tee /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages + + +DPDK memory pools hold packets received from NICs. These memory pools are allocated in hugepages. +One memory pool is allocated per interface. The size of each memory pool can be individual and is set with +the `mempool-size`. Memory (in bytes) for one memory pool is calculated as: `mempool-size` * `mtu`. +Sum of memory pool requirements divided by the size of one hugepage results in the number of required hugepages. +It causes no problem to allocate more memory than required but it is vital for Suricata to not run out of hugepages. + +Mempool cache is local to the individual CPU cores and holds packets that were recently processed. As the mempool is +shared among all cores, cache tries to minimize the required inter-process synchronization. Recommended size of the cache +is covered in the YAML file. + +There has been an ongoing effort to add a DPDK support into Suricata. While the capture interface is continually evolving, +there has been certain areas with an increased focus. The current version of the DPDK capture interface provides +support for physical NICs and for running on physical machines in workers runmode. +The work has not been tested neither with the virtual interfaces nor +in the virtual environments like VMs, Docker or similar. + +Although the capture interface uses DPDK library, there is no need to configure any lcores. +The capture interface uses the standard Suricata threading module. +Additionally, Suricata is intended to run as a primary process only. + +The minimal supported DPDK is version 19.11 which should be available in most repositories of major distributions. +Alternatively, it is also possible to use `meson` and `ninja` to build and install DPDK from scratch. +It is required to have correctly configured tool `pkg-config` as it is used to load libraries and CFLAGS during +the Suricata configuration and compilation. + +To be able to run DPDK on Intel cards, it is required to change the default Intel driver to either +`vfio-pci` or `igb_uio` driver. The process is described in +`DPDK manual page regarding Linux drivers `_. +DPDK is natively supported by Mellanox and thus their NICs should work "out of the box". + Pf-ring ~~~~~~~ diff --git a/doc/userguide/configuration/suricata-yaml/dpdk.png b/doc/userguide/configuration/suricata-yaml/dpdk.png new file mode 100644 index 0000000000000000000000000000000000000000..b7a4759e3f889e55fa9e166db08b36330b2092c6 GIT binary patch literal 19781 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