Advanced Topics
This chapter explains the inner workings of Little Snitch for Linux for technically interested readers. Nothing here is required for everyday use, but it helps to understand what the product can and cannot do, and why.
Why Linux cannot do everything the macOS version does
Little Snitch hooks into the Linux network stack using eBPF, a mechanism that lets programs safely observe and intercept events inside the kernel. An eBPF program watches outgoing and incoming connections and feeds data to the daemon, which tracks statistics and serves the web UI.
eBPF is powerful but deliberately bounded. Before an eBPF program is loaded, the kernel's verifier statically analyzes it and rejects anything whose safety and termination it cannot prove. This imposes strict limits on program complexity and on storage:
-
No connection alerts. eBPF programs cannot put the calling program on hold while an alert is shown.
-
No deep packet inspection. The macOS version of Little Snitch parses protocol headers inside network packets. Algorithms of that complexity would not pass the eBPF verifier, so the Linux version cannot inspect protocol contents beyond basic TCP/IP headers.
-
Bounded cache tables. eBPF maps have fixed sizes. Under heavy traffic, the tables that associate packets with processes and DNS names can overflow. When that happens, some packets can no longer be reliably attributed.
This is the technical root of the statement in the Introduction: Little Snitch for Linux is built for privacy, not for hardening a system against a determined adversary.
Resolving IP addresses to names
The problem
Users want rules for domain names; everything else is obscure. But the filter works in the kernel, where only IP addresses are visible. Little Snitch must therefore reconstruct which name was used to look up a given IP address.
This reconstruction is fundamentally ambiguous. Many names can resolve to the same IP address. High traffic sites are typically hosted on content delivery networks. It is entirely possible (and even likely) that a tracking site and a computer magazine share the same address.
Two known techniques
There are two general techniques for recovering a name for an IP address:
- Temporal proximity of lookups. Watch DNS queries and responses. When a connection to an IP address is made, take the most recent DNS query that returned that address.
- Names carried inside the protocol. Many protocols include the remote host name in their data. Most importantly, TLS carries it in the SNI (Server Name Indication) header, and QUIC has an equivalent.
Both techniques are unreliable. A process may perform a lookup, cache the result, and connect much later. By then, the most recent lookup for that address may have been for a different name. Still, the name found this way is at least one valid name for the address. And this only works while DNS lookups are unencrypted. The SNI header, on the other hand, is entirely under control of the client. The client can send any name, as long as the server accepts it. And there are ongoing efforts to encrypt it (Encrypted Client Hello).
Little Snitch on macOS uses both techniques: it primarily reads the name from protocol headers, but validates it against recent DNS lookups. Little Snitch on Linux cannot inspect protocol headers, as explained above, so it relies on temporal proximity of DNS lookups only. This usually works very well, unless a DNS cache sits between the application and the visible network traffic, or DNS lookups are encrypted.
DNS visibility and systemd-resolved
On modern distributions, systemd-resolved commonly sits between applications and the network, and it can do both of the things that defeat lookup observation: caching and DNS encryption. The consequence: Little Snitch should observe the communication between the application and systemd-resolved, not the lookups systemd-resolved sends to the remote DNS server.
systemd-resolved offers applications two interfaces:
- A UDP/TCP socket at 127.0.0.53, port 53. This is ordinary loopback network traffic, which Little Snitch's network filter can observe.
- A Unix domain socket, used by glibc's
nss-resolvemodule. Traffic on Unix domain sockets is not network traffic and is not easily accessible to the filter.
For best results, make sure applications use the UDP socket rather than the Unix domain socket. This is controlled by the hosts: line in /etc/nsswitch.conf. If that line contains the keyword resolve, glibc uses the nss-resolve module and talks to systemd-resolved over the Unix domain socket, invisibly to Little Snitch. Remove the resolve keyword (and any condition in brackets immediately following it) so that glibc falls back to classic DNS via /etc/resolv.conf, which on a systemd managed system points at 127.0.0.53. For example:
hosts: files myhostname mdns4_minimal [NOTFOUND=return] dns
Name resolution behaves the same afterwards; the queries merely travel over the observable UDP socket instead of the hidden Unix domain socket.
Application level DNS encryption
Some applications, notably web browsers, offer their own DNS over HTTPS (or TLS or QUIC) setting. When enabled, the application sends encrypted queries directly to a remote resolver and Little Snitch sees no lookups at all from that application.
Our recommendation: disable DNS encryption in individual applications and enable it in systemd-resolved instead. Little Snitch then observes the plain queries between the application and systemd-resolved, while systemd-resolved encrypts the traffic that actually leaves your machine. As a welcome side effect, your entire system benefits from DNS encryption, not just the browser.
To enable encrypted DNS system wide, set DNSOverTLS=yes (or DNSOverTLS=opportunistic) in /etc/systemd/resolved.conf or a drop in file under /etc/systemd/resolved.conf.d/, and restart systemd-resolved. In browsers, look for the DNS over HTTPS or "secure DNS" setting and switch it to use the operating system's resolver.
Loopback answers
DNS responses containing loopback addresses (127.0.0.0/8, ::1) are discarded. Such answers usually come from blocking resolvers like Pi-hole, and names blocked there should not be attached to loopback traffic.
Process attribution: the parent chain
As described in Watching Your Connections, Little Snitch shows "parent via process" when a helper like curl makes a connection on behalf of another program. Here is how the parent is found.
Little Snitch walks the parent chain of the connecting process and tries to find "something interesting". If an interesting parent is found, the connection is displayed as "parent via process", and rules for this combination are matched. Rules for the parent alone are also matched. These apply to connections made by the parent directly or via any other process.
"Something interesting" is deliberately in quotes, because there is no crisp definition. Three refinements make the heuristic useful:
- App managers stop the search. Every process on the system is ultimately a child of
systemd, and nobody wants to see "systemd via something" everywhere. There is a list of app managers (init systems, desktop launchers, and the like). When one of these is encountered in the parent chain, the search is aborted. - Shells are skipped. In a chain like "app calls shell calls curl", the shell is not the interesting part, the app is. A list of shells defines executables that are silently skipped when walking the chain.
- A process is never its own parent. Some programs
fork()and let a subprocess do the work. Since it is the same executable, no parent is shown.
The app manager and shell lists are configured as glob patterns in executables.toml. See Advanced Configuration for how to override that file and the file itself for the syntax.
Executable normalization
Short of other identification criteria, executables are identified by their path in the file system. For most executables the path is fixed, but some contain a version number in a parent directory which changes with each upgrade, and some are mounted dynamically under a mount point whose name contains a unique identifier that changes on every start.
Rules should survive updates and restarts, and the connections view should not show a separate line for each version or incarnation of an application. Little Snitch therefore normalizes paths before matching rules or grouping entries.
Normalization converts the path into a glob pattern: the version number or unique identifier is replaced with *, the same style of pattern used by bash and other shells.
Which part of a path is a version number or unique identifier cannot be guessed reliably, it is configured in executables.toml. A normalization entry consists of:
- a regular expression that matches the paths in question, and
- a glob pattern with replacement references, into which the matched substrings are substituted.
One constraint deserves emphasis: the resulting glob pattern must itself be matched by the regular expression. This cannot be enforced or easily verified in code. It must be guaranteed by the person writing the pattern. When you add your own entries, double check this property.
If you write normalization patterns that could benefit other users, please consider sharing them with us.
Was this help page useful? Send feedback.
© 2016-2026 by Objective Development Software GmbH