fix(#2862): open the receiver's port on the mesh interface

argus caught it: binding the socket to the mesh address does not open
the port. NixOS's firewall is default-deny and filters in netfilter,
before a packet reaches a bound socket — the bind chooses which
address accepts connections, not whether packets arrive. As shipped
the receiver was unreachable.

swarm.nix already shows the pattern for exactly this situation: it
opens the mesh's UDP port explicitly right after bringing the
interface up.

Interface-scoped to wg-hive rather than host-wide, so the option's
"reachable exactly by mesh peers" claim is actually true. A global
allowedTCPPorts would open the port on every interface including the
public NIC, leaving only the socket's bind address between the
internet and a root btrfs receive.
This commit is contained in:
atlas 2026-07-31 18:32:01 +02:00 committed by mara
commit 4989579270

View file

@ -191,6 +191,22 @@ in
# creates its own tree lazily.
systemd.tmpfiles.rules = [ "d ${cfg.path} 0700 root root -" ];
# Open the receiver's port, scoped to the mesh interface.
#
# ⚠️ Binding the socket to the mesh address is NOT sufficient on its
# own: NixOS's firewall is default-deny and filters in netfilter,
# before a packet ever reaches a bound socket. The bind chooses
# WHICH address accepts connections; it does not open the port. The
# mesh's own UDP port is opened the same explicit way in swarm.nix.
#
# Interface-scoped rather than host-wide so the reachability
# property stays exactly what the option docs claim --- mesh peers
# and nobody else. A global `allowedTCPPorts` would open the port on
# every interface, including whatever public NIC the box has, and
# only the socket's bind address would still be standing between
# the internet and a root `btrfs receive`.
networking.firewall.interfaces.wg-hive.allowedTCPPorts = [ cfg.port ];
# Socket-activated on purpose: no long-running root daemon, and the
# unit exists only while a transfer does.
#