9/23/2026

DNS for transparent routing

When an application talks to Specola’s local HTTP/SOCKS5 port, it hands over the destination name: CONNECT api.github.com:443. Domain rules are easy.

Transparent capture is different. By the time a program calls connect(), it has already resolved the name and only an IP address is left. Several unrelated domains can share that IP behind a CDN, and the same domain can resolve to many IPs. Without help, DOMAIN-SUFFIX,github.com,work has nothing to match.

How Specola connects names to connections

With the Linux eBPF backend, Specola’s DNS module is always active:

  1. DNS queries are intercepted in the kernel. Before any bypass check, the cgroup hooks send UDP and TCP queries to port 53 to Core’s DNS module.
  2. Core answers and remembers. For each answer, Core records which domain produced which address and which rule that domain matched.
  3. The kernel learns the candidates. Addresses that belong to domains with rules are pushed into the BPF candidate map, so a later connect() to that address is redirected to Core.
  4. Core restores the name. When the redirected connection arrives, Core looks the address up, finds the domain, and applies the domain rule.

Domains without rules are resolved normally and their connections follow FINAL. With FINAL,DIRECT they never leave the kernel fast path.

Fake-IP

The recommended mode for captured traffic is Fake-IP:

[dns.tun]
mode = "fake-ip"
fake-ip-range = "198.18.0.1/16"
fake-ip-filter-mode = "blacklist"
fake-ip-filter = ["+.lan", "+.local"]

For a domain that a rule sends through a proxy, Core answers with an address from the 198.18.0.0/15 pool instead of the real one. That address is a token: it is unique per domain, so CDN sharing no longer loses information, and a connection to it always goes to Core, which maps it back to the name.

Two useful side effects:

Domains routed DIRECT get their real address, so direct traffic behaves exactly as without Specola. Names matched by fake-ip-filter (local names like *.lan) always get real addresses. A domain routed to REJECT gets REFUSED.

The alternative, mode = "redir-host", returns real addresses for everything. It is only useful for software that insists on real IPs, and it inherits the CDN ambiguity described above.

Pitfalls

Applications with their own encrypted DNS. Browsers with “secure DNS” enabled, and some CLIs, resolve names over HTTPS themselves. Specola never sees those queries, so their connections carry no domain. Either turn off the application’s built-in DoH, or match it with a PROCESS-NAME or IP-CIDR rule.

Existing connections. A connection keeps the decision made when it was opened. After changing rules, restart long-lived clients or wait for them to reconnect.

IPv6. The eBPF backend captures IPv4 only. If a domain has AAAA records and the application prefers IPv6, that connection bypasses Specola. Keep [dns].ipv6 = false (the default) so captured applications receive IPv4 answers.

Sniffing is a fallback, not a replacement. Specola can also read the HTTP Host header or the TLS SNI of a redirected connection. That helps once a connection reaches Core, but with FINAL,DIRECT an unmatched connection is never redirected, so there is nothing to sniff.

For the full list of DNS options, see the DNS manual.