Files
Marcus Almert 9106cac2a2
Some checks failed
Build and Deploy MITM Webserver / build (push) Has been cancelled
Build and Deploy MITM Webserver / traffic_target (push) Has been cancelled
documentation md files
2026-08-30 17:31:46 +02:00

234 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Technical reference: packet sniffing modes
This document specifies the implemented capture behavior in `network_sniffer.py`,
`bridge_telemetry.py`, `ebpf_bridge_events.py`, and `packet_tracker.py`. It makes a
deliberate distinction between observed facts and inferred forwarding results.
## Session model and mode selection
A capture session has a UUID and targets exactly one interface or exactly one
bridge. A bridge is expanded once with `get_bridge_ports_once`; its member list is
a creation-time snapshot. Later bridge membership changes are not added to the
existing session. Session state includes target label/type, effective mode, benchmark
flag, port snapshot, AF_PACKET sockets, optional reader thread, stop event, and
benchmark counters.
| Request | Effective mode | Capture source | Path/outcome evidence |
| --- | --- | --- | --- |
| Interface, any requested mode | `af_packet` | One raw socket on the interface | Packet-socket type labels an outgoing copy as egress; no kernel verdict telemetry. |
| Bridge, `af_packet` | `af_packet` | One raw socket per snapshot bridge port | Two matching port observations can infer forwarding. |
| Bridge, `tc_ebpf` (default) | `tc_ebpf` | One tc/eBPF helper across snapshot bridge ports | TC ingress/egress and skb-free/drop events, normally matched by skb mark. |
| Any mode with benchmark enabled | Same hook/socket setup | Counted but not processed | No parsing, enrichment, DB write, or WebSocket event. |
Interface targets always use AF_PACKET. The TC/eBPF mode is only selected for a
bridge target. A stop by session ID is the safest selector. Stopping a session also
discards pending tracker entries relating to its interfaces, so unpersisted data can
be lost deliberately at shutdown.
Multiple sessions may overlap on an interface. This is not an independent-capture
guarantee: the TC manager maps an interface to several sessions but assigns raw
ingress parsing to the first sorted session ID.
## AF_PACKET capture
### Socket behavior
For each capture interface the service opens `AF_PACKET` / `SOCK_RAW` with protocol
`htons(0x0003)` (`ETH_P_ALL`), requests the configured receive buffer (default
4 MiB), best-effort requests `TPACKET_V3`, binds to `(ifname, 0)`, and makes the
socket non-blocking. Failure to set the buffer or TPACKET version is non-fatal.
Failure to create or bind leaves the interface uncaptured; session creation can still
complete. This requires raw-socket privilege, commonly `CAP_NET_RAW`.
One daemon reader thread is started only when the session has sockets. It uses a
selector, receives at most `BACKEND_SNIFFER_RECV_BYTES` bytes per event (default
65,536), stamps the frame with userspace UTC receive time, creates Scapy `Ether`,
and calls the common parser. `ENODEV`, `ENETDOWN`, and `EBADF` close the affected
socket; it is not reopened in that session. The thread periodically attempts a
pre-DB-buffer drain during selector idle time.
### Direction and bridge inference
Packet-socket address metadata is used only as follows: `PACKET_OUTGOING` (normally
4) becomes `path_role: egress`; every other packet type becomes `path_role:
ingress`. This is a packet-socket perspective, not proof of a Linux bridge decision.
For a bridge session, the tracker groups AF_PACKET observations by session ID. It
uses an explicit ingress observation if available, otherwise the earliest one. It
prefers an explicit egress observation on a different port, otherwise a later
different-port observation. If one correlated packet is seen on at least two ports,
the tracker records:
```text
verdict = accept
verdict_reason = bridge-af_packet-forwarded-observed
verdict_confidence = medium
```
This means matching evidence was observed on two bridge ports. It does not prove a
particular kernel forwarding verdict and can be affected by duplicate copies, loops,
or fallback-identity collisions. A single-interface AF_PACKET record has no terminal
verdict from AF_PACKET itself.
### AF_PACKET implications
AF_PACKET provides full observed frame bytes without BCC or tc changes and is the
only interface-capture mode. It neither alters packets nor controls forwarding. It
also has no definitive drop visibility, reports userspace rather than kernel event
time, can observe local/outgoing copies, and can lose traffic under socket/userspace
load. The full-frame Scapy parse, tshark lookup, tracking and persistence path makes
it more expensive than sampled telemetry.
## TC/eBPF bridge capture
### Collector lifecycle and destructive qdisc behavior
Bridge sessions in `tc_ebpf` mode are aggregated into one helper process. Any change
to the active *interface set* stops the helper and recreates it for the new set;
there is a capture gap during that restart. The helper attaches direct-action
`BPF.SCHED_CLS` programs at TC ingress (`ffff:fff2`, handle `:20`) and egress
(`ffff:fff3`, handle `:30`) to every bridge **member interface**, not the bridge
device itself.
Before attachment the helper runs `tc qdisc del dev <iface> clsact` (ignoring its
result), then `tc qdisc add dev <iface> clsact`. It deletes `clsact` again for every
instrumented interface at helper shutdown and after an attachment failure.
> Starting, restarting, failing, or stopping TC/eBPF capture can remove pre-existing
> clsact qdiscs and their filters. Do not use it on interfaces with unrelated TC
> configuration unless coexistence and recovery are explicitly managed.
The BCC Python runtime, a compatible kernel, BPF/tracepoint access, TC and netlink
privileges are required. Session creation does not wait for a collector health
acknowledgement, so a successful start response is not proof that BPF attached.
### Kernel event generation
The helper opens a raw-ingress perf buffer and a metadata perf buffer. The ingress
TC program creates an skb mark only when it is zero, using the low 28 bits of
`bpf_ktime_get_ns()` and replacing zero with one. It preserves any existing nonzero
mark. It extracts Ethernet addresses, EtherType, a single 802.1Q/802.1AD VLAN ID,
ARP IPv4 addresses, and IPv4/IPv6 addresses with TCP/UDP ports. IPv6 extension
headers are not traversed; the base next-header is used as protocol.
The egress TC program never creates a mark. It exports metadata only for marked
packets. The `skb:kfree_skb` tracepoint reads the linear skb representation and
exports a drop event only for marked skbs whose device is a selected interface.
The emitted payload contains userspace and kernel-monotonic timestamps, interface,
mark, length, parsed L2–L4 fields, and event type. Drop events add a numerical
reason and `skb_drop_reason_<n>` label. Ingress events may contain `raw_b64`; egress
and drop events do not.
A kfree_skb event is evidence that a marked skb was freed in the kernel context. It
is not automatically evidence that nftables caused the outcome; interpret the
reason code in the context of kernel behavior and other instrumentation.
### Sampling
Raw and metadata sampling are independent settings.
| Value | Effect |
| --- | --- |
| `0` | Never emits that sample category. |
| `1` | Emits every marked packet in that category. |
| `N > 1` | Emits when `skb_mark % N == 0`. |
At ingress, a raw-selected packet emits a raw event; only a packet not chosen for
raw can emit an ingress metadata event. Egress and drop use metadata sampling only.
Therefore raw-enabled/meta-disabled capture stores sampled ingress frame records
without egress/drop visibility; raw-disabled/meta-enabled capture produces
metadata-only rows without raw bytes. Both enabled does not make raw and metadata
populations identical.
Sampling uses the entire existing skb mark. The documented mark layout reserves
bits 0–27 for packet ID and upper bits for drop/reject hints. This capture program
creates only the low-28-bit value for previously zero marks; it does not set verdict
hints. Any other mark-using subsystem must coordinate its mark semantics, because
it can change both sampling and correlation.
### Userspace event handling and loss
The manager reads JSON helper output into a bounded queue. For a non-benchmark
ingress event with `raw_b64`, it decodes the frame and sends it into the common Scapy
parser as source `tc_ingress_raw`, with `packet_id`, `skb_mark`, and capture mode
`tc_ingress`. It then sends every non-benchmark ingress/egress/drop event to the
tracker. A sampled raw ingress packet usually therefore has both a parsed capture
observation and a telemetry observation under the same mark-derived key.
When the telemetry queue is full, the manager drops oldest queued events down to
`BACKEND_BRIDGE_TELEMETRY_QUEUE_RECOVERY_SIZE`, attempts to keep the new event, and
counts dropped events and raw payloads. Perf buffers can also lose samples before
userspace. Neither loss mechanism is recovered. `/api/sniffer/debug` reports queue
size, queue drops, benchmark counts, collector interfaces, and tracker statistics.
### TC/eBPF implications
This mode yields better within-host correlation and explicit TC egress evidence. A
matching egress produces `accept`, `egress-observed`, confidence `high`; a matching
drop produces `drop` (or mark hint), confidence `high`. Absence of egress is not
proof of a drop: sampling, perf loss, queue loss, an uninstrumented path, teardown,
or collector failure can all explain it. Raw bytes are ingress-only and sampled.
## Common parsing, enrichment, and identity
Both modes use `parse_packet` / `parse_packet_bytes`. The parser records Ethernet
addresses, EtherType and VLAN, ARP operation/addressing, IPv4 ID or IPv6 base
header, TCP sequence/acknowledgement/flags, UDP ports, ICMP/ICMPv6 type/code, and
an embedded IPv4 tuple from eligible ICMP errors. It stores full raw frame bytes
when supplied by AF_PACKET or sampled TC ingress.
tshark workers are enabled for non-benchmark capture interfaces. They may add
application protocol, category, confidence, hostname, encryption/risk, and flow/DPI
metadata. They are optional and asynchronous; a failure or late match does not
discard underlying capture, and later backfill can enrich stored records.
The preferred identity is `pid:<packet-id>`, where the ID is bits 0–27 of skb mark.
Without it, a SHA-1 `uid` is calculated. The Scapy fallback includes L2–L4 fields,
IPv4 ID, ARP/ICMP fields and TCP sequence/ack/flags; eBPF metadata's fallback uses
only the smaller L2–L4 tuple and length. Hash-only correlation is consequently a
best-effort fallback, weaker for repeated/identical/fragmented traffic.
## Tracker outcomes and persistence
The tracker deduplicates observations, merges available fields, retains the earliest
timestamp, and waits the configured finalization delay (default 250 ms).
| Evidence | Verdict | Confidence |
| --- | --- | --- |
| TC egress telemetry | `accept`; `egress-observed` | high |
| TC drop telemetry | mark hint or `drop`; kernel reason / `kfree_skb` | high |
| Matching recent TCP RST or ICMP unreachable after drop | `reject` | medium |
| Same AF_PACKET bridge record on two ports | `accept`; forwarding observed | medium |
| No terminal evidence before delay expires | `unknown`; `timeout` | low |
It asynchronously upserts batches to PostgreSQL. Entry-cap pressure, persistence
failure/retry limits, dirty-age expiry, collector queue loss, socket loss, and
shutdown can all cause incompleteness. A packet history or WebSocket feed is never
a proof of lossless capture. Batch upserts also do not individually publish packet
updates, so realtime consumers must use history reconciliation.
## Benchmark mode
Benchmark mode still creates sockets or TC hooks but bypasses normal processing.
AF_PACKET increments received frame and byte counters. TC/eBPF increments helper
event counters and raw-payload-event counters. It does not parse Scapy, invoke
tshark, call the tracker, persist rows, or publish updates. AF_PACKET counters count
socket frames; TC counters count emitted sampled events. They are not comparable as
equal packet totals without accounting for sampling and multiple event types.
## Selection guidance
| Need | Mode | Important caveat |
| --- | --- | --- |
| Full raw visibility for a single interface | AF_PACKET | No definitive kernel egress/drop verdict. |
| Full raw frames across bridge ports | Bridge AF_PACKET | High userspace work; bridge forwarding is inferred. |
| Ingress/egress/drop evidence on a controlled bridge | TC/eBPF | Requires BPF/TC privileges and resets clsact. |
| Reduced overhead / sampled observability | TC/eBPF sampling | Data is intentionally incomplete. |
| Hook-overhead measurement | Benchmark mode | Counts differ between AF_PACKET and TC. |
Before TC/eBPF capture, inspect `tc qdisc` and filters for every target port,
coordinate skb-mark ownership, verify BCC/kernel support, and plan recovery of the
TC configuration. For every mode, monitor sniffer debug counters, system logs,
capture/process health, DB persistence failures, and expected traffic rate before
making operational or security conclusions.