Skip to content

eBPF TCP setup

TCP SYN fingerprinting uses an XDP program loaded by huginn-ebpf-agent. The agent pins BPF maps under bpffs (for example under HUGINN_EBPF_PIN_PATH). Huginn Proxy opens those maps read-only and emits x-tcp-p0f.

Two processes cooperate:

  1. eBPF agent (huginn-ebpf-agent): loads XDP, attaches to the interface, pins maps, exposes metrics, stays running.
  2. Proxy (huginn-proxy): accepts connections, looks up (src_ip, src_port) in the map, formats the p0f-style signature.
  • Kernel ≥ 5.11 (recommended for current BPF UAPI).
  • bpffs mounted at /sys/fs/bpf (Compose uses a bpf volume; on bare metal ensure the mount exists).
  • One agent per interface that loads XDP on that NIC; two loaders on the same interface race.
ComponentTypical needs
eBPF agentCAP_BPF, CAP_NET_ADMIN, CAP_PERFMON (or root); often seccomp/apparmor unconfined so bpf() and XDP attach succeed in containers.
ProxyCAP_BPF is enough when it only opens pinned maps (no XDP load in the proxy process).

These must be consistent between agent and proxy where shared (pin path, map size). Names match what the binaries read at runtime.

VariableRole
HUGINN_EBPF_PIN_PATHDirectory under bpffs where maps are pinned (e.g. /sys/fs/bpf/huginn). Same on agent and proxy.
HUGINN_EBPF_INTERFACENIC the agent attaches XDP to (in Docker Compose with network_mode: service:proxy, this is the proxy container’s eth0).
HUGINN_EBPF_DST_PORTListener port the agent filters toward (the proxy’s TLS/HTTP port, e.g. 7000).
HUGINN_EBPF_DST_IP_V4IPv4 address considered “to the proxy” (0.0.0.0 often means any local IPv4 listener on that port; align with your bind addresses).
HUGINN_EBPF_DST_IP_V6IPv6 counterpart (e.g. :: for any).
HUGINN_EBPF_SYN_MAP_MAX_ENTRIESBPF map capacity for SYN entries; same on agent and proxy.
HUGINN_EBPF_XDP_MODEXDP attach mode (e.g. skb vs native / driver-dependent; see agent docs in repo).
HUGINN_EBPF_METRICS_ADDR / HUGINN_EBPF_METRICS_PORTWhere the agent binds its HTTP server for /metrics, /health, /ready, /live. See Agent metrics bind address below.

Full stack layout (Compose, caps, volumes) is maintained in examples/docker-compose.ebpf.yml (build from source) and examples/docker-compose.release-ebpf.yml (pre-built GHCR images).

HUGINN_EBPF_METRICS_ADDRListens onScrape / curl from
127.0.0.1Loopback in the agent netns onlySame netns, or host 127.0.0.1:$PORT when the port is published.
0.0.0.0All interfaces in that netnsHost or pod IP (+ port) when scraping remotely—not 127.0.0.1 from another machine.

See docker-compose.release-ebpf.yml for a Compose example using 0.0.0.0.

  • network_mode: "service:proxy" on the agent puts the agent in the proxy’s network namespace, so the interface name (eth0) and destination filter match the traffic the proxy actually receives.
  • bpffs must be mounted into both containers at /sys/fs/bpf (or adjust paths consistently).
  • Health: agent /ready should succeed when maps are pinned; proxy /health on telemetry.metrics_port is separate.

See Containers for the two Compose layouts (eBPF vs plain) and clone/run flow, and Artifacts for GHCR image names.

The SYN map is keyed by source IP and port as seen on the wire. CNIs that SNAT client traffic in a way that hides the original tuple can break correlation. Prefer CNIs that preserve the client endpoint toward the pod, or place the proxy where it sees the real tuple.

Start the agent before or restart the proxy after maps exist; the proxy retries map open when the agent comes up later.