dpdk: add live run test suite

Add live tests to verify that mempool, mempool cache sizes, and
potentially other DPDK features are initialized correctly during the
startup.
Follows up on bugs found in mempool settings on real/bonded devices.

The tests are placed in the new folder qa/live/ after a comment
in https://github.com/OISF/suricata/pull/14896 which indicated other
live tests are going to be moved there as well.

Ticket: 8320
pull/14908/head
Lukas Sismis 5 months ago committed by Victor Julien
parent ecd15a6c7e
commit d4434ef4e8

@ -1858,7 +1858,7 @@ jobs:
RUSTFLAGS: "-C instrument-coverage"
# IDS config
- run: |
./.github/workflows/live/dpdk.sh ".github/workflows/dpdk/suricata-null-ids.yaml"
./.github/workflows/live/dpdk.sh "qa/live/dpdk/suricata-null-ids.yaml"
env:
LLVM_PROFILE_FILE: "/tmp/dpdk-ids.profraw"
# IPS config
@ -1866,6 +1866,10 @@ jobs:
./.github/workflows/live/dpdk.sh ".github/workflows/dpdk/suricata-null-ips.yaml"
env:
LLVM_PROFILE_FILE: "/tmp/dpdk-ips.profraw"
# DPDK configuration checks
- run: ./qa/live/dpdk/dpdk-testsuite.sh
env:
LLVM_PROFILE_FILE: "/tmp/dpdk-testsuite-%p.profraw"
# AF_PACKET tests
- run: |
./.github/workflows/live/afp-ids.sh "2" "autofp"
@ -2247,10 +2251,12 @@ jobs:
CC: "clang-18"
# IDS config
- run: |
./.github/workflows/live/dpdk.sh ".github/workflows/dpdk/suricata-null-ids.yaml"
./.github/workflows/live/dpdk.sh "qa/live/dpdk/suricata-null-ids.yaml"
# IPS config
- run: |
./.github/workflows/live/dpdk.sh ".github/workflows/dpdk/suricata-null-ips.yaml"
# DPDK configuration checks
- run: ./qa/live/dpdk/dpdk-testsuite.sh
# AF_PACKET tests
- run: |
./.github/workflows/live/afp-ids.sh "2" "autofp"
@ -2937,10 +2943,12 @@ jobs:
- run: make check
# IDS config
- run: |
./.github/workflows/live/dpdk.sh ".github/workflows/dpdk/suricata-null-ids.yaml"
./.github/workflows/live/dpdk.sh "qa/live/dpdk/suricata-null-ids.yaml"
# IPS config
- run: |
./.github/workflows/live/dpdk.sh ".github/workflows/dpdk/suricata-null-ips.yaml"
# DPDK configuration checks
- run: ./qa/live/dpdk/dpdk-testsuite.sh
debian-12:
name: Debian 12 (xdp)

