# DF-3449 — dummynet3 KLD unload leaves stale ipfw3 filter hooks → deterministic call into unmapped module text

## Files
- `trigger.sh` — full reproduction, runs detached on the guest (root).
- `trigger_fixed.sh` — fix-validation run (patched module).
- `run.sh` — step-by-step equivalent (documented; the automated `trigger.sh`
  is what was actually executed because the panic kills ssh).
- `panic.txt` / `console_full.txt` — serial-console capture of the panic.
- `run.patched.log` — full untrimmed fixed-module run (guest alive at end).
- `fix.diff` — git-apply-able patch against `sys/net/dummynet3/ip_dummynet3.c`.
- `env.txt` — guest uname / kldstat / securelevel.

## Build (on the guest, as root)
```sh
for d in ipfw3 ipfw3_basic dummynet3; do
	(cd /usr/src/sys/net/$d && make obj && make)
done
# artifacts:
#   /usr/obj/usr/src/sys/net/ipfw3/ipfw3.ko
#   /usr/obj/usr/src/sys/net/ipfw3_basic/ipfw3_basic.ko
#   /usr/obj/usr/src/sys/net/dummynet3/dummynet3.ko
```

## Run (baseline, unpatched)
```sh
sh /root/df3449_trigger.sh        # uploaded from trigger.sh, run detached
```
The script must run detached (`nohup ... &`) because the panic kills ssh.
Note: `ipfw3_basic.ko` is loaded standalone BEFORE dummynet3 on purpose —
dummynet3 `MODULE_DEPEND`s on ipfw3_basic, and the kldunload dependency
cascade would otherwise unload basic first and trip the *known, separate*
DF-0669 `rn_flush(NULL)` panic in ipfw3_table.c before our bug can fire.

## Expected output (baseline)
1. `ipfw3 initialized, default to accept` / `ipfw3 module basic loaded` /
   `ipfw3 module dummynet loaded` / `DUMMYNET initialized (011031)` on console.
2. pipe 1 configured, rule `00100 pipe 1` added, ping through the pipe works
   (~44 ms: 10 ms delay + 1 Mbit/s shaping).
3. `kldunload dummynet3` **succeeds** although rule 100 references module 2 —
   dummynet3's MOD_UNLOAD never calls `ip_fw3_unregister_module()`.
4. First packet after the unload:
   ```
   Fatal trap 12: page fault while in kernel mode
   fault virtual address = 0xffffffff8260d000
   fault code           = supervisor read instruction, page not present
   instruction pointer  = 0x8:0xffffffff8260d000
   current process      = Idle
   ```
   0xffffffff8260d000 is exactly the former load address of dummynet3.ko
   (see `kldstat` in run.patched.log: `6 1 0xffffffff8260d000 4000 dummynet3.ko`).
   The RIP equals the address `filter_funcs[2][O_DUMMYNET_PIPE]` (= `check_pipe`)
   still holds: check_body() (ip_fw3.c:506) calls it with no NULL check after
   the module text was unmapped. Kernel drops into DDB; guest is dead.

## Fix validation (patched)
Apply `fix.diff` in /usr/src, rebuild dummynet3.ko, run `trigger_fixed.sh`:
- `kldunload dummynet3` with the rule present → `Device busy` (EBUSY), rule
  and traffic keep working;
- after `ipfw3 delete 100` the unload succeeds (`ipfw3 module dummynet unloaded`
  on console), ping works, no panic;
- adding a new pipe rule after the unload is rejected by the CLI because the
  module is no longer registered.
Full log: `run.patched.log`.
