Linux eBPF
On Linux, Enhanced Mode uses eBPF programs attached to cgroup v2 socket hooks. When any program opens a connection, the kernel already knows which process it belongs to and where it goes; Specola uses that to route it by your rules. There is no TUN device and no change to your routing table.
Requirements
- Linux 5.17 or newer;
- cgroup v2 mounted at
/sys/fs/cgroup; - kernel BTF (
/sys/kernel/btf/vmlinux); - root, to load and attach the BPF programs.
Check:
uname -r # 5.17 or newer
stat -fc %T /sys/fs/cgroup # cgroup2fs
test -r /sys/kernel/btf/vmlinux && echo BTF okCurrent Arch Linux (including Omarchy), Fedora, Ubuntu 24.04+ and Debian 12+ kernels meet all three. Inside containers, BPF and cgroup attachment are usually blocked even for root.
Configuration
[general]
route-mode = "rule"
port = 7890
protocols = "mixed"
[[proxy]]
name = "office"
type = "http"
server = "proxy.example.com"
port = 8080
[rule]
list = [
"PROCESS-NAME,git-remote-http,office",
"PROCESS-NAME,cargo,office",
"DOMAIN-SUFFIX,corp.example.com,office",
"IP-CIDR,10.0.0.0/8,DIRECT,no-resolve",
"FINAL,DIRECT",
]
[dns.tun]
mode = "fake-ip"
[enhanced-mode]
enable = true
type = "ebpf"
ip-version = "ipv4"
[enhanced-mode.ebpf]
process-routing = trueThe local listener must stay enabled (port > 0): redirected connections are handed to Core through it. [dns.tun] configures how Core answers DNS for captured processes; the defaults are Fake-IP, so the table can be omitted.
Fields
| Field | Default | Notes |
|---|---|---|
[enhanced-mode] ip-version | "ipv4" | the eBPF backend captures IPv4; IPv6 traffic bypasses it |
[enhanced-mode.ebpf] process-routing | false | attribute connections to processes; set true when rules use PROCESS-* |
[enhanced-mode.ebpf] unmatched-traffic | "direct" | accepted for older profiles; unmatched traffic now always follows FINAL |
Run
Validate as your normal user, then start with root:
specola-core -t --config my.toml
sudo specola-core --config my.tomlA successful start logs:
Linux IPv4 process proxy started with cgroup eBPF socket redirection (IPv6 bypassed)Stop with Ctrl+C or SIGTERM; Core detaches its programs. With sudo, a bare file name in --config is still looked up in your own ~/.config/specola/profiles/.
To run it permanently, use a systemd service; see Setting up Specola on Omarchy for a complete unit file.
How a connection is decided
For every new TCP connection or UDP send, in the kernel:
- Core's own sockets and the Specola UI are always let through.
- DNS queries to port 53 are sent to Core's DNS module.
- Loopback, local addresses, private and link-local networks, multicast and broadcast are let through.
- Everything else is compared with a candidate list Core keeps in BPF maps: process names from your rules, IP ranges from IP rules, addresses that DNS returned for names with domain rules, and the current
FINALtarget. - A connection that is not a candidate follows
FINALin the kernel. WithFINAL,DIRECTit keeps its original socket and never reaches Core. - A candidate is redirected to Core's local listener. Core runs the full rule list in order and goes direct, rejects, or connects through the chosen proxy or group.
What this means for your rules:
- Exact
PROCESS-NAMErules can be pre-checked in the kernel.PROCESS-PATH, wildcards and regular expressions cannot, so they make more connections go through Core. - If
FINALis a proxy or group, every non-local connection goes through Core. - Changing rules affects new connections; existing ones keep their decision.
- An application with its own encrypted DNS never shows Core the name. Match it with a process or IP rule.
Verify
curl -sI https://example.com # appears in the Specola connection log
sudo bpftool cgroup tree /sys/fs/cgroup # lists the attached programsAfter Core stops, the programs are gone from bpftool cgroup tree.
To test a process rule without changing real programs, copy a binary under the name the rule expects:
cp /usr/bin/curl /tmp/cargo && /tmp/cargo -sI https://example.com # matches PROCESS-NAME,cargoTroubleshooting
| Symptom | Cause and fix |
|---|---|
enhanced-mode.type=tun is not supported on Linux | use type = "ebpf" |
cgroup v2 is not mounted at /sys/fs/cgroup | the host uses cgroup v1 or a nonstandard mount; enable the unified hierarchy |
Operation not permitted | not running as root, or a container / LSM blocks BPF |
| A process rule never matches | the kernel name differs; check cat /proc/<pid>/comm (at most 15 characters) |
| A domain rule never matches | the application uses its own DNS (DoH); use a process or IP rule |
| IPv6 sites bypass Specola | expected; the backend captures IPv4. Keep [dns].ipv6 = false so captured programs get IPv4 answers |
| Everything goes through Core | FINAL is a proxy, or path/wildcard rules make most connections candidates |