@ -0,0 +1,206 @@
#!/bin/bash
#
# DPDK configuration check script.
#
set -o pipefail
usage() {
cat <<'EOF'
Start Suricata with a DPDK config, optionally overriding per-interface
settings, and verify expected log output. Supports both positive tests
(Suricata starts) and negative tests (Suricata fails with expected error).
Usage:
dpdk-checklog.sh [OPTIONS] <yaml>
Options:
--interface-cfg-set <iface>.<key>=<value>
Override a DPDK interface setting (repeatable).
Resolved to: --set dpdk.interfaces.<N>.<key>=<value>
where <N> is the YAML index of <iface>.
--suricata-log-check-grep <regex>
Verify regex is found in the Suricata log (repeatable).
--expect-start Expect Suricata to start successfully (default).
--expect-fail Expect Suricata to fail during startup.
-h, --help Show this help message.
Examples:
dpdk-checklog.sh \
--interface-cfg-set net_null0.mempool-size=auto \
--interface-cfg-set net_null0.mempool-cache-size=auto \
--suricata-log-check-grep "mempools of size 31, cache size 1" \
--expect-start \
suricata-null-ids.yaml
dpdk-checklog.sh \
--interface-cfg-set net_null0.mempool-size=15 \
--suricata-log-check-grep "mempool size is likely too small" \
--expect-fail \
suricata-null-ids.yaml
EOF
}
# Argument parsing
YAML=""
EXPECT="start" # "start" or "fail"
declare -a CFG_SETS=()
declare -a LOG_CHECKS=()
while [ $# -gt 0 ]; do
case "$1" in
--interface-cfg-set)
CFG_SETS+=("$2"); shift 2 ;;
--suricata-log-check-grep)
LOG_CHECKS+=("$2"); shift 2 ;;
--expect-start)
EXPECT="start"; shift ;;
--expect-fail)
EXPECT="fail"; shift ;;
-h|--help)
usage; exit 0 ;;
-*)
echo "ERROR: unknown option: $1"; exit 1 ;;
*)
if [ -z "$YAML" ]; then YAML="$1"; else
echo "ERROR: unexpected argument: $1"; exit 1
fi
shift ;;
esac
done
if [ -z "$YAML" ]; then
usage
exit 1
fi
# Install trap early so all temp files are cleaned up on any exit path.
TMPFILES=()
trap 'rm -f "${TMPFILES[@]}"' EXIT
# DPDK version compatibility (member= / slave=)
DPDK_YAML="$YAML"
if grep -q "net_bonding" "$YAML"; then
DPDK_VER=$(pkg-config --modversion libdpdk 2>/dev/null || echo "0.0")
DPDK_MAJOR=$(echo "$DPDK_VER" | cut -d. -f1)
DPDK_MINOR=$(echo "$DPDK_VER" | cut -d. -f2)
DPDK_YAML=$(mktemp /tmp/dpdk-checklog-XXXXXX.yaml)
TMPFILES+=("$DPDK_YAML")
if [ "$DPDK_MAJOR" -lt 23 ] || { [ "$DPDK_MAJOR" -eq 23 ] && [ "$DPDK_MINOR" -lt 11 ]; }; then
sed 's/member=/slave=/g' "$YAML" > "$DPDK_YAML"
else
sed 's/slave=/member=/g' "$YAML" > "$DPDK_YAML"
fi
fi
# Resolve --interface-cfg-set to Suricata --set arguments
# Build a map: interface-name -> YAML index (0-based).
# Parses lines like " - interface: net_null0" from the YAML.
declare -A IFACE_INDEX=()
idx=0
while IFS= read -r iface_name; do
IFACE_INDEX["$iface_name"]=$idx
idx=$((idx + 1))
done < <(grep -E '^\s*-\s*interface:' "$YAML" | sed 's/.*interface:\s*//' | awk '{print $1}')
SURI_SET_ARGS=()
for entry in "${CFG_SETS[@]}"; do
# entry format: <iface>.<key>=<value>
iface="${entry%%.*}"
key_value="${entry#*.}"
if [ -z "${IFACE_INDEX[$iface]+x}" ]; then
echo "ERROR: interface '$iface' not found in $YAML"
echo " Available interfaces: ${!IFACE_INDEX[*]}"
exit 1
fi
suri_idx="${IFACE_INDEX[$iface]}"
SURI_SET_ARGS+=(--set "dpdk.interfaces.${suri_idx}.${key_value}")
done
# Run Suricata
SURILOG=$(mktemp /tmp/dpdk-checklog-log-XXXXXX.log)
TMPFILES+=("$SURILOG")
TIMEOUT_SEC=30
./src/suricata -c "$DPDK_YAML" -S /dev/null -l ./ --dpdk \
"${SURI_SET_ARGS[@]}" > "$SURILOG" 2>&1 &
SURIPID=$!
# Poll: wait for "Engine started" (success) or process exit (failure).
ENGINE_STARTED=false
SURI_EXIT=0
# $SECONDS is a bash builtin: total seconds since shell start (does not wrap at 60).
T0=$SECONDS
while [ $((SECONDS - T0)) -lt "$TIMEOUT_SEC" ]; do
if grep -q "Engine started" "$SURILOG" 2>/dev/null; then
ENGINE_STARTED=true
break
fi
if ! kill -0 "$SURIPID" 2>/dev/null; then
wait "$SURIPID"
SURI_EXIT=$?
break
fi
sleep 0.2
done
# Clean up: kill Suricata if still running (engine started or timeout).
if kill -0 "$SURIPID" 2>/dev/null; then
kill "$SURIPID" 2>/dev/null
wait "$SURIPID" 2>/dev/null || true
fi
# Evaluate results
RES=0
echo "=========================================="
echo " DPDK checklog: $(basename "$YAML")"
echo " expect=$EXPECT exit=$SURI_EXIT engine_started=$ENGINE_STARTED"
echo "=========================================="
# Check startup expectation
if [ "$EXPECT" = "start" ]; then
if [ "$ENGINE_STARTED" = true ]; then
echo " PASS Engine started as expected"
else
echo " FAIL Engine did NOT start (expected start)"
RES=1
fi
elif [ "$EXPECT" = "fail" ]; then
if [ "$ENGINE_STARTED" = false ] && [ "$SURI_EXIT" -ne 0 ]; then
echo " PASS Engine failed as expected (exit=$SURI_EXIT)"
else
echo " FAIL Engine did not fail as expected (exit=$SURI_EXIT, started=$ENGINE_STARTED)"
RES=1
fi
fi
# Check log patterns
for pattern in "${LOG_CHECKS[@]}"; do
if grep -qE "$pattern" "$SURILOG"; then
echo " PASS log-check: $pattern"
else
echo " FAIL log-check: $pattern"
RES=1
fi
done
echo "=========================================="
# Dump log on failure for debugging
if [ "$RES" -ne 0 ]; then
echo ""
echo "--- Suricata log (failure) ---"
cat "$SURILOG"
echo "--- end log ---"
fi
exit $RES

