Skip to content

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:

bash
uname -r                                  # 5.17 or newer
stat -fc %T /sys/fs/cgroup                # cgroup2fs
test -r /sys/kernel/btf/vmlinux && echo BTF ok

Current 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

toml
[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 = true

The 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

FieldDefaultNotes
[enhanced-mode] ip-version"ipv4"the eBPF backend captures IPv4; IPv6 traffic bypasses it
[enhanced-mode.ebpf] process-routingfalseattribute 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:

bash
specola-core -t --config my.toml
sudo specola-core --config my.toml

A successful start logs:

text
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:

  1. Core's own sockets and the Specola UI are always let through.
  2. DNS queries to port 53 are sent to Core's DNS module.
  3. Loopback, local addresses, private and link-local networks, multicast and broadcast are let through.
  4. 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 FINAL target.
  5. A connection that is not a candidate follows FINAL in the kernel. With FINAL,DIRECT it keeps its original socket and never reaches Core.
  6. 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-NAME rules can be pre-checked in the kernel. PROCESS-PATH, wildcards and regular expressions cannot, so they make more connections go through Core.
  • If FINAL is 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

bash
curl -sI https://example.com                   # appears in the Specola connection log
sudo bpftool cgroup tree /sys/fs/cgroup         # lists the attached programs

After 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:

bash
cp /usr/bin/curl /tmp/cargo && /tmp/cargo -sI https://example.com   # matches PROCESS-NAME,cargo

Troubleshooting

SymptomCause and fix
enhanced-mode.type=tun is not supported on Linuxuse type = "ebpf"
cgroup v2 is not mounted at /sys/fs/cgroupthe host uses cgroup v1 or a nonstandard mount; enable the unified hierarchy
Operation not permittednot running as root, or a container / LSM blocks BPF
A process rule never matchesthe kernel name differs; check cat /proc/<pid>/comm (at most 15 characters)
A domain rule never matchesthe application uses its own DNS (DoH); use a process or IP rule
IPv6 sites bypass Specolaexpected; the backend captures IPv4. Keep [dns].ipv6 = false so captured programs get IPv4 answers
Everything goes through CoreFINAL is a proxy, or path/wildcard rules make most connections candidates