· 6 min read ·

Per-Process Network Control Comes to Linux, and the Tech Behind It Is Fascinating

Source: lobsters

Objective Development has been shipping Little Snitch for macOS since 2004. It occupies a specific niche: a firewall that works at the application layer, prompting you whenever a process tries to make an outbound connection and letting you allow or deny it permanently, temporarily, or by rule. It is the kind of tool that, once you have used it, makes every other OS feel slightly untrustworthy. The announcement of a Linux version is therefore interesting not just as a product story but as a technical one, because building this correctly on Linux requires solving problems that macOS hands you solutions for.

What Little Snitch Actually Does on macOS

On macOS, Little Snitch hooks into the operating system via Apple’s Network Extension framework, which replaced the older kernel extension (kext) approach after macOS 10.15. The Network Extension API gives user-space code the ability to inspect and filter network traffic at the socket level, with the kernel doing the heavy lifting of associating each socket with the originating process. The app gets told: “process X at path Y with code signature Z is trying to connect to host H on port P.” You decide what to do.

This is a first-class, Apple-supported API. The process attribution is authoritative. The code signature verification means Little Snitch can distinguish between a legitimate build of curl and something that has replaced it on disk. The whole system is designed for exactly this use case, which is part of why Little Snitch works so reliably on macOS.

Linux has no equivalent API. That is not a criticism; it is just a different set of design priorities. The challenge of building something analogous requires assembling several unrelated subsystems and accepting some inherent tradeoffs.

The Core Technical Problem: Process Attribution

The fundamental question a per-process firewall needs to answer is: which process owns this network connection? On Linux, this is not directly exposed at the netfilter layer where packet filtering happens. Netfilter, the framework behind iptables and nftables, operates on packets and knows about network namespaces, marks, and connection state, but it does not natively expose process IDs or binary paths.

The classic solution is to cross-reference the kernel’s /proc filesystem. The files /proc/net/tcp and /proc/net/tcp6 list all TCP connections with their socket inodes. By scanning /proc/[pid]/fd/ for each running process, you can find which process has a file descriptor pointing to that socket inode. This tells you the PID, and from the PID you can read /proc/[pid]/exe to get the binary path.

This works, but it has a real problem: it is a race. By the time a userspace daemon intercepts the packet, queries /proc/net/tcp, and walks the process file descriptors, the process that opened the socket might have exited, or a different process might now own the file descriptor. In practice these races are rare for interactive applications, but they are not zero, and a security tool that can be fooled by timing is a tool with a meaningful gap in its guarantees.

NFQUEUE: Giving Userspace the Decision

NFQUEUE is the netfilter mechanism that lets a userspace program accept or drop packets. You configure an iptables or nftables rule to queue matched packets to a specific queue number, and then a userspace daemon binds to that queue using libnetfilter_queue. For each queued packet, the daemon receives the packet data, makes a verdict (accept, drop, or modify), and returns it.

A per-process firewall built on NFQUEUE would look roughly like this:

# Queue all outbound packets to queue 0
nftables: nft add rule ip filter output queue num 0

# Userspace daemon receives packets, queries /proc for process attribution,
# checks rules, and issues verdict

The daemon then implements its own rule engine: look up the process, check if there is an existing rule for that process/destination pair, and if not, prompt the user. This is essentially the architecture that OpenSnitch uses. OpenSnitch is an open-source GNU/Linux implementation of Little Snitch’s concept, written in Go, with a gRPC-based daemon and a GTK3 frontend. It is functional and actively maintained, but it carries the inherent limitations of the /proc-based attribution approach.

eBPF Changes the Equation

The more modern approach uses eBPF, which allows verified programs to run inside the kernel at specific hook points. Where NFQUEUE requires packets to leave the kernel and return, eBPF can intercept at the socket creation level, before the packet even exists.

With eBPF, you can attach programs to the sock_ops hook to observe TCP state transitions, or use tc (traffic control) hooks for full packet visibility. The critical advantage is that at socket creation time, the eBPF program runs in the context of the creating process, so bpf_get_current_pid_tgid() and bpf_get_current_comm() give you accurate, race-free process information. You store this in a BPF map keyed by socket cookie or connection tuple, and your userspace daemon reads from the map when making policy decisions.

This approach is what tools like Cilium and Falco use for network policy enforcement and security monitoring in Kubernetes environments. The attribution is authoritative because it happens at the moment the socket is created, not after the fact.

For a product like Little Snitch, the ideal architecture probably combines both: eBPF for socket-level attribution stored in maps, and either NFQUEUE or TC hooks for the actual packet hold-and-verdict flow that gives the UI time to prompt the user.

cgroups and the Process Hierarchy Problem

One subtlety that a casual implementation misses is process hierarchy. If a shell script spawns curl, which process is responsible for the connection? The attribution tools above will correctly identify curl as the process with the socket, but the interesting policy question might be about the parent script, or the service manager that launched it.

cgroups v2 provide a natural solution here. Modern Linux systems use a unified cgroup hierarchy where every process belongs to a cgroup, and cgroups can represent logical services (systemd units, containers, user sessions). A sophisticated implementation can attribute network connections to cgroups rather than raw PIDs, which gives much more stable and meaningful policy anchors. A systemd service that restarts with a new PID still belongs to the same cgroup, so existing rules apply without modification.

What a Commercial Implementation Needs to Get Right

OpenSnitch proves the concept works. Where a commercial product like Little Snitch for Linux would need to differentiate is in the details that make security tools trustworthy.

Code signing verification is one. On macOS, Little Snitch can check the code signature of a binary, giving high confidence that the curl trying to phone home is the real curl and not a trojan with the same name. On Linux, there is no universal code signing infrastructure for user applications, though IMA (Integrity Measurement Architecture) and dm-verity provide building blocks for distros that care about this. A commercial implementation would need to decide how to handle this gap, possibly by hashing the binary at rule creation time and alerting when the hash changes.

Rule portability matters too. Little Snitch’s rule format is proprietary and macOS-specific, but a Linux version targeting professional users would benefit from import/export in standard formats, integration with existing firewall management tools, and possibly a command-line interface for scripting.

The GUI question is non-trivial on Linux. macOS gives you one UI toolkit. Linux has GNOME (GTK4), KDE (Qt), and everything else, plus the ongoing Wayland transition. A native GNOME app excludes KDE users; a Qt app looks out of place on GNOME. Electron solves the portability problem while introducing its own overhead and irony (an app that monitors network traffic, built on a runtime that makes a lot of network traffic). Most serious Linux desktop apps in 2026 are either GTK4 with libadwaita or Qt with Kirigami, and both have reasonable cross-desktop compatibility.

Why This Matters Beyond the Product

The arrival of a commercial, well-resourced implementation of per-process network control on Linux matters for a few reasons beyond the convenience of the tool itself. It signals that the Linux desktop is worth treating seriously as a security-conscious platform. It will likely push improvements into the underlying kernel and library ecosystem, the way commercial investment in the X11 and then Wayland ecosystems accelerated work that benefited everyone.

It also highlights how much of the security tooling that macOS and Windows users take for granted simply does not exist on Linux in polished form. OpenSnitch is good software maintained by volunteers. A team with the institutional knowledge and commercial incentive that Objective Development brings to this space can push the quality bar significantly higher, and the open ecosystem means the techniques they develop will influence tools beyond their own product.

For anyone who has spent time with Little Snitch on macOS and then moved to Linux, this announcement lands with some weight. The underlying technology is ready; it just needed someone willing to do the integration work properly.

Was this interesting?