@ -0,0 +1,190 @@
#!/bin/bash
#
# DPDK run test suite.
#
#
# Usage:
# dpdk-testsuite.sh
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CHECKLOG="${SCRIPT_DIR}/dpdk-checklog.sh"
IDS_YAML="${SCRIPT_DIR}/suricata-null-ids.yaml"
BOND_YAML="${SCRIPT_DIR}/suricata-null-bond.yaml"
PASS=0
FAIL=0
run_test() {
local description="$1"; shift
echo ""
echo "=== TEST: ${description} ==="
local t0=$SECONDS
if bash "$CHECKLOG" "$@"; then
echo " (${description}: $((SECONDS - t0))s)"
PASS=$((PASS + 1))
else
echo " (${description}: $((SECONDS - t0))s)"
FAIL=$((FAIL + 1))
echo "^^^ FAILED: ${description}"
fi
}
# IDS (single interface: net_null0, 16 rx/tx descriptors)
run_test "IDS: auto mempool, auto cache (1 thread)" \
--interface-cfg-set net_null0.threads=1 \
--interface-cfg-set net_null0.mempool-size=auto \
--interface-cfg-set net_null0.mempool-cache-size=auto \
--suricata-log-check-grep "1 packet mempools of size 31, cache size 1" \
--expect-start \
"$IDS_YAML"
run_test "IDS: auto mempool, auto cache (2 threads)" \
--interface-cfg-set net_null0.threads=2 \
--interface-cfg-set net_null0.mempool-size=auto \
--interface-cfg-set net_null0.mempool-cache-size=auto \
--suricata-log-check-grep "2 packet mempools of size 31, cache size 1" \
--expect-start \
"$IDS_YAML"
run_test "IDS: auto mempool, static cache=1 (1 thread)" \
--interface-cfg-set net_null0.threads=1 \
--interface-cfg-set net_null0.mempool-size=auto \
--interface-cfg-set net_null0.mempool-cache-size=1 \
--suricata-log-check-grep "1 packet mempools of size 31, cache size 1" \
--expect-start \
"$IDS_YAML"
run_test "IDS: auto mempool, static cache=1 (2 threads)" \
--interface-cfg-set net_null0.threads=2 \
--interface-cfg-set net_null0.mempool-size=auto \
--interface-cfg-set net_null0.mempool-cache-size=1 \
--suricata-log-check-grep "2 packet mempools of size 31, cache size 1" \
--expect-start \
"$IDS_YAML"
run_test "IDS: auto mempool, oversized cache=1024 (fail)" \
--interface-cfg-set net_null0.threads=1 \
--interface-cfg-set net_null0.mempool-size=auto \
--interface-cfg-set net_null0.mempool-cache-size=1024 \
--suricata-log-check-grep "mempool cache size requires a positive number" \
--expect-fail \
"$IDS_YAML"
run_test "IDS: static mempool=1023, auto cache (1 thread)" \
--interface-cfg-set net_null0.threads=1 \
--interface-cfg-set net_null0.mempool-size=1023 \
--interface-cfg-set net_null0.mempool-cache-size=auto \
--suricata-log-check-grep "1 packet mempools of size 1023, cache size 341" \
--expect-start \
"$IDS_YAML"
# 2^n - 1 mempool size recommendation
run_test "IDS: static mempool=1024, auto cache (1 thread)" \
--interface-cfg-set net_null0.threads=1 \
--interface-cfg-set net_null0.mempool-size=1024 \
--interface-cfg-set net_null0.mempool-cache-size=auto \
--suricata-log-check-grep "1 packet mempools of size 1023, cache size 341" \
--expect-start \
"$IDS_YAML"
run_test "IDS: static mempool=1023, auto cache (2 threads)" \
--interface-cfg-set net_null0.threads=2 \
--interface-cfg-set net_null0.mempool-size=1023 \
--interface-cfg-set net_null0.mempool-cache-size=auto \
--suricata-log-check-grep "2 packet mempools of size 511, cache size 73" \
--expect-start \
"$IDS_YAML"
run_test "IDS: mempool too small (fail)" \
--interface-cfg-set net_null0.threads=1 \
--interface-cfg-set net_null0.mempool-size=15 \
--suricata-log-check-grep "mempool size is likely too small" \
--expect-fail \
"$IDS_YAML"
run_test "IDS: auto descriptors, auto mempool (OOM expected with nohuge)" \
--interface-cfg-set net_null0.threads=1 \
--interface-cfg-set net_null0.mempool-size=auto \
--interface-cfg-set net_null0.mempool-cache-size=auto \
--interface-cfg-set net_null0.rx-descriptors=auto \
--interface-cfg-set net_null0.tx-descriptors=auto \
--suricata-log-check-grep "1 packet mempools of size 65535, cache size 257" \
--suricata-log-check-grep "rte_pktmbuf_pool_create failed" \
--expect-fail \
"$IDS_YAML"
# Bond (net_bonding0, 2 members, 16 rx/tx descriptors)
run_test "Bond: auto mempool, auto cache (1 thread)" \
--interface-cfg-set net_bonding0.threads=1 \
--interface-cfg-set net_bonding0.mempool-size=auto \
--interface-cfg-set net_bonding0.mempool-cache-size=auto \
--suricata-log-check-grep "1 packet mempools of size 63, cache size 21" \
--expect-start \
"$BOND_YAML"
run_test "Bond: auto mempool, auto cache (2 threads)" \
--interface-cfg-set net_bonding0.threads=2 \
--interface-cfg-set net_bonding0.mempool-size=auto \
--interface-cfg-set net_bonding0.mempool-cache-size=auto \
--suricata-log-check-grep "2 packet mempools of size 63, cache size 21" \
--expect-start \
"$BOND_YAML"
run_test "Bond: auto mempool, static cache=7 (1 thread)" \
--interface-cfg-set net_bonding0.threads=1 \
--interface-cfg-set net_bonding0.mempool-size=auto \
--interface-cfg-set net_bonding0.mempool-cache-size=7 \
--suricata-log-check-grep "1 packet mempools of size 63, cache size 7" \
--expect-start \
"$BOND_YAML"
run_test "Bond: auto mempool, static cache=7 (2 threads)" \
--interface-cfg-set net_bonding0.threads=2 \
--interface-cfg-set net_bonding0.mempool-size=auto \
--interface-cfg-set net_bonding0.mempool-cache-size=7 \
--suricata-log-check-grep "2 packet mempools of size 63, cache size 7" \
--expect-start \
"$BOND_YAML"
run_test "Bond: auto mempool, oversized cache=1024 (fail)" \
--interface-cfg-set net_bonding0.threads=1 \
--interface-cfg-set net_bonding0.mempool-size=auto \
--interface-cfg-set net_bonding0.mempool-cache-size=1024 \
--suricata-log-check-grep "mempool cache size requires a positive number" \
--expect-fail \
"$BOND_YAML"
run_test "Bond: static mempool=1023, auto cache (1 thread)" \
--interface-cfg-set net_bonding0.threads=1 \
--interface-cfg-set net_bonding0.mempool-size=1023 \
--interface-cfg-set net_bonding0.mempool-cache-size=auto \
--suricata-log-check-grep "1 packet mempools of size 1023, cache size 341" \
--expect-start \
"$BOND_YAML"
run_test "Bond: static mempool=1023, auto cache (2 threads)" \
--interface-cfg-set net_bonding0.threads=2 \
--interface-cfg-set net_bonding0.mempool-size=1023 \
--interface-cfg-set net_bonding0.mempool-cache-size=auto \
--suricata-log-check-grep "2 packet mempools of size 511, cache size 73" \
--expect-start \
"$BOND_YAML"
run_test "Bond: mempool too small (fail)" \
--interface-cfg-set net_bonding0.threads=1 \
--interface-cfg-set net_bonding0.mempool-size=15 \
--suricata-log-check-grep "mempool size is likely too small" \
--expect-fail \
"$BOND_YAML"
# Summary
echo ""
echo "=========================================="
echo " DPDK test suite: ${PASS} passed, ${FAIL} failed"
echo "=========================================="
exit $FAIL

@ -0,0 +1,42 @@
%YAML 1.1
---
outputs:
- eve-log:
enabled: yes
filetype: regular
append: false
filename: eve.json
level: Info
types:
- stats:
totals: yes
dpdk:
eal-params:
proc-type: primary
vdev: ["net_null0", "net_null1", "net_bonding0,member=net_null0,member=net_null1,mode=0"]
no-huge:
no-pci:
m: 256
interfaces:
- interface: net_bonding0
threads: 1
mempool-size: auto
mempool-cache-size: auto
rx-descriptors: 16
tx-descriptors: 16
copy-mode: none
copy-iface: none
threading:
set-cpu-affinity: yes
cpu-affinity:
- management-cpu-set:
cpu: [ 0 ]
- worker-cpu-set:
cpu: [ "all" ]
mode: "exclusive"
unix-command:
enabled: auto
Loading…
Cancel
Save