Files
nginx-ipng-stats-plugin/Makefile
Pim van Pelt 8e0b1cdde9 PRE-RELEASE v0.7.0
Self-heal device= → ifindex attribution and expose plugin meta
counters in the scrape.

ipng_stats_rescan_interval (default 60s, 0 to disable) runs a
per-worker timer that re-resolves every binding via if_nametoindex,
so interface teardown/recreate (e.g. GRE tunnel reprovision) picks
up the new ifindex without requiring an nginx reload.

nginx_ipng_ifindex_misses_total increments whenever a cmsg-reported
ingress ifindex doesn't match any binding — making stale mappings
observable. Also expose the existing zone_full_events and
flushes_total shared-memory counters, which were tracked but never
emitted. JSON output gains a top-level "meta" object; schema stays
at 2 (additive change).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 19:37:15 +02:00

253 lines
10 KiB
Makefile

# SPDX-License-Identifier: Apache-2.0
# Makefile for nginx-ipng-stats-plugin
#
# Targets:
# build - build ngx_http_ipng_stats_module.so out-of-tree.
# pkg-deb - build a .deb via dpkg-buildpackage for the current release.
# robot-test - build .deb, then run Robot Framework end-to-end tests
# in containerlab (requires docker + containerlab).
# install-deps - install build and test dependencies via apt.
# clean - remove build artifacts and the fetched nginx source tree.
# help - print this help.
#
# Overridable variables:
# NGINX_SRC - path to an unpacked nginx source tree. If unset, the
# `build` target will apt-source one into ./build/nginx-src.
MODULE_NAME := ngx_http_ipng_stats_module
MODULE_DIR := $(CURDIR)
BUILD_DIR := $(CURDIR)/build
# Single source of truth for the module version. When cutting a release,
# bump this AND add a matching top entry to debian/changelog — dpkg reads
# the package version from there directly. The C code picks up VERSION
# via the generated src/version.h (written by the version-header target
# below and depended on by the module build).
VERSION := 0.7.0
NGINX_SRC ?=
.PHONY: help build build-asan pkg-deb robot-test install-deps clean fetch-nginx-src version-header
TEST ?= tests/
help:
@echo "nginx-ipng-stats-plugin — make targets"
@echo ""
@echo " make build Build $(MODULE_NAME).so out-of-tree."
@echo " make build-asan Build a full nginx + module with AddressSanitizer"
@echo " into build/nginx-asan/ for local crash-hunting."
@echo " make pkg-deb Build a Debian package via dpkg-buildpackage."
@echo " make robot-test Run Robot Framework e2e tests (all suites)."
@echo " make install-deps Install build and test dependencies (apt)."
@echo " make clean Remove build artifacts."
@echo ""
@echo "Overridable:"
@echo " NGINX_SRC=<path> Use an existing nginx source tree."
@echo " TEST=<path> Run a specific .robot file (default: tests/)."
# ----------------------------------------------------------------------
# build: out-of-tree dynamic module build
# ----------------------------------------------------------------------
build: $(BUILD_DIR)/$(MODULE_NAME).so
@echo ""
@echo "Built: $(BUILD_DIR)/$(MODULE_NAME).so"
@echo ""
@echo "To try it locally without installing a .deb:"
@echo " sudo install -m 0644 $(BUILD_DIR)/$(MODULE_NAME).so /usr/lib/nginx/modules/"
@echo " echo 'load_module modules/$(MODULE_NAME).so;' | sudo tee /etc/nginx/modules-enabled/50-mod-http-ipng-stats.conf"
@echo " sudo nginx -t && sudo nginx -s reload"
# version-header: write src/version.h iff its contents would change. The
# target is .PHONY so it's re-evaluated every build, but the file itself
# is only touched on VERSION bumps — keeps the .so from rebuilding when
# nothing has actually changed.
version-header:
@NEW='#define NGX_HTTP_IPNG_STATS_VERSION "$(VERSION)"'; \
if [ ! -f $(MODULE_DIR)/src/version.h ] \
|| [ "$$NEW" != "$$(cat $(MODULE_DIR)/src/version.h)" ]; then \
echo "Generating src/version.h (VERSION=$(VERSION))"; \
echo "$$NEW" > $(MODULE_DIR)/src/version.h; \
fi
$(BUILD_DIR)/$(MODULE_NAME).so: version-header fetch-nginx-src
@set -e; \
if [ -z "$(NGINX_SRC)" ]; then \
NGX_SRC="$(BUILD_DIR)/nginx-src"; \
else \
NGX_SRC="$(NGINX_SRC)"; \
fi; \
echo "Configuring nginx in $$NGX_SRC against module at $(MODULE_DIR)"; \
cd "$$NGX_SRC" && ./configure --with-compat --add-dynamic-module=$(MODULE_DIR); \
echo "Building module"; \
$(MAKE) -C "$$NGX_SRC" -f objs/Makefile modules; \
mkdir -p $(BUILD_DIR); \
cp "$$NGX_SRC/objs/$(MODULE_NAME).so" $(BUILD_DIR)/$(MODULE_NAME).so
fetch-nginx-src:
@set -e; \
if [ -n "$(NGINX_SRC)" ]; then \
echo "Using NGINX_SRC=$(NGINX_SRC)"; \
exit 0; \
fi; \
if [ -d "$(BUILD_DIR)/nginx-src" ] && [ -f "$(BUILD_DIR)/nginx-src/configure" ]; then \
echo "Reusing $(BUILD_DIR)/nginx-src"; \
exit 0; \
fi; \
mkdir -p $(BUILD_DIR); \
if [ -d /usr/share/nginx/src ] && [ -f /usr/share/nginx/src/configure ]; then \
echo "Copying /usr/share/nginx/src (from nginx-dev) to $(BUILD_DIR)/nginx-src"; \
rm -rf $(BUILD_DIR)/nginx-src; \
cp -a /usr/share/nginx/src $(BUILD_DIR)/nginx-src; \
chmod -R u+w $(BUILD_DIR)/nginx-src; \
exit 0; \
fi; \
rm -rf $(BUILD_DIR)/apt-src; \
mkdir -p $(BUILD_DIR)/apt-src; \
echo "Fetching nginx source via \`apt source nginx\` in $(BUILD_DIR)/apt-src"; \
cd $(BUILD_DIR)/apt-src && apt source nginx; \
NGX_DIR=$$(find $(BUILD_DIR)/apt-src -maxdepth 1 -type d -name 'nginx-*' | head -n1); \
if [ -z "$$NGX_DIR" ]; then \
echo "error: could not find unpacked nginx source tree under $(BUILD_DIR)/apt-src" >&2; \
exit 1; \
fi; \
rm -rf $(BUILD_DIR)/nginx-src; \
mv "$$NGX_DIR" $(BUILD_DIR)/nginx-src; \
rm -rf $(BUILD_DIR)/apt-src
# ----------------------------------------------------------------------
# build-asan: full nginx + module built with AddressSanitizer. ASan
# needs to be present in the main binary to instrument dynamic modules;
# running a stock nginx with LD_PRELOAD=libasan.so does NOT work with
# a statically-linked module's allocations. So we build nginx itself
# here, into an isolated prefix under build/nginx-asan/, which you can
# run by hand against any config. Normal `make build` is unaffected.
#
# Typical workflow for the reload-crash hunt:
# make build-asan
# cp your-test.conf build/nginx-asan/conf/nginx.conf
# ASAN_OPTIONS=detect_odr_violation=0:abort_on_error=1:halt_on_error=1:detect_leaks=0 \
# build/nginx-asan/sbin/nginx -p build/nginx-asan
# # in another shell:
# build/nginx-asan/sbin/nginx -p build/nginx-asan -s reload
# ASan writes any findings to stderr of the master before aborting.
#
# detect_odr_violation=0 suppresses false positives for symbols nginx
# intentionally duplicates between its main binary and dynamic modules
# (e.g. ngx_module_names in objs/ngx_http_ipng_stats_module_modules.c).
# ----------------------------------------------------------------------
# -fno-sanitize=nonnull-attribute suppresses a UBSan finding in nginx's
# ngx_cpymem (src/core/ngx_string.c:84): the macro expands to memcpy(...)
# which glibc declares _Nonnull, but nginx legitimately calls it with
# (dst, NULL, 0) in many places. Filtering just that check keeps real
# undefined behaviour visible.
ASAN_CFLAGS := -fsanitize=address -fsanitize=undefined -fno-sanitize=nonnull-attribute -fno-omit-frame-pointer -fno-common -g -O1
ASAN_LDFLAGS := -fsanitize=address -fsanitize=undefined
# The ASan build rebuilds nginx from source rather than only the module,
# because ASan must instrument the main binary to catch heap issues in
# modules it loads. The `nginx-dev` shim source tree under /usr/share
# only carries headers, so we always fall through to `apt source nginx`
# here and keep the full source tree isolated in build/nginx-asan-src/.
build-asan: version-header
@set -e; \
NGX_SRC="$(BUILD_DIR)/nginx-asan-src"; \
if [ ! -d "$$NGX_SRC" ] || [ ! -f "$$NGX_SRC/src/core/nginx.c" ]; then \
echo "Fetching full nginx source via apt-source for ASan build"; \
mkdir -p $(BUILD_DIR)/apt-src-asan; \
rm -rf $$NGX_SRC; \
cd $(BUILD_DIR)/apt-src-asan && apt source nginx; \
NGX_DIR=$$(find $(BUILD_DIR)/apt-src-asan -maxdepth 1 -type d -name 'nginx-*' | head -n1); \
if [ -z "$$NGX_DIR" ]; then \
echo "error: could not find unpacked nginx source tree" >&2; \
exit 1; \
fi; \
mv "$$NGX_DIR" $$NGX_SRC; \
rm -rf $(BUILD_DIR)/apt-src-asan; \
fi; \
PREFIX="$(BUILD_DIR)/nginx-asan"; \
echo "Configuring ASan nginx in $$NGX_SRC (prefix=$$PREFIX)"; \
cd "$$NGX_SRC" && ./configure \
--prefix="$$PREFIX" \
--with-cc-opt="$(ASAN_CFLAGS)" \
--with-ld-opt="$(ASAN_LDFLAGS)" \
--with-compat \
--with-debug \
--add-dynamic-module=$(MODULE_DIR); \
echo "Building ASan nginx + module (this takes a minute)"; \
$(MAKE) -C "$$NGX_SRC" -f objs/Makefile build; \
$(MAKE) -C "$$NGX_SRC" -f objs/Makefile install; \
install -d $$PREFIX/modules; \
install -m 0644 "$$NGX_SRC/objs/$(MODULE_NAME).so" $$PREFIX/modules/; \
for d in client_body_temp fastcgi_temp proxy_temp scgi_temp uwsgi_temp; do \
rm -rf "$$PREFIX/$$d"; \
done; \
echo ""; \
echo "ASan build ready:"; \
echo " nginx: $$PREFIX/sbin/nginx"; \
echo " module: $$PREFIX/modules/$(MODULE_NAME).so"; \
echo ""; \
echo "Run with:"; \
echo " ASAN_OPTIONS=detect_odr_violation=0:abort_on_error=1:halt_on_error=1:detect_leaks=0 \\"; \
echo " $$PREFIX/sbin/nginx -p $$PREFIX"
# ----------------------------------------------------------------------
# pkg-deb: build a .deb
# ----------------------------------------------------------------------
pkg-deb:
dpkg-buildpackage -us -uc -b
@mkdir -p $(BUILD_DIR)
@# dpkg-buildpackage writes artifacts to ../ — relocate them into
@# $(BUILD_DIR) so everything ephemeral lives under build/.
@for f in ../libnginx-mod-http-ipng-stats*.deb \
../libnginx-mod-http-ipng-stats*.ddeb \
../nginx-ipng-stats-plugin_*.buildinfo \
../nginx-ipng-stats-plugin_*.changes; do \
if [ -f "$$f" ]; then mv -f "$$f" $(BUILD_DIR)/; fi; \
done
@echo ""
@echo "Resulting .deb(s):"
@ls -1 $(BUILD_DIR)/*.deb 2>/dev/null || true
# ----------------------------------------------------------------------
# clean
# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
# robot-test: containerlab + Robot Framework end-to-end tests
# ----------------------------------------------------------------------
tests/.venv: tests/requirements.txt
python3 -m venv tests/.venv
tests/.venv/bin/pip install -q -r tests/requirements.txt
robot-test: tests/.venv
tests/rf-run.sh docker $(TEST)
# ----------------------------------------------------------------------
# install-deps: install build and test dependencies
# ----------------------------------------------------------------------
install-deps:
sudo apt-get update -qq
sudo apt-get install -y \
nginx-dev dpkg-dev debhelper \
python3 python3-venv \
curl
@echo ""
@echo "Build dependencies installed. For 'make robot-test' you also need:"
@echo " - docker: https://docs.docker.com/engine/install/debian/"
@echo " - containerlab: https://containerlab.dev/install/"
# ----------------------------------------------------------------------
# clean
# ----------------------------------------------------------------------
clean:
rm -rf $(BUILD_DIR) tests/.venv tests/out
rm -f $(MODULE_DIR)/src/version.h
-dh_clean 2>/dev/null || true