Recipe 10 — Watch the dynamic DoS signature: the shield runner end to end
| Tool | Section | Level | Time | Layer | Tags |
|---|---|---|---|---|---|
cmd/shield/ · internal/shield/ |
Detection | hard | 15 min | kernel + engine | DoS, slowloris, signature, TR-195 |
Problem
A slowloris trickle walks one flow; no request ever looks bad alone. You want the kernel-side counters to catch the shape and publish a ban. You want the same measured facts in evidence and telemetry.
Solution
The runner reads the pinned flow_stats map, forms the cue from the flow window and drives the scored ban path. The signature states what the flow carried: the segment count, the bytes per segment and the span.
- Run the shield demo in the lab netns. The lab owns the pins and the netns; the runner reads the same pins the programs write:
bash lab/demo.sh # brings the veth pair, the tc hook and the pins up
sudo ./shield --flow-stats /sys/fs/bpf/trishula/flow_stats \
--bans-v4 /sys/fs/bpf/trishula/bans_v4 -interval 2s- Drive the trickle from the client netns. Thirty tiny frames, one per second and a bit, one flow:
segs >= 24 · bytes/seg <= 64 · span >= 30 s
- Read the would-ban the runner writes to the evidence JSONL:
tail -1 /tmp/tr195/dos_runner_evidence.jsonl{"key":"10.77.0.66","verdict":"would-ban","tier":"scored-window",
"osig":{"class":"slowloris_suspect","segs":30,"bytesPerSeg":9,"spanSec":34}}
Read the same facts in telemetry. The engine emits
verdict.dos.sigon the verdict record. Every flow-keyed would-ban carries atrishula.banrecord (ban.key,ban.tier,ban.score,ban.reason); the pipeline test pins both shapes.Watch the kernel row stop the next attempt. After the crossing the offender’s packet dies at the XDP ban program. A replay connect to the shield answers with silence. The in-VM acceptance records exactly this chain on #253.
Tune: the detector floors live in
internal/engine/rateban/dos.go(SlowlorisMinSegs= 24,SlowlorisMaxBytesPerSeg= 64,SlowlorisMinSpanSec= 30). The cue weights live in the ban weights table (kernelBurstFlag= 3 points). Four cues per source cross the threshold of 10.
Discussion
The runner = the automation the spec promises: the map read, the cue, the scored evidence, the enforcer publish. Each sweep runs one at a time on an interval. Fail-open holds: a failed read or write surfaces, the packets keep passing, the interval retries.
The signature is one object on three surfaces: the cue note, the evidence row (osig) and the verdict record (verdict.dos.sig). The in-VM acceptance proves the chain against a live kernel. The chain = the paced trickle, the 4-sweep crossing, the XDP drop and the replay dying at the NIC.
The rapid-reset shape (h2) scores through the same ban path. Its cue carries the same signature layout with the reset ratio in place of the segment shape.
See also
- spec 13.7 — who does what. spec 9.3 — the maps the runner reads.
- Issue #253 — the in-VM acceptance receipt.
- PR #249 — the runner. PR #250 — the signature on the records.
- Recipe 9 — the manual path for the same ban row.
- Recipe 6 — the scored ladder the cues feed.
Provenance
Verified against trishula@798beb1 on main, 2026-10-25.
Smoke run, 2026-10-25, inside the sprint Linux VM (OrbStack trishula-build-dev, arm64):
go test -run TestDoSRunnerE2E ./test/dosrunner/: the paced trickle. The 4-sweep crossing, the banned ping dead at XDP, the replay dead at the NIC followed. The map row after the crossing:bans_v4[10.77.0.66] = {until_ms=…, score=1200, reason_code=1300}. The evidence JSONL carries the sig. The verbatim transcript sits on #253.