From 5b41264fd269d02a227a5a217d4efc26548ee468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 13 Dec 2025 05:09:32 +0100 Subject: [PATCH 1/2] Support reloading in qubes-firewall service --- qubesagent/firewall.py | 47 ++++++++++++++++++++++--------- vm-systemd/qubes-firewall.service | 2 +- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/qubesagent/firewall.py b/qubesagent/firewall.py index 5a43faaab..2af383369 100755 --- a/qubesagent/firewall.py +++ b/qubesagent/firewall.py @@ -27,6 +27,7 @@ import subprocess import pwd import shutil +import time import qubesdb import sys @@ -342,6 +343,7 @@ def dns_addresses(family=None): def main(self): self.terminate_requested = False + self.reload_requested = False self.init() self.run_firewall_dir() if not self.is_custom_persist_enabled(): @@ -355,29 +357,45 @@ def main(self): self.handle_addr(source_addr) self.update_connected_ips(4) self.update_connected_ips(6) - try: - for watch_path in iter(self.qdb.read_watch, None): - if watch_path == '/connected-ips': - self.update_connected_ips(4) + while not self.terminate_requested: + if self.reload_requested: + clock_monotonic = time.clock_gettime(time.CLOCK_MONOTONIC) + clock_monotonic = int(clock_monotonic * 1_000_000) + self.sd_notify(f'RELOADING=1\nMONOTONIC_USEC={clock_monotonic}') + for source_addr in self.list_targets(): + self.handle_addr(source_addr) + self.reload_requested = False + self.sd_notify('READY=1') + try: + watch_path = self.qdb.read_watch() + except OSError: # EINTR + # signal received, re-check loop condition + continue - if watch_path == '/connected-ips6': - self.update_connected_ips(6) + if watch_path is None: + break - # ignore writing rules itself - wait for final write at - # source_addr level empty write (/qubes-firewall/SOURCE_ADDR) - if watch_path.startswith('/qubes-firewall/') and watch_path.count('/') == 2: - source_addr = watch_path.split('/')[2] - self.handle_addr(source_addr) + if watch_path == '/connected-ips': + self.update_connected_ips(4) + + if watch_path == '/connected-ips6': + self.update_connected_ips(6) + + # ignore writing rules itself - wait for final write at + # source_addr level empty write (/qubes-firewall/SOURCE_ADDR) + if watch_path.startswith('/qubes-firewall/') and watch_path.count('/') == 2: + source_addr = watch_path.split('/')[2] + self.handle_addr(source_addr) - except OSError: # EINTR - # signal received, don't continue the loop - pass self.cleanup() def terminate(self): self.terminate_requested = True + def reload(self): + self.reload_requested = True + class NftablesWorker(FirewallWorker): supported_rule_opts = ['action', 'proto', 'dst4', 'dst6', 'dsthost', 'dstports', 'specialtarget', 'icmptype'] @@ -662,6 +680,7 @@ def main(): print('Sorry, iptables no longer supported', file=sys.stderr) sys.exit(1) signal.signal(signal.SIGTERM, lambda _signal, _stack: worker.terminate()) + signal.signal(signal.SIGHUP, lambda _signal, _stack: worker.reload()) worker.main() diff --git a/vm-systemd/qubes-firewall.service b/vm-systemd/qubes-firewall.service index c91cb464b..e49d1d690 100644 --- a/vm-systemd/qubes-firewall.service +++ b/vm-systemd/qubes-firewall.service @@ -5,7 +5,7 @@ After=qubes-iptables.service Before=qubes-network.service [Service] -Type=notify +Type=notify-reload ExecStart=/usr/bin/qubes-firewall [Install] From 08f9d2a3e74a952345317e49fb2b23dbf4bf9b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 13 Dec 2025 04:15:59 +0100 Subject: [PATCH 2/2] network: reload firewall rules on DNS change When DNS changes (for example after switching network, or connecting VPN) it is possible that some names will resolve to different addresses. 2256411a "network: Update specialtarget=dns rules on DNS change" handled one specific case of DNS firewall rules specifically, change it to reloading the whole firewall instead. This also fixes the case where qubes-firewall is not enabled at all. Fixes QubesOS/qubes-issues#10444 --- network/qubes-setup-dnat-to-ns | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/network/qubes-setup-dnat-to-ns b/network/qubes-setup-dnat-to-ns index 74fb286f9..6d740ef62 100755 --- a/network/qubes-setup-dnat-to-ns +++ b/network/qubes-setup-dnat-to-ns @@ -142,10 +142,11 @@ def install_firewall_rules(dns): sys.exit(100) if os.path.exists('/run/qubes-service/qubes-firewall'): - rules.append("flush set ip qubes-firewall dns-addr") - if len(dns_resolved) > 0: - dns_ = ", ".join(str(addr) for addr in dns_resolved) - rules.append(f"add element ip qubes-firewall dns-addr {{ {dns_} }}") + subprocess.call(["systemctl", + "--no-block", + "try-reload-or-restart", + "qubes-firewall.service" + ]) os.execvp("nft", ("nft", "--", "\n".join(preamble + rules)))