qubes-opensnitch-pipes resolves the identity crisis when running OpenSnitch in Qubes OS.
By default, connecting multiple AppVMs to a central OpenSnitch UI results in all traffic appearing as "localhost," making it impossible to distinguish between source VMs. This project utilizes helper scripts to tunnel traffic through unique ports and dummy network interfaces, allowing the OpenSnitch UI to correctly identify and filter traffic per VM.
⚠️ Status: Experimental This setup is experimental. While it works to separate VM identities, reliability with multiple concurrent nodes is not guaranteed.
This system consists of two components:
-
qubes-opensnitch-piped(The Server/UI Side):- Runs on the VM hosting the OpenSnitch UI (e.g.,
snitch-ui). - Listens on a control port (default
50050) to assign slots. - Creates dummy network devices (e.g.,
dummy0at127.0.0.2) for each registered node. - Redirects traffic from the node's assigned port to the OpenSnitch UI, preserving a unique IP per node.
- Runs on the VM hosting the OpenSnitch UI (e.g.,
-
qubes-opensnitch-pipe(The Client/Node Side):- Runs on the AppVMs you want to monitor.
- Connects to the server via Qubes RPC (
qubes.ConnectTCP). - Requests a free port, injects it into
/etc/opensnitchd/default-config.json, establishes asocatpipe, and starts the system daemon.
- Qubes OS
- OpenSnitch installed on the TemplateVM used by your nodes and the UI VM.
Remember to disable opensnitch systemd service systemctl disable opensnitch and remove /etc/xdg/autostart/opensnitch_ui.desktop. We will handle these steps differently later.
You must allow TCP connections between your nodes and the UI VM.
Edit /etc/qubes/policy.d/30-opensnitch.policy in dom0:
# OpenSnitch node connections
# 50050 is the handshake/control port
qubes.ConnectTCP +50050 @tag:snitch @default allow target=snitch-ui
# 50051+ are the data ports (one per node slot)
qubes.ConnectTCP +50052 @tag:snitch @default allow target=snitch-ui
qubes.ConnectTCP +50053 @tag:snitch @default allow target=snitch-ui
qubes.ConnectTCP +50054 @tag:snitch @default allow target=snitch-ui
qubes.ConnectTCP +50055 @tag:snitch @default allow target=snitch-ui
qubes.ConnectTCP +50056 @tag:snitch @default allow target=snitch-ui
qubes.ConnectTCP +50057 @tag:snitch @default allow target=snitch-ui
qubes.ConnectTCP +50058 @tag:snitch @default allow target=snitch-ui
qubes.ConnectTCP +50059 @tag:snitch @default allow target=snitch-uiReplace snitch-ui with the actual name of your UI AppVM.
Apply the snitch tag to any AppVM that should send data to the UI:
qvm-tags add [VM_NAME] snitchConfigure the AppVM where opensnitch-ui will run.
Create a config file to map your VMs to specific loopback IP addresses. This ensures that sys-net always appears as 127.0.0.3 (for example).
mkdir -p ~/.config/qubes-opensnitch-piped
nano ~/.config/qubes-opensnitch-piped/config.jsonExample config.json:
{
"vault": "127.0.0.2",
"sys-net": "127.0.0.3",
"sys-usb": "127.0.0.4",
"gpu-personal": "127.0.0.5",
"sys-protonvpn": "127.0.0.6",
"test": "127.0.0.7"
}Create the user-level systemd service file:
File: ~/.config/systemd/user/qubes-opensnitch-piped.service
[Unit]
Description=Qubes OpenSnitch Piped Connector
After=graphical-session.target
[Service]
ExecStart=/usr/local/bin/qubes-opensnitch-piped -ad \
-c /home/user/.config/qubes-opensnitch-piped/config.json
Restart=on-failure
[Install]
WantedBy=default.targetEnable the user-level systemd service for the pipe daemon:
systemctl --user enable --now qubes-opensnitch-pipedEnsure the standard OpenSnitch UI is listening on all interfaces (or specifically the IPv6 wildcard) so it can accept the forwarded traffic. Add this to your /rw/config/rc.local or autostart:
# Start local daemon
systemctl enable --now opensnitch
# Start UI listening on port 50051
opensnitch-ui --socket "[::]:50051" &There are two ways to configure the nodes: via the TemplateVM (System-wide) or per AppVM (User mode).
Since AppVMs reset /etc on reboot, you must symlink the rules directory to a persistent location. Run this on your TemplateVM:
mkdir -p /rw/config/opensnitch/rules
rm -rf /etc/opensnitchd/rules
ln -s /rw/config/opensnitch/rules /etc/opensnitchd/rulesThis method allows you to deploy the script in a TemplateVM but only activate it on specific hosts (e.g., sys-net).
File: /etc/systemd/system/qubes-opensnitch-pipe.service
[Unit]
Description=Qubes OpenSnitch Pipe
After=graphical-session.target
# Only start on these specific VMs:
ConditionHost=|sys-net
ConditionHost=|sys-usb
[Service]
ExecStart=/usr/local/bin/qubes-opensnitch-pipe -sp /rw/config/opensnitchd/rules
Restart=on-failure
KillMode=process
[Install]
WantedBy=default.targetNote: The -sp flag adds a prefix to the rules directory (e.g., /rw/config/opensnitchd/rules.sys-net), allowing different rule sets for different VMs.
If you prefer configuring per AppVM (requires sudo privileges):
File: ~/.config/systemd/user/qubes-opensnitch-pipe.service
[Unit]
Description=Qubes OpenSnitch Pipe
After=graphical-session.target
[Service]
ExecStart=/usr/local/bin/qubes-opensnitch-pipe -sp /rw/config/opensnitchd/rules
Restart=on-failure
KillMode=process
[Install]
WantedBy=default.targetEnable it: systemctl --user enable --now qubes-opensnitch-pipe
-
Check if UI is listening:
sudo ss -tulnp | grep 50051 # Should show: LISTEN ... opensnitch-ui
-
Check if Piped is running:
systemctl --user status qubes-opensnitch-piped
-
Check connection to the pipe:
sudo ss -tulnp | grep 50052 # (Or whichever port was assigned)
-
Check the daemon status:
systemctl status opensnitch
Run a network request from the Node VM:
curl [http://example.com](http://example.com)A popup should appear in snitch-ui on the Server VM.
Note: ICMP requests (
ping) do not reliably generate events in Qubes networking. Usecurlordigfor testing.
usage: qubes-opensnitch-pipe [-h] [-c CONFIG] [-t CONNECT] [-p PORT]
[-s RULES] [-sp RULES_PREFIX] [-d RULES_DEST]
[-ll {info,warn,error,critical}]
options:
-h, --help show this help message and exit
-c, --config OpenSnitch config file (where port is injected)
-t, --connect IP address for server connection
-p, --port Port for server connection
-s, --rules Rules directory (copied to --rules-dest)
-sp, --rules-prefix Rules directory prefix (adds .$hostname to path)
-d, --rules-dest Define OpenSnitch rules directory (default: /etc/opensnitchd/rules)
usage: qubes-opensnitch-piped [-h] [-c CONFIG] [-a ADDRESS] [-p PORT]
[-l LISTEN] [-lp LISTEN_PORT] [-ad]
[-ll {info,warn,error,critical}]
options:
-h, --help show this help message and exit
-c, --config JSON file path for { "host":"ip_address" } pairs
-a, --address Starting IP address (default: 127.0.0.1)
-p, --port Starting port for node connections (default: 50051)
-l, --listen Listen address for service (default: 127.0.0.1)
-lp, --listen-port Listen port for service (default: 50050)
-ad, --allow-disposables Allow disposables without address defined in config