Skip to content

daemon/rule: atomic rule saves + built-in network aliases fallback - #1705

Open
andrewfader wants to merge 2 commits into
evilsocket:masterfrom
andrewfader:rule-atomic-save-and-default-aliases
Open

andrewfader wants to merge 2 commits into
evilsocket:masterfrom
andrewfader:rule-atomic-save-and-default-aliases

Conversation

@andrewfader

@andrewfader andrewfader commented Nov 5, 2026 •

Copy link
Copy Markdown

Two small daemon fixes for problems that showed up as repeated warnings in /var/log/opensnitchd.log on an Arch Linux install (opensnitch 1.7.2, nftables, eBPF procmon). Each is its own commit with tests.

1. daemon/rule: save rules atomically

Loader.Save() used ioutil.WriteFile(), which truncates and then writes. A kill or power loss between the two leaves an empty/partial rule file; from then on every daemon start logs

Error parsing rule from /etc/opensnitchd/rules/<name>.json: unexpected end of JSON input

and the rule is silently not enforced. I found exactly such a 0-byte rule file on my machine (written 2026-07-21). The fix writes to a temp file in the rules directory, fsyncs, and rename()s over the target, so readers (including the loader's own fsnotify watcher) only ever see a complete file. Mode stays 0600.

2. daemon/rule: fall back to built-in network aliases

The UI offers "LAN"/"MULTICAST" as destination networks and the prompt dialog writes them verbatim into dest.network rules; the daemon resolves them from /etc/opensnitchd/network_aliases.json. v1.7.2 shipped the daemon-side alias code (daemon/rule/operator_aliases.go) and the UI that emits "LAN", but daemon/data/network_aliases.json only appeared in v1.8.0, so no v1.7.2 package could install it and every such rule fails with

Operator.Compile() error: CIDR parsing error: invalid CIDR address: LAN

and is disabled. That is the root cause behind #1559 (closed with "copy the file"), and it recurs on any install where the file is missing or damaged. This keeps the file as the source of truth but loads DefaultAliases (a copy of daemon/data/network_aliases.json) when it is missing or invalid, so the aliases the UI exposes always resolve. It also accepts bare IPs in alias lists as /32 or /128: the shipped file contains "::1", which net.ParseCIDR() rejects, so IPv6 loopback was being dropped from LAN silently.

Testing

  • go build ./..., go vet ./rule/ (only the pre-existing copylocks notes in loader.go/rule.go), go test ./rule/ all green on Go 1.27 / Linux 7.3.
  • New tests: TestLoaderSaveIsAtomic (file complete and parseable after two saves, mode 0600, no temp files left), TestLoaderSaveMissingDir, TestLoadAliasesFallsBackToDefaults (missing file → LAN/MULTICAST resolve and a dest.network LAN operator compiles), TestLoadAliasesInvalidFileFallsBack, TestLoadAliasesFromFile (bare IPv4/IPv6 become /32 and /128, unparseable entries skipped, defaults not merged when a file loads).
  • Not yet run: PRIVILEGED_TESTS=1 NETLINK_TESTS=1 go test ./... as root (unrelated packages).

Loader.Save() wrote the rule with ioutil.WriteFile(), which truncates the
target first and then writes. If the daemon is killed or the machine loses
power between those two steps, the rule file is left empty (or partial).
On the next start the loader logs

  Error parsing rule from /etc/opensnitchd/rules/<name>.json: unexpected end of JSON input

and skips it, so a rule the user believes is active is silently gone until
they notice the warning. A zero-byte rule file produced this way was found
on a production machine.

Write to a temporary file in the rules directory, fsync it, and rename it
over the target. rename(2) is atomic on the same filesystem, so readers
(including the loader's own fsnotify watcher) only ever see the old
complete file or the new complete file. Mode stays 0600.
@andrewfader
andrewfader force-pushed the rule-atomic-save-and-default-aliases branch from 84985c8 to e137a96 Compare November 5, 2026 00:12
…ile is missing

The UI offers "LAN" and "MULTICAST" as destination networks and writes
them verbatim into dest.network rules. The daemon resolves them from
/etc/opensnitchd/network_aliases.json; when that file is absent every such
rule fails to compile with

  Operator.Compile() error: CIDR parsing error: invalid CIDR address: LAN

and is disabled. v1.7.2 shipped the daemon-side alias code and a UI that
emits "LAN", but daemon/data/network_aliases.json was only added for
v1.8.0, so no v1.7.2 package could install it (Arch's 1.7.2 package, for
example, has nothing to install). That is the root cause of evilsocket#1559, and the
same thing happens on any install where the file is missing or damaged.

Keep the file as the source of truth, but when it is missing or invalid
load DefaultAliases (a copy of daemon/data/network_aliases.json) so the
aliases the UI exposes always resolve. While here, accept bare IP
addresses in alias lists as /32 or /128: the shipped file contains "::1",
which net.ParseCIDR() rejects, so loopback IPv6 was silently dropped from
the LAN alias.
@andrewfader
andrewfader force-pushed the rule-atomic-save-and-default-aliases branch from e137a96 to 12a02aa Compare November 5, 2026 00:17
@andrewfader
andrewfader marked this pull request as ready for review November 5, 2026 00:20

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant