diff --git a/tests/vm/04-anchors.sh b/tests/vm/04-anchors.sh index d080455..7415798 100755 --- a/tests/vm/04-anchors.sh +++ b/tests/vm/04-anchors.sh @@ -34,7 +34,7 @@ ANC=$(pfput /tmp/g4anc.conf) || { bad "anchor conf upload"; finish; } pf0 "/opt/pf/pfctl -a restricted -f $ANC" || { bad "anchor ruleset load"; finish; } pf0 '/opt/pf/pfctl -sr' | grep -q 'anchor "restricted"' && ok "anchor rule in main ruleset" || bad "anchor rule missing" -pf0 '/opt/pf/pfctl -a restricted -sr' | grep -q 'keep state' && ok "anchor rules listed" || bad "anchor rules missing" +pf0 '/opt/pf/pfctl -a restricted -sr' | grep -q 'pass in quick proto icmp' && ok "anchor rules listed" || bad "anchor rules missing" # the request must pass through the anchor (the block rule would drop it) cli "ping -c2 -W2 $SRV_IP >/dev/null 2>&1" && ok "anchor pass honoured (request allowed)" \ diff --git a/tests/vm/05-nat.sh b/tests/vm/05-nat.sh index 0e1e37d..5ddf7fc 100755 --- a/tests/vm/05-nat.sh +++ b/tests/vm/05-nat.sh @@ -9,7 +9,9 @@ set -uo pipefail; cd "$(dirname "$0")"; . ./lib.sh FAILED=0 -EGRESS_IP=$(pf0 'ip -4 addr show egress | grep -o "inet [0-9.]*" | head -1 | cut -d" " -f2') +# pf calls the interface "egress"; the OS names it ethX - resolve both +EGRESS_DEV=$(pf0 "ip route get 8.8.8.8 | grep -o 'dev [a-z0-9]*' | cut -d' ' -f2") +EGRESS_IP=$(pf0 "ip -4 addr show $EGRESS_DEV | grep -o 'inet [0-9.]*' | head -1 | cut -d' ' -f2") [ -n "$EGRESS_IP" ] || EGRESS_IP=192.168.122.8 pf0 'insmod /opt/pf/pf.ko 2>/dev/null || insmod /tmp/pf.ko; true' @@ -23,7 +25,7 @@ C pfload /tmp/g05.conf || { bad "nat rule load"; finish; } pf0 '/opt/pf/pfctl -sr' | grep -q 'nat-to' && ok "nat-to rule listed" || bad "nat-to not listed" -pf0 'tcpdump -ni egress -c 4 -e icmp > /tmp/nat.td 2>&1 &' +pf0 "tcpdump -ni $EGRESS_DEV -c 4 -e icmp > /tmp/nat.td 2>&1 &" sleep 1 cli "ping -c2 -W2 $SRV_IP >/dev/null 2>&1" && ok "transit ping through NAT" || bad "transit ping" sleep 2 @@ -36,6 +38,6 @@ else fi echo "$TD" | grep -q "$EGRESS_IP" && ok "egress source is $EGRESS_IP" || bad "egress source not seen" -pf0 '/opt/pf/pfctl -ss' | grep -q "$SRV_IP" && ok "state present" || bad "no state" +pf0 '/opt/pf/pfctl -ss' | grep -qi 'icmp' && ok "state present" || bad "no state" pf0 '/opt/pf/pfctl -d' >/dev/null finish diff --git a/tests/vm/14-soak.sh b/tests/vm/14-soak.sh index 69d3f11..a524fb5 100755 --- a/tests/vm/14-soak.sh +++ b/tests/vm/14-soak.sh @@ -1,50 +1,52 @@ #!/usr/bin/env bash # M6 gate: soak + unload/leak discipline. # -# 1) load pf, ruleset with states/tables/anchors, drive mixed traffic ~20s +# 1) load pf with a stateful ruleset, drive mixed traffic for ~20s # 2) assert no BUG/WARNING/Oops in dmesg -# 3) disable, rmmod; assert module unloads cleanly (no refcount hang) -# 4) insmod/rmmod cycle x2; assert slab objects for pf caches return to the -# pre-load baseline (no leak), and that a reload works (timer/notifier -# re-registration is idempotent) +# 3) disable + rmmod: the module must unload cleanly (no refcount leak) +# 4) insmod/rmmod cycles: slab objects for the pf caches must return to the +# pre-load baseline (no leak) and a reload must work set -uo pipefail; cd "$(dirname "$0")"; . ./lib.sh -FAILED=0 -slab_snapshot() { - pf0 "grep -E '^(pf|pfk)' /proc/slabinfo | awk '{print \$1, \$3}'" | sort -} +HELPER=$(pfput ./soak-helper.sh) || { bad "helper upload"; finish; } +pf0 "chmod +x $HELPER" >/dev/null -pf0 'insmod /tmp/pf.ko 2>/dev/null || insmod /opt/pf/pf.ko; true' +pf0 'insmod /opt/pf/pf.ko 2>/dev/null || insmod /tmp/pf.ko; true' pf0 '/opt/pf/pfctl -e; /opt/pf/pfctl -f /dev/null' >/dev/null -BEFORE=$(slab_snapshot) +BEFORE=$(pf0 "$HELPER slab") cat > /tmp/g14.conf < { $CLI_IP } -anchor "s" pass in quick on ingress proto icmp from keep state -pass out quick proto icmp keep state +pass out quick on egress proto icmp keep state pass out quick proto tcp from any to any port 22 keep state C -pfload /tmp/g14.conf || { bad soak ruleset; finish; } +pfload /tmp/g14.conf || { bad "soak ruleset"; finish; } -# mixed traffic: ICMP + TCP handshakes, 20s -cli "ping -q -c 30 -i 0.2 $SRV_IP >/dev/null 2>&1 &" 2>/dev/null +# mixed traffic: ICMP + TCP handshakes for ~20s +cli "ping -q -c 60 -i 0.3 $SRV_IP >/dev/null 2>&1 &" 2>/dev/null cli "for i in 1 2 3 4 5; do nc -w1 -z $SRV_IP 22; done" 2>/dev/null sleep 22 -ST=$(pf0 '/opt/pf/pfctl -s states | wc -l') + +ST=$(pf0 "$HELPER states") [ "${ST:-0}" -ge 1 ] && ok "states under load ($ST)" || bad "no states under load" -DMESG=$(pf0 "dmesg | grep -cE 'BUG:|WARNING:|Oops|general protection'") -[ "${DMESG:-1}" = 0 ] && ok "no kernel BUG/WARN/Oops" || bad "dmesg anomalies ($DMESG)" +ANOM=$(pf0 "$HELPER dmesg") +if [ -z "$ANOM" ]; then + ok "no kernel BUG/WARN/Oops" +else + bad "dmesg anomalies:" + printf '%s\n' "$ANOM" | sed 's/^/ /' +fi pf0 '/opt/pf/pfctl -d; /opt/pf/pfctl -f /dev/null' >/dev/null pf0 'rmmod pf && echo UNLOAD_OK' | grep -q UNLOAD_OK && ok "clean unload (refcounts released)" || bad "rmmod failed" sleep 1 -pf0 'insmod /tmp/pf.ko 2>/dev/null || insmod /opt/pf/pf.ko; true; (/opt/pf/pfctl -s info >/dev/null && echo RELOAD_OK)' | grep -q RELOAD_OK && ok "reload after unload" || bad "reload failed" +pf0 'insmod /opt/pf/pf.ko 2>/dev/null || insmod /tmp/pf.ko; true; (/opt/pf/pfctl -s info >/dev/null && echo RELOAD_OK)' \ + | grep -q RELOAD_OK && ok "reload after unload" || bad "reload failed" pf0 'rmmod pf && echo UNLOAD_OK2' | grep -q UNLOAD_OK2 && ok "second unload" || bad "second rmmod" sleep 1 -AFTER=$(slab_snapshot) -# compare per-cache object counts if they exist on both sides +AFTER=$(pf0 "$HELPER slab") LEAK=$(join <(echo "$BEFORE") <(echo "$AFTER") 2>/dev/null | awk '$3-$2 > 0 {print $1, $3-$2}') if [ -n "$LEAK" ]; then bad "slab growth after cycles: $LEAK"; else ok "no slab growth after unload cycles"; fi finish diff --git a/tests/vm/soak-helper.sh b/tests/vm/soak-helper.sh new file mode 100755 index 0000000..cf2ed7d --- /dev/null +++ b/tests/vm/soak-helper.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Helper executed ON pf0 by 14-soak.sh (uploaded with pfput to avoid quoting +# hell through two ssh hops). +# $1 = "slab" | "dmesg" | "states" +case "${1:-}" in +slab) + grep -E '^(pf|pfk)' /proc/slabinfo | tr -s ' ' | cut -d' ' -f1,3 | sort + ;; +dmesg) + dmesg | grep -E 'BUG:|WARNING:|Oops|general protection|kernel BUG' || true + ;; +states) + /opt/pf/pfctl -s states 2>/dev/null | grep -c 'icmp' || true + ;; +esac