Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ ENABLED_EXTENSION_MANIFESTS="$LOCAL_BOOTSTRAP_MANIFEST"
# §3.6 and anygpt-42. When set to 1 the prod-host install path passes
# USE_AF_XDP=1 to make and rejects a cached AF_PACKET-only binary.
ANYSCAN_USE_AF_XDP="${ANYSCAN_USE_AF_XDP:-0}"
# Build-time DPDK opt-in. Mirrors ANYSCAN_USE_AF_XDP / ANYSCAN_USE_PFRING_ZC.
# When 1, deploy.sh forwards `USE_DPDK=1` to make and rejects a cached
# non-DPDK binary the same way it does for the AF_XDP cache path.
# See plans/2026-04-28-portscan-dpdk-impl-v1.md §3.10.3.
ANYSCAN_USE_DPDK="${ANYSCAN_USE_DPDK:-0}"
# Build-time PF_RING ZC opt-in (anygpt-46). Same shape as
# ANYSCAN_USE_AF_XDP but probes for libpfring linkage and forwards
# USE_PFRING_ZC=1 to make. PF_RING ZC requires a commercial ntop license
Expand Down Expand Up @@ -126,6 +131,22 @@ binary_has_afxdp_linkage() {
return 1
}

binary_has_dpdk_linkage() {
local bin="$1"
[ -x "$bin" ] || return 1
if command -v ldd >/dev/null 2>&1; then
if ldd "$bin" 2>/dev/null | grep -q 'librte_eal\.so'; then
return 0
fi
fi
if command -v readelf >/dev/null 2>&1; then
if readelf -d "$bin" 2>/dev/null | grep -E '\(NEEDED\)' | grep -q 'librte_eal\.so'; then
return 0
fi
fi
return 1
}

