Central control, distributed enforcement: one control plane compiles and signs policy, per-host eBPF agents enforce it in the kernel. The control plane is never in the packet path.
qf splits into two parts: a central control plane and one lightweight agent per host. The control plane compiles label-selector policies into BPF-map data and pushes signed bundles over an mTLS gRPC stream; each agent enforces them in the kernel. Control is centralized, enforcement is distributed across hosts.
The control plane is autonomous — no Kubernetes, no etcd, no service mesh. Its entire state lives in PostgreSQL plus a PKI directory, so it runs as a single process under systemd or a container. There is no extra control plane to babysit, and nothing to phone home.
Every step is code-grounded: compile, sign, push by generation number, enforce.
Operators declare label-selector policies and object groups (ipset / portset / hostset) via REST or the UI. Policy is the source of truth, not the host.
A change triggers cascade recompute of affected hosts. The control plane builds an effective rule set and compiles it into BPF-map data per host.
Each bundle is Ed25519-signed for policy authenticity and pushed to live agents over the bidirectional mTLS gRPC stream, tracked by an incrementing generation number.
The agent verifies the signature, translates the bundle into BPF-map writes, and applies it to the running eBPF datapath at the TC hook — no iptables dependency.
Agents stream verdict, system and flow events plus counters back to the control plane, which partitions them into PostgreSQL and fans live updates to the UI.
If the control plane is unreachable, agents keep enforcing the last bundle from their offline cache and buffer events until they reconnect. A datapath outage cannot be caused by the control plane being down.
A single Go process: policy compiler, PKI/CA, gRPC server for agents, REST/UI. State is PostgreSQL plus a PKI directory — no Kubernetes, no etcd. From 1 vCPU / 256 MB RAM; HA is 2 replicas behind a TCP balancer sharing PKI and one master key.
A small Go binary that loads the eBPF objects, manages the BPF maps and attaches to the interface. It enforces the last known bundle even while offline and consumes minimal resources.
Bidirectional mTLS stream between agent and control plane. New bundles are pushed by generation number; a separate unary service handles one-shot enrollment. Events and counters flow back on the same connection.
TCX on kernels ≥6.6 (coexists with Cilium), classic TC (clsact + cls_bpf) on <6.6 without Cilium. Two BPF builds — up to 2048 rules on ≥5.17, a bounded 32-rule variant for 5.15–5.16 — picked at load time by kernel capability.
qf is a host firewall, not another overlay or orchestrator — it coexists rather than takes over.
On kernel ≥6.6 qf attaches via TCX and runs alongside Cilium without fighting over the qdisc. On <6.6 with Cilium already attached, qf refuses to start rather than cause a conflict — an honest, explicit limit.
Enforcement is eBPF at the TC hook, not netfilter rules. qf does not rewrite your iptables or nftables chains; it runs the L3/L4 filter in the kernel and leaves the rest of your stack alone.
The control plane needs no Kubernetes, no etcd and no service mesh. If you do not want to operate another distributed system just to manage a firewall, there is nothing extra to babysit.
No phone-home. Enrollment is registry-mirror plus an out-of-band CA-fingerprint pin, and agents keep enforcing offline — so the same architecture works air-gapped.
Fan-out is sequential, not linearly scalable: 5–15 s across 500 hosts, 30–90 s across 5000. Validated to 1000 hosts; 5000 is a projected estimate. On kernels <6.6 with Cilium already attached, qf refuses to start.
Because a host firewall should not require you to run another distributed system to manage it. The control plane is a single Go process whose state is PostgreSQL plus a PKI directory. It runs standalone under systemd or as a container, and HA is just two replicas behind a TCP balancer.
Enforcement continues. The control plane is not in the packet path — agents keep applying the last signed bundle from their offline cache and buffer events on disk until they reconnect. This is fail-open by design: a forgotten policy or an unreachable control plane will not cut a host off.
On kernel ≥6.6 qf attaches via TCX and coexists with Cilium. On <6.6 without Cilium it uses classic TC. On <6.6 with Cilium already attached, qf refuses to start rather than fight over the qdisc — an explicit, documented limit rather than a silent failure.
One control plane serves up to 5000 agents. Fan-out is sequential — 5–15 s across 500 hosts, 30–90 s across 5000 — so it is not linearly scalable. The datapath has been validated to 1000 hosts; 5000 is a projected estimate. Rule capacity is up to 2048 on kernels ≥5.17 and 32 on 5.15–5.16.