#!/bin/sh
# DF-0735 trigger -- root-only.
#
# Builds sys/netgraph7/ng_ipfw.c into ng_ipfw.ko, loads it + its dependencies,
# sets up an ipfw ngtee divert rule (rule 1 -> ipfw ng node hook "1"), then
# sends one ICMP packet to ourselves. ipfw calls ng_ipfw_input (the function
# registered at MOD_LOAD in ng_ipfw.c:118) which tags the packet and queues
# it to the ipfw node's hook. The netgraph worker thread picks up the queued
# item and calls ng_ipfw_rcvdata (ng_ipfw.c:220) -> reads the tag (line 228)
# -> for dir=NG_IPFW_IN (which is what 'ngtee ... in' produces) calls
# ip_input(m) at line 248 DIRECTLY from the ng worker thread -> the
# ASSERT_NETISR_NCPUS(mycpuid) at ip_input.c:460 fires -> kernel panic.
#
# Required: ng_ipfw is NOT built by default (gated on `optional netgraph7_ipfw`
# in sys/conf/files and absent from sys/netgraph7/Makefile SUBDIR). Building
# and loading it requires root.
#
# Usage:   ./trigger_df735.sh        (run on the guest as root)
# Output:  exits 0 after sending the packet; the panic lands in the serial
#          boot log (kernel side), not on stdout.

set -eu

SRC=/usr/src/sys/netgraph7
WRK=${WRK:-/root/df735_wrk}

echo "[*] preparing work dir: $WRK"
rm -rf "$WRK"; mkdir -p "$WRK"
cp "$SRC/ng_ipfw.c" "$SRC/ng_ipfw.h" "$WRK/"
cat > "$WRK/Makefile" <<'EOF'
KMOD=   ng_ipfw
SRCS=   ng_ipfw.c
SYSDIR?=        /usr/src/sys
.include <bsd.kmod.mk>
EOF

echo "[*] building ng_ipfw.ko from in-tree source (unchanged)"
cd "$WRK"
make -m /usr/share/mk obj 2>&1 | tail -3 || true
make -m /usr/share/mk KMODDIR=/boot/kernel 2>&1 | tail -20
test -s ng_ipfw.ko || { echo "[-] ng_ipfw.ko not built"; exit 1; }
ls -l ng_ipfw.ko

echo "[*] loading dependencies + ng_ipfw"
kldload netgraph   || true
kldload ipfw       || true
kldload "$WRK/ng_ipfw.ko"
kldstat | grep -E "ng_ipfw|ipfw|netgraph"

echo "[*] verifying the ipfw ng node exists"
ngctl list 2>/dev/null | grep -E "Name:|Type:" | head -20
ngctl show ipfw: 2>&1 || true

echo "[*] installing ipfw ngtee rule (rule 1) -- both in/out"
# ngtee <rule> delivers a COPY of the packet to the netgraph ipfw node's
# hook named after the rule number; the original keeps flowing. The dir
# passed to ng_ipfw_input is IP_FW_DIVERT_DIR (in for inbound, out for
# outbound). We add both so the first packet that arrives triggers it.
ipfw -q flush 2>/dev/null || true
ipfw -q add 100 ngtee 1 ip from any to any in  via vtnet0
ipfw -q add 110 ngtee 1 ip from any to any out via vtnet0
ipfw list 2>&1 | head

echo "[*] sending one ICMP packet (self ping) -- expected to panic the kernel"
sync
# Give the kernel a moment, then ping. The packet traverses ipfw -> ngtee ->
# ng_ipfw_input -> NG_SEND_DATA_ONLY -> ng worker thread -> ng_ipfw_rcvdata
# -> ip_input(m) directly -> ASSERT_NETISR_NCPUS fires -> panic.
ping -c 1 -t 2 10.0.2.2 >/dev/null 2>&1 || true
ping -c 1 -t 2 127.0.0.1 >/dev/null 2>&1 || true

echo "[!] if the kernel is still up after this, the assertion did NOT fire."
echo "    check the serial boot log / dmesg for ASSERT_NETISR_NCPUS."
exit 0