binary_has_pfring_zc_linkage() {
local bin="$1"
[ -x "$bin" ] || return 1
Expand Down Expand Up @@ -255,6 +276,9 @@ install_vulnscanner_binary() {
if [ "${ANYSCAN_USE_PFRING_ZC:-0}" = "1" ]; then
make_args+=("USE_PFRING_ZC=1")
fi
if [ "${ANYSCAN_USE_DPDK:-0}" = "1" ]; then
make_args+=("USE_DPDK=1")
fi
# When AF_XDP is requested but the cached source binary lacks libxdp
# linkage, drop it so the build branch below fires. anygpt-42: the
# previous logic short-circuited on the existence of a stale
Expand Down Expand Up @@ -283,6 +307,18 @@ install_vulnscanner_binary() {
make -C "$VULNSCANNER_SOURCE_DIR" clean >/dev/null 2>&1 || true
fi
fi
# Same logic for DPDK: drop a non-DPDK cached binary so the build path
# with USE_DPDK=1 fires. plans/2026-04-28-portscan-dpdk-impl-v1.md §3.10.3.
if [ "${ANYSCAN_USE_DPDK:-0}" = "1" ] \
&& [ -x "$VULNSCANNER_SOURCE_BIN" ] \
&& ! binary_has_dpdk_linkage "$VULNSCANNER_SOURCE_BIN"; then
printf '[*] Removing pre-DPDK scanner at %s so the build path with USE_DPDK=1 fires.\n' \
"$VULNSCANNER_SOURCE_BIN"
rm -f "$VULNSCANNER_SOURCE_BIN"
if [ -f "$VULNSCANNER_SOURCE_DIR/Makefile" ] && command -v make >/dev/null 2>&1; then
make -C "$VULNSCANNER_SOURCE_DIR" clean >/dev/null 2>&1 || true
fi
fi
if [ -x "$VULNSCANNER_SOURCE_BIN" ]; then
source_bin="$VULNSCANNER_SOURCE_BIN"
printf '[*] Installing VulnScanner binary from %s...\n' "$source_bin"
Expand Down Expand Up @@ -319,6 +355,12 @@ install_vulnscanner_binary() {
return 1
fi

if [ "${ANYSCAN_USE_DPDK:-0}" = "1" ] && ! binary_has_dpdk_linkage "$source_bin"; then
printf '[!] ANYSCAN_USE_DPDK=1 but %s does not link librte_eal.so. Install libdpdk-dev and re-run.\n' \
"$source_bin" >&2
return 1
fi

install -m 0755 "$source_bin" "$VULNSCANNER_INSTALL_BIN"
return 0
}
Expand Down
115 changes: 115 additions & 0 deletions install-external-deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,23 @@ ANYSCAN_USE_AF_XDP="${ANYSCAN_USE_AF_XDP:-0}"
# the runtime-side gating knob ANYSCAN_PFRING_ZC_AVAILABLE.
ANYSCAN_USE_PFRING_ZC="${ANYSCAN_USE_PFRING_ZC:-0}"

# Build-time DPDK opt-in. Mirrors ANYSCAN_USE_AF_XDP / ANYSCAN_USE_PFRING_ZC.
# When 1 the engine make is invoked with `USE_DPDK=1` so the scanner gets
# librte_eal + librte_ethdev + librte_mbuf + librte_net_ena linked in and the
# io_engine_dpdk vtable in src/engine.c is reachable from pick_io_engine().
# Without this flag, --io-engine=dpdk fails at parse time with
# "binary not built with USE_DPDK=1".
#
# DPDK additionally requires HOST setup the apt-get install does NOT cover —
# hugepages reserved + the target NIC bound to vfio-pci. Those are owned by
# tools/setup-dpdk.sh (idempotent + reversible). install-worker-bundle.sh's
# probe_dpdk_runtime_available checks both at runtime so an in-place upgrade
# that flipped USE_DPDK=1 but never ran the host-setup script gets
# ANYSCAN_DPDK_AVAILABLE=false and the adapter falls back to af_packet.
#
# See plans/2026-04-28-portscan-dpdk-impl-v1.md §3.10 for the full wire-up.
ANYSCAN_USE_DPDK="${ANYSCAN_USE_DPDK:-0}"

# Opt-in kernel backport upgrade. Default 0 leaves the running kernel
# untouched (existing AMIs unchanged). Setting 1 installs a Debian
# backports kernel image so the host can run kernel 6.16+ with the
Expand Down Expand Up @@ -155,6 +172,29 @@ binary_has_pfring_zc_linkage() {
return 1
}

# True when the existing scanner binary was linked against librte_eal at
# build time. The DPDK build path (USE_DPDK=1) pulls in libdpdk via
# pkg-config which produces ~50 -lrte_* link flags; we probe for librte_eal
# specifically because every DPDK-built binary links it (it's the EAL core
# library) and PMD-only / mempool-only DPDK applications still need it.
# Same ldd → readelf -d fallback shape as binary_has_afxdp_linkage so the
# check works on hosts that strip glibc.
binary_has_dpdk_linkage() {
local bin="$1"
[ -x "$bin" ] || return 1
if command -v ldd >/dev/null 2>&1; then
if ldd "$bin" 2>/dev/null | grep -q 'librte_eal\.so'; then
return 0
fi
fi
if command -v readelf >/dev/null 2>&1; then
if readelf -d "$bin" 2>/dev/null | grep -E '\(NEEDED\)' | grep -q 'librte_eal\.so'; then
return 0
fi
fi
return 1
}

# Resolve the make argv once so install/bundle/deploy paths produce
# byte-identical invocations and the unit tests in
# tools/test-install-external-deps-{afxdp,pfring-zc}.sh can assert the
Expand All @@ -167,6 +207,9 @@ vulnscanner_make_args() {
if [ "${ANYSCAN_USE_PFRING_ZC:-0}" = "1" ]; then
printf 'USE_PFRING_ZC=1\n'
fi
if [ "${ANYSCAN_USE_DPDK:-0}" = "1" ]; then
printf 'USE_DPDK=1\n'
fi
}

# Lexicographic numeric compare of two `<major>.<minor>` version strings.
Expand Down Expand Up @@ -421,6 +464,59 @@ install_pfring_zc_build_deps() {
fi
}

# Install build-time dependencies for the DPDK I/O path the scanner gains
# under USE_DPDK=1 in the engine Makefile (-lrte_eal -lrte_ethdev -lrte_mbuf
# etc, pulled in via `pkg-config --libs libdpdk`). libdpdk-dev is in main on
# Debian bookworm/trixie + Ubuntu 24.04 noble. Same fail-open semantics as
# install_afxdp_build_deps: skip if apt-get missing, skip if no privilege,
# skip if sudo would prompt. The default `make` does not need these
# packages — only `make USE_DPDK=1` does — so failure to install just
# means USE_DPDK=1 builds will fail loudly later, which is the correct
# escalation rather than silently producing a non-DPDK binary.
#
# DPDK additionally requires HOST setup (hugepages + vfio-pci binding)
# that this function does NOT do — that lives in tools/setup-dpdk.sh and
# is the install-time, not build-time, prerequisite. The split exists
# because hugepages reservation modifies system memory pressure and
# binding NICs to vfio-pci removes them from kernel networking; both
# need an explicit operator action, not an apt-get side-effect.
#
# Set ANYSCAN_INSTALL_DPDK_DEPS=false to suppress this block (e.g. on
# AMIs where the operator pre-pinned a different libdpdk version).
install_dpdk_build_deps() {
if [ "${ANYSCAN_INSTALL_DPDK_DEPS:-true}" != "true" ]; then
return 0
fi
if ! command -v apt-get >/dev/null 2>&1; then
printf '[*] Skipping DPDK build deps: apt-get not on PATH (non-Debian host).\n'
return 0
fi
local apt_cmd=()
if [ "$(id -u 2>/dev/null || echo 1)" = "0" ]; then
apt_cmd=(apt-get)
elif command -v sudo >/dev/null 2>&1; then
apt_cmd=(sudo -n apt-get)
else
printf '[*] Skipping DPDK build deps: not root and sudo is not available.\n'
printf ' Install manually if you plan to build the scanner with USE_DPDK=1:\n'
printf ' sudo apt-get install -y libdpdk-dev dpdk\n'
return 0
fi
if [ "${apt_cmd[0]}" = "sudo" ] && ! sudo -n true >/dev/null 2>&1; then
printf '[*] Skipping DPDK build deps: sudo would prompt for a password.\n'
printf ' Install manually if you plan to build the scanner with USE_DPDK=1:\n'
printf ' sudo apt-get install -y libdpdk-dev dpdk\n'
return 0
fi
printf '[*] Installing DPDK build deps (libdpdk-dev dpdk)...\n'
if ! "${apt_cmd[@]}" install -y --no-install-recommends \
libdpdk-dev dpdk >/dev/null 2>&1; then
printf '[!] apt-get install of DPDK build deps failed; the scanner will still build with default `make`.\n' >&2
printf ' Re-run with USE_DPDK=1 only after libdpdk-dev is present.\n' >&2
return 0
fi
}

upsert_env_value() {
local key="$1"
local value="$2"
Expand Down Expand Up @@ -453,6 +549,7 @@ fi

install_afxdp_build_deps
install_pfring_zc_build_deps
install_dpdk_build_deps
install_kernel_backport_if_requested

if [ -d "$VULNSCANNER_REPO_DIR/.git" ]; then
Expand Down Expand Up @@ -491,6 +588,18 @@ elif [ "$ANYSCAN_USE_PFRING_ZC" = "1" ] && ! binary_has_pfring_zc_linkage "$VULN
fi
rm -f "$VULNSCANNER_BIN_PATH"
need_build=1
elif [ "$ANYSCAN_USE_DPDK" = "1" ] && ! binary_has_dpdk_linkage "$VULNSCANNER_BIN_PATH"; then
# Same shape as the AF_XDP / PF_RING cache checks: force clean
# rebuild when the cached binary lacks librte_eal linkage. Without
# this the cache short-circuit above would keep shipping a non-DPDK
# binary and --io-engine=dpdk would error at parse time with
# "binary not built with USE_DPDK=1".
printf '[*] Existing scanner at %s lacks librte_eal linkage; forcing rebuild because ANYSCAN_USE_DPDK=1.\n' "$VULNSCANNER_BIN_PATH"
if [ -f "$VULNSCANNER_REPO_DIR/Makefile" ] && command -v make >/dev/null 2>&1; then
make -C "$VULNSCANNER_REPO_DIR" clean >/dev/null 2>&1 || true
fi
rm -f "$VULNSCANNER_BIN_PATH"
need_build=1
fi

if [ "$need_build" = "1" ]; then
Expand Down Expand Up @@ -526,6 +635,12 @@ if [ "$ANYSCAN_USE_PFRING_ZC" = "1" ] && ! binary_has_pfring_zc_linkage "$VULNSC
exit 1
fi

if [ "$ANYSCAN_USE_DPDK" = "1" ] && ! binary_has_dpdk_linkage "$VULNSCANNER_BIN_PATH"; then
printf '[!] ANYSCAN_USE_DPDK=1 but %s does not link librte_eal.so. Build deps were probably missing — install libdpdk-dev and re-run.\n' \
"$VULNSCANNER_BIN_PATH" >&2
exit 1
fi

mkdir -p "$(dirname "$LOCAL_ENV_FILE")" "$LOCAL_BOOTSTRAP_ARTIFACT_DIR"
printf '[*] Writing repo-local AnyScan env snippet to %s...\n' "$LOCAL_ENV_FILE"
touch "$LOCAL_ENV_FILE"
Expand Down
105 changes: 105 additions & 0 deletions install-worker-bundle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,110 @@ apply_afxdp_availability() {
fi
}

# True when the installed scanner binary at $1 was linked against librte_eal
# at build time (i.e. compiled with USE_DPDK=1). Mirrors
# binary_has_afxdp_linkage / binary_has_pfring_zc_linkage; same ldd →
# readelf -d fallback so the check works on stripped or static glibc hosts.
binary_has_dpdk_linkage() {
local bin="$1"
[ -x "$bin" ] || return 1
if command_exists ldd; then
if ldd "$bin" 2>/dev/null | grep -q 'librte_eal\.so'; then
return 0
fi
fi
if command_exists readelf; then
if readelf -d "$bin" 2>/dev/null | grep -E '\(NEEDED\)' | grep -q 'librte_eal\.so'; then
return 0
fi
fi
return 1
}

probe_dpdk_runtime_available() {
# Phase 2 of plans/2026-04-28-portscan-dpdk-impl-v1.md §4.3. The bundled
# scanner can be invoked with --io-engine=dpdk only when ALL of:
# (a) the installed scanner binary at $VULNSCANNER_BIN_DEST was built
# with USE_DPDK=1 (probed by checking librte_eal linkage on the
# on-disk binary). Mirrors the (c) gate the PR #75 review added
# to probe_pfring_zc_runtime_available.
# (b) librte_eal.so is loadable on the host (the scanner is dynamically
# linked against it; without the runtime libs the scanner crashes
# at startup with a dlopen error).
# (c) the vfio-pci kernel module is loaded (the EAL bring-up calls
# rte_eal_init which probes vfio-pci-bound devices; without the
# module no DPDK port is reachable).
# (d) at least one hugepage is reserved (the DPDK mempool needs
# hugepage-backed memory for the mbuf pool — no hugepages →
# rte_pktmbuf_pool_create fails). We probe both 2 MiB and 1 GiB
# hugepage pools because tools/setup-dpdk.sh prefers 1 GiB when
# available and falls back to 2 MiB.
# (e) at least one NIC is bound to vfio-pci. We don't probe this
# directly because dpdk-devbind.py may not be on PATH yet at
# install time; instead we check that a vfio control device
# (/dev/vfio/vfio) exists, which is the kernel-side prerequisite
# that vfio-pci's binding step would have created.
# If any check fails ANYSCAN_DPDK_AVAILABLE=false and the adapter falls
# back to af_packet — no silent failure modes.
if ! binary_has_dpdk_linkage "$VULNSCANNER_BIN_DEST"; then
printf 'false'
return 0
fi
if ! command_exists ldconfig; then
printf 'false'
return 0
fi
if ! ldconfig -p 2>/dev/null | grep -q '\<librte_eal\.so'; then
printf 'false'
return 0
fi
if [ ! -d /sys/module/vfio_pci ] && [ ! -d /sys/module/vfio-pci ]; then
printf 'false'
return 0
fi
# Hugepages probe: walk /sys/kernel/mm/hugepages/* and assert that at
# least one page-size directory has nr_hugepages > 0. Matches both 2 MiB
# and 1 GiB pages without hard-coding which one is expected.
local hugepages_total=0
if [ -d /sys/kernel/mm/hugepages ]; then
local hp_dir count
for hp_dir in /sys/kernel/mm/hugepages/hugepages-*kB; do
[ -e "$hp_dir/nr_hugepages" ] || continue
count="$(cat "$hp_dir/nr_hugepages" 2>/dev/null || echo 0)"
if [[ "$count" =~ ^[0-9]+$ ]]; then
hugepages_total=$(( hugepages_total + count ))
fi
done
fi
if [ "$hugepages_total" -le 0 ]; then
printf 'false'
return 0
fi
if [ ! -e /dev/vfio/vfio ]; then
# The vfio control char device is created by the vfio-pci module
Comment on lines +503 to +504
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Gate DPDK readiness on a bound VFIO device, not /dev/vfio/vfio

probe_dpdk_runtime_available treats /dev/vfio/vfio as proof that a NIC is bound for DPDK, but that node can exist as soon as VFIO is loaded even when no PCI device is attached to vfio-pci. In that state this probe can write ANYSCAN_DPDK_AVAILABLE=true, so the adapter forwards --io-engine=dpdk and the scanner can still fail at runtime due to zero usable DPDK ports. The check should verify at least one bound device (for example via /sys/bus/pci/drivers/vfio-pci/* or a /dev/vfio/<group> node) rather than only the control device.

Useful? React with 👍 / 👎.

# when at least one device has been bound. If it's missing, no NIC
# has been bound yet — operator must run tools/setup-dpdk.sh bind.
printf 'false'
return 0
fi
printf 'true'
}

apply_dpdk_availability() {
# Mirror of apply_afxdp_availability / apply_pfring_zc_availability:
# always write the flag so /etc/agentd/runtime.env carries an explicit
# value and a partial upgrade can't leave a stale "true" in place
# after vfio-pci was unloaded or hugepages were freed.
local dpdk_available
dpdk_available="$(probe_dpdk_runtime_available)"
upsert_env_value "ANYSCAN_DPDK_AVAILABLE" "$dpdk_available" "$RUNTIME_ENV_FILE"
if [ "$dpdk_available" = "true" ]; then
printf '[*] DPDK runtime probe passed (binary + librte_eal.so + vfio_pci + hugepages + /dev/vfio); ANYSCAN_DPDK_AVAILABLE=true.\n'
else
printf '[*] DPDK runtime probe failed (binary not librte_eal-linked, librte_eal.so missing, vfio_pci unloaded, no hugepages reserved, or /dev/vfio absent); ANYSCAN_DPDK_AVAILABLE=false. Run tools/setup-dpdk.sh bind on the host to fix the runtime side.\n'
fi
}

# True when the installed scanner binary at $1 was linked against
# libpfring at build time (i.e. compiled with USE_PFRING_ZC=1). Same
# probe shape as binary_has_pfring_zc_linkage in install-external-deps.sh
Expand Down Expand Up @@ -875,6 +979,7 @@ main() {
apply_host_resource_defaults "$cpu_threads"
apply_afxdp_availability
apply_pfring_zc_availability
apply_dpdk_availability
apply_scanner_host_tunings

if [ "$existing_install" = "true" ]; then
Expand Down
Loading