summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/PACKAGING-GUIDE.md30
-rwxr-xr-xpackaging/build/build-node.sh16
-rw-r--r--packaging/firewall/firewalld/meshbay-node.xml6
-rw-r--r--packaging/firewall/ufw/meshbay5
-rw-r--r--packaging/rpm/meshbay-node.spec1
5 files changed, 54 insertions, 4 deletions
diff --git a/docs/PACKAGING-GUIDE.md b/docs/PACKAGING-GUIDE.md
index 5a5c388..56504d4 100644
--- a/docs/PACKAGING-GUIDE.md
+++ b/docs/PACKAGING-GUIDE.md
@@ -202,6 +202,36 @@ sudo firewall-cmd --reload
sudo ufw allow "MeshBay Cast"
```
+### Peer connections (node)
+
+Opens inbound UDP 1024-65535. **Scope it to the LAN** — apply the firewalld
+service to the zone holding the LAN interface, and give the ufw rule a
+`from`. It does not belong in an internet-facing zone.
+
+```bash
+# Fedora (firewalld) — replace FedoraWorkstation with your LAN zone
+sudo firewall-cmd --permanent --zone=FedoraWorkstation --add-service=meshbay-node
+sudo firewall-cmd --reload
+
+# Ubuntu (ufw)
+sudo ufw allow from 192.168.1.0/24 app "MeshBay Node"
+
+# a libvirt guest reaching the node on its own hypervisor: scope to the guest
+# subnet, since traffic to the host's own address is not masqueraded
+sudo ufw allow in on virbr0 from 192.168.200.0/24 app "MeshBay Node"
+```
+
+**Why a node needs this.** WebRTC binds an ephemeral UDP port per connection,
+so there is no fixed port to open. A connection succeeds if *either* side can
+initiate. Browsers publish their host candidate as an mDNS `<uuid>.local`
+name, which `aioice` cannot resolve on any platform and discards — so the node
+can never call a browser back, and the browser must call the node. A node that
+refuses unsolicited inbound UDP is unreachable from every browser on its own
+LAN, and falls back to reflexive candidates, which fail whenever both peers
+share one public IP and the router will not hairpin.
+
+The node's administration surface is unaffected: loopback only, see below.
+
The node's own administration surface is a loopback API (127.0.0.1 only,
per-run token) reached by the CLI and the desktop client's Node page. It is
never network-exposed and ships no firewall profile.
diff --git a/packaging/build/build-node.sh b/packaging/build/build-node.sh
index 9ad4968..bd81096 100755
--- a/packaging/build/build-node.sh
+++ b/packaging/build/build-node.sh
@@ -75,12 +75,20 @@ cp "$REPO/packaging/systemd/meshbay-node.service" \
cp "$REPO/packaging/systemd/meshbay-node-user.service" \
"$ROOT/usr/lib/systemd/user/meshbay-node.service"
-# --- Firewall profile ---------------------------------------------------------
-# UFW (Ubuntu/Debian) — carries the "MeshBay Cast" LAN-casting profile only.
-# The node's own admin surface is a loopback API (127.0.0.1, token-gated) and
-# is never firewall-exposed, so it ships no profile.
+# --- Firewall profiles --------------------------------------------------------
+# The node's admin surface is a loopback API (127.0.0.1, token-gated) and is
+# never firewall-exposed. Its *peer* traffic is: WebRTC binds an ephemeral UDP
+# port per connection, and a peer that publishes an unroutable address — every
+# browser does, as an mDNS .local name aioice cannot resolve — can only be
+# reached if it calls the node. A node refusing unsolicited inbound UDP is
+# therefore unreachable from browsers on its own LAN. Both profiles are passive:
+# packaged, not activated, and meant to be scoped to a LAN zone/source.
mkdir -p "$ROOT/etc/ufw/applications.d"
cp "$REPO/packaging/firewall/ufw/meshbay" \
"$ROOT/etc/ufw/applications.d/"
+mkdir -p "$ROOT/usr/lib/firewalld/services"
+cp "$REPO/packaging/firewall/firewalld/meshbay-node.xml" \
+ "$ROOT/usr/lib/firewalld/services/"
+
echo "==> meshbay-node staging ready at $ROOT"
diff --git a/packaging/firewall/firewalld/meshbay-node.xml b/packaging/firewall/firewalld/meshbay-node.xml
new file mode 100644
index 0000000..2f863ec
--- /dev/null
+++ b/packaging/firewall/firewalld/meshbay-node.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<service>
+ <short>MeshBay Node</short>
+ <description>Inbound WebRTC/ICE for a MeshBay node. Peers connect over a DataChannel negotiated through the hub; there is no fixed listening port, because aiortc binds an ephemeral UDP port per connection. A peer that cannot reach the node directly can only be reached if the node initiates, which requires the peer to publish a routable address — browsers do not, they publish an mDNS .local name the node cannot resolve. So a node that refuses unsolicited inbound UDP is unreachable from any browser it cannot itself call back. Apply this to the zone holding the LAN interface only; it does not belong in an internet-facing zone. The node's administration surface is unaffected: it is a loopback API on 127.0.0.1 and is never exposed here.</description>
+ <port protocol="udp" port="1024-65535"/>
+</service>
diff --git a/packaging/firewall/ufw/meshbay b/packaging/firewall/ufw/meshbay
index 732e5d8..b87afdb 100644
--- a/packaging/firewall/ufw/meshbay
+++ b/packaging/firewall/ufw/meshbay
@@ -2,3 +2,8 @@
title=MeshBay Chromecast relay
description=HTTP relay for casting decrypted video to LAN devices
ports=19550:19553/tcp|5353/udp
+
+[MeshBay Node]
+title=MeshBay node WebRTC/ICE
+description=Inbound UDP for peer connections; scope it to your LAN with "from"
+ports=1024:65535/udp
diff --git a/packaging/rpm/meshbay-node.spec b/packaging/rpm/meshbay-node.spec
index 2bebb26..7910a83 100644
--- a/packaging/rpm/meshbay-node.spec
+++ b/packaging/rpm/meshbay-node.spec
@@ -43,6 +43,7 @@ fi
%{_unitdir}/meshbay-node@.service
%{_userunitdir}/meshbay-node.service
/etc/ufw/applications.d/meshbay
+/usr/lib/firewalld/services/meshbay-node.xml
%changelog
* Sun Aug 31 2026 MeshBay Team <team@meshbay.org> - %{version}-1