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.

  1. 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
  1. 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
  1. 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}}
  1. Read the same facts in telemetry. The engine emits verdict.dos.sig on the verdict record. Every flow-keyed would-ban carries a trishula.ban record (ban.key, ban.tier, ban.score, ban.reason); the pipeline test pins both shapes.

  2. 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.

  3. 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

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.