Blame SOURCES/libvirt-network-force-re-creation-of-iptables-private-chains-on-firewalld-restart.patch

ab145e
From 021167719bebe7fb7a0e366c371b6c7057ebed7e Mon Sep 17 00:00:00 2001
ab145e
Message-Id: <021167719bebe7fb7a0e366c371b6c7057ebed7e@dist-git>
cd2d5b
From: Laine Stump <laine@redhat.com>
ab145e
Date: Wed, 14 Apr 2021 23:25:34 -0400
cd2d5b
Subject: [PATCH] network: force re-creation of iptables private chains on
cd2d5b
 firewalld restart
cd2d5b
MIME-Version: 1.0
cd2d5b
Content-Type: text/plain; charset=UTF-8
cd2d5b
Content-Transfer-Encoding: 8bit
cd2d5b
cd2d5b
When firewalld is stopped, it removes *all* iptables rules and chains,
cd2d5b
including those added by libvirt. Since restarting firewalld means
cd2d5b
stopping and then starting it, any time it is restarted, libvirt needs
cd2d5b
to recreate all the private iptables chains it uses, along with all
cd2d5b
the rules it adds.
cd2d5b
cd2d5b
We already have code in place to call networkReloadFirewallRules() any
cd2d5b
time we're notified of a firewalld start, and
cd2d5b
networkReloadFirewallRules() will call
cd2d5b
networkPreReloadFirewallRules(), which calls
cd2d5b
networkSetupPrivateChains(); unfortunately that last call is called
cd2d5b
using virOnce(), meaning that it will only be called the first time
cd2d5b
through networkPreReloadFirewallRules() after libvirtd starts - so of
cd2d5b
course when firewalld is later restarted, the call to
cd2d5b
networkSetupPrivateChains() is skipped.
cd2d5b
cd2d5b
The neat and tidy way to fix this would be if there was a standard way
cd2d5b
to reset a pthread_once_t object so that the next time virOnce was
cd2d5b
called, it would think the function hadn't been called, and call it
cd2d5b
again. Unfortunately, there isn't any official way of doing that (we
cd2d5b
*could* just fill it with 0 and hope for the best, but that doesn't
cd2d5b
seem very safe.
cd2d5b
cd2d5b
So instead, this patch just adds a static variable called
cd2d5b
chainInitDone, which is set to true after networkSetupPrivateChains()
cd2d5b
is called for the first time, and then during calls to
cd2d5b
networkPreReloadFirewallRules(), if chainInitDone is set, we call
cd2d5b
networkSetupPrivateChains() directly instead of via virOnce().
cd2d5b
cd2d5b
It may seem unsafe to directly call a function that is meant to be
cd2d5b
called only once, but I think in this case we're safe - there's
cd2d5b
nothing in the function that is inherently "once only" - it doesn't
cd2d5b
initialize anything that can't safely be re-initialized (as long as
cd2d5b
two threads don't try to do it at the same time), and it only happens
cd2d5b
when responding to a dbus message that firewalld has been started (and
cd2d5b
I don't think it's possible for us to be processing two of those at
cd2d5b
once), and even then only if the initial call to the function has
cd2d5b
already been completed (so we're safe if we receive a firewalld
cd2d5b
restart call at a time when we haven't yet called it, or even if
cd2d5b
another thread is already in the process of executing it. The only
cd2d5b
problematic bit I can think of is if another thread is in the process
cd2d5b
of adding an iptable rule at the time we're executing this function,
cd2d5b
but 1) none of those threads will be trying to add chains, and 2) if
cd2d5b
there was a concurrency problem with other threads adding iptables
cd2d5b
rules while firewalld was being restarted, it would still be a problem
cd2d5b
even without this change.
cd2d5b
cd2d5b
This is yet another patch that fixes an occurrence of this error:
cd2d5b
cd2d5b
COMMAND_FAILED: '/usr/sbin/iptables -w10 -w --table filter --insert LIBVIRT_INP --in-interface virbr0 --protocol tcp --destination-port 67 --jump ACCEPT' failed: iptables: No chain/target/match by that name.
cd2d5b
cd2d5b
Signed-off-by: Laine Stump <laine@redhat.com>
cd2d5b
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
cd2d5b
(cherry picked from commit f5418b427e7d2f26803880309478de9103680826)
cd2d5b
ab145e
https://bugzilla.redhat.com/1942805
ab145e
(cloned from the RHEL-AV version: https://bugzilla.redhat.com/1813830 )
ab145e
cd2d5b
Conflicts:
cd2d5b
  src/network/bridge_driver.c:
cd2d5b
    In one place a later commit was backported prior to this commit,
cd2d5b
    removing a VIR_DEBUG line and some { }. (see upstream commit
cd2d5b
    c102bbd3efc35, which was backported for
cd2d5b
    https://bugzilla.redhat.com/1607929
cd2d5b
cd2d5b
Signed-off-by: Laine Stump <laine@redhat.com>
ab145e
Message-Id: <20210415032534.723202-3-laine@redhat.com>
ab145e
Reviewed-by: Pavel Hrdina <phrdina@redhat.com>
cd2d5b
---
cd2d5b
 src/network/bridge_driver.c          | 16 ++++---
cd2d5b
 src/network/bridge_driver_linux.c    | 69 ++++++++++++++++++----------
cd2d5b
 src/network/bridge_driver_nop.c      |  3 +-
cd2d5b
 src/network/bridge_driver_platform.h |  2 +-
cd2d5b
 4 files changed, 58 insertions(+), 32 deletions(-)
cd2d5b
cd2d5b
diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c
cd2d5b
index 5995396f78..b8118067d1 100644
cd2d5b
--- a/src/network/bridge_driver.c
cd2d5b
+++ b/src/network/bridge_driver.c
cd2d5b
@@ -271,7 +271,9 @@ static int
cd2d5b
 networkShutdownNetworkExternal(virNetworkObjPtr obj);
cd2d5b
 
cd2d5b
 static void
cd2d5b
-networkReloadFirewallRules(virNetworkDriverStatePtr driver, bool startup);
cd2d5b
+networkReloadFirewallRules(virNetworkDriverStatePtr driver,
cd2d5b
+                           bool startup,
cd2d5b
+                           bool force);
cd2d5b
 
cd2d5b
 static void
cd2d5b
 networkRefreshDaemons(virNetworkDriverStatePtr driver);
cd2d5b
@@ -690,7 +692,7 @@ firewalld_dbus_filter_bridge(DBusConnection *connection G_GNUC_UNUSED,
cd2d5b
     }
cd2d5b
 
cd2d5b
     if (reload)
cd2d5b
-        networkReloadFirewallRules(driver, false);
cd2d5b
+        networkReloadFirewallRules(driver, false, true);
cd2d5b
 
cd2d5b
     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
cd2d5b
 }
cd2d5b
@@ -791,7 +793,7 @@ networkStateInitialize(bool privileged,
cd2d5b
     virNetworkObjListPrune(network_driver->networks,
cd2d5b
                            VIR_CONNECT_LIST_NETWORKS_INACTIVE |
cd2d5b
                            VIR_CONNECT_LIST_NETWORKS_TRANSIENT);
cd2d5b
-    networkReloadFirewallRules(network_driver, true);
cd2d5b
+    networkReloadFirewallRules(network_driver, true, false);
cd2d5b
     networkRefreshDaemons(network_driver);
cd2d5b
 
cd2d5b
     if (virDriverShouldAutostart(network_driver->stateDir, &autostart) < 0)
cd2d5b
@@ -861,7 +863,7 @@ networkStateReload(void)
cd2d5b
                                 network_driver->networkConfigDir,
cd2d5b
                                 network_driver->networkAutostartDir,
cd2d5b
                                 network_driver->xmlopt);
cd2d5b
-    networkReloadFirewallRules(network_driver, false);
cd2d5b
+    networkReloadFirewallRules(network_driver, false, false);
cd2d5b
     networkRefreshDaemons(network_driver);
cd2d5b
     virNetworkObjListForEach(network_driver->networks,
cd2d5b
                              networkAutostartConfig,
cd2d5b
@@ -2229,14 +2231,16 @@ networkReloadFirewallRulesHelper(virNetworkObjPtr obj,
cd2d5b
 
cd2d5b
 
cd2d5b
 static void
cd2d5b
-networkReloadFirewallRules(virNetworkDriverStatePtr driver, bool startup)
cd2d5b
+networkReloadFirewallRules(virNetworkDriverStatePtr driver,
cd2d5b
+                           bool startup,
cd2d5b
+                           bool force)
cd2d5b
 {
cd2d5b
     VIR_INFO("Reloading iptables rules");
cd2d5b
     /* Ideally we'd not even register the driver when unprivilegd
cd2d5b
      * but until we untangle the virt driver that's not viable */
cd2d5b
     if (!driver->privileged)
cd2d5b
         return;
cd2d5b
-    networkPreReloadFirewallRules(driver, startup);
cd2d5b
+    networkPreReloadFirewallRules(driver, startup, force);
cd2d5b
     virNetworkObjListForEach(driver->networks,
cd2d5b
                              networkReloadFirewallRulesHelper,
cd2d5b
                              NULL);
cd2d5b
diff --git a/src/network/bridge_driver_linux.c b/src/network/bridge_driver_linux.c
cd2d5b
index b6b324d1d5..f707bf8e47 100644
cd2d5b
--- a/src/network/bridge_driver_linux.c
cd2d5b
+++ b/src/network/bridge_driver_linux.c
cd2d5b
@@ -36,11 +36,14 @@ VIR_LOG_INIT("network.bridge_driver_linux");
cd2d5b
 #define PROC_NET_ROUTE "/proc/net/route"
cd2d5b
 
cd2d5b
 static virOnceControl createdOnce;
cd2d5b
-static bool createdChains;
cd2d5b
+static bool chainInitDone; /* true iff networkSetupPrivateChains was ever called */
cd2d5b
+static bool createdChains; /* true iff networkSetupPrivateChains created chains during most recent call */
cd2d5b
 static virErrorPtr errInitV4;
cd2d5b
 static virErrorPtr errInitV6;
cd2d5b
 
cd2d5b
-/* Only call via virOnce */
cd2d5b
+/* Usually only called via virOnce, but can also be called directly in
cd2d5b
+ * response to firewalld reload (if chainInitDone == true)
cd2d5b
+ */
cd2d5b
 static void networkSetupPrivateChains(void)
cd2d5b
 {
cd2d5b
     int rc;
cd2d5b
@@ -82,6 +85,8 @@ static void networkSetupPrivateChains(void)
cd2d5b
             VIR_DEBUG("Global IPv6 chains already exist");
cd2d5b
         }
cd2d5b
     }
cd2d5b
+
cd2d5b
+    chainInitDone = true;
cd2d5b
 }
cd2d5b
 
cd2d5b
 
cd2d5b
@@ -111,7 +116,10 @@ networkHasRunningNetworks(virNetworkDriverStatePtr driver)
cd2d5b
 }
cd2d5b
 
cd2d5b
 
cd2d5b
-void networkPreReloadFirewallRules(virNetworkDriverStatePtr driver, bool startup)
cd2d5b
+void
cd2d5b
+networkPreReloadFirewallRules(virNetworkDriverStatePtr driver,
cd2d5b
+                              bool startup,
cd2d5b
+                              bool force)
cd2d5b
 {
cd2d5b
     /*
cd2d5b
      * If there are any running networks, we need to
cd2d5b
@@ -130,29 +138,42 @@ void networkPreReloadFirewallRules(virNetworkDriverStatePtr driver, bool startup
cd2d5b
      * of starting the network though as that makes them
cd2d5b
      * more likely to be seen by a human
cd2d5b
      */
cd2d5b
-    if (!networkHasRunningNetworks(driver)) {
cd2d5b
-        VIR_DEBUG("Delayed global rule setup as no networks are running");
cd2d5b
-        return;
cd2d5b
-    }
cd2d5b
+    if (chainInitDone && force) {
cd2d5b
+        /* The Private chains have already been initialized once
cd2d5b
+         * during this run of libvirtd, so 1) we can't do it again via
cd2d5b
+         * virOnce(), and 2) we need to re-add the private chains even
cd2d5b
+         * if there are currently no running networks, because the
cd2d5b
+         * next time a network is started, libvirt will expect that
cd2d5b
+         * the chains have already been added. So we call directly
cd2d5b
+         * instead of via virOnce().
cd2d5b
+         */
cd2d5b
+        networkSetupPrivateChains();
cd2d5b
 
cd2d5b
-    ignore_value(virOnce(&createdOnce, networkSetupPrivateChains));
cd2d5b
+    } else {
cd2d5b
+        if (!networkHasRunningNetworks(driver)) {
cd2d5b
+            VIR_DEBUG("Delayed global rule setup as no networks are running");
cd2d5b
+            return;
cd2d5b
+        }
cd2d5b
 
cd2d5b
-    /*
cd2d5b
-     * If this is initial startup, and we just created the
cd2d5b
-     * top level private chains we either
cd2d5b
-     *
cd2d5b
-     *   - upgraded from old libvirt
cd2d5b
-     *   - freshly booted from clean state
cd2d5b
-     *
cd2d5b
-     * In the first case we must delete the old rules from
cd2d5b
-     * the built-in chains, instead of our new private chains.
cd2d5b
-     * In the second case it doesn't matter, since no existing
cd2d5b
-     * rules will be present. Thus we can safely just tell it
cd2d5b
-     * to always delete from the builin chain
cd2d5b
-     */
cd2d5b
-    if (startup && createdChains) {
cd2d5b
-        VIR_DEBUG("Requesting cleanup of legacy firewall rules");
cd2d5b
-        iptablesSetDeletePrivate(false);
cd2d5b
+        ignore_value(virOnce(&createdOnce, networkSetupPrivateChains));
cd2d5b
+
cd2d5b
+        /*
cd2d5b
+         * If this is initial startup, and we just created the
cd2d5b
+         * top level private chains we either
cd2d5b
+         *
cd2d5b
+         *   - upgraded from old libvirt
cd2d5b
+         *   - freshly booted from clean state
cd2d5b
+         *
cd2d5b
+         * In the first case we must delete the old rules from
cd2d5b
+         * the built-in chains, instead of our new private chains.
cd2d5b
+         * In the second case it doesn't matter, since no existing
cd2d5b
+         * rules will be present. Thus we can safely just tell it
cd2d5b
+         * to always delete from the builin chain
cd2d5b
+         */
cd2d5b
+        if (startup && createdChains) {
cd2d5b
+            VIR_DEBUG("Requesting cleanup of legacy firewall rules");
cd2d5b
+            iptablesSetDeletePrivate(false);
cd2d5b
+        }
cd2d5b
     }
cd2d5b
 }
cd2d5b
 
cd2d5b
diff --git a/src/network/bridge_driver_nop.c b/src/network/bridge_driver_nop.c
cd2d5b
index 08d737511f..db89c10023 100644
cd2d5b
--- a/src/network/bridge_driver_nop.c
cd2d5b
+++ b/src/network/bridge_driver_nop.c
cd2d5b
@@ -20,7 +20,8 @@
cd2d5b
 #include <config.h>
cd2d5b
 
cd2d5b
 void networkPreReloadFirewallRules(virNetworkDriverStatePtr driver G_GNUC_UNUSED,
cd2d5b
-                                   bool startup G_GNUC_UNUSED)
cd2d5b
+                                   bool startup G_GNUC_UNUSED,
cd2d5b
+                                   bool force G_GNUC_UNUSED)
cd2d5b
 {
cd2d5b
 }
cd2d5b
 
cd2d5b
diff --git a/src/network/bridge_driver_platform.h b/src/network/bridge_driver_platform.h
cd2d5b
index 169417a6c0..48ab52c160 100644
cd2d5b
--- a/src/network/bridge_driver_platform.h
cd2d5b
+++ b/src/network/bridge_driver_platform.h
cd2d5b
@@ -62,7 +62,7 @@ struct _virNetworkDriverState {
cd2d5b
 typedef struct _virNetworkDriverState virNetworkDriverState;
cd2d5b
 typedef virNetworkDriverState *virNetworkDriverStatePtr;
cd2d5b
 
cd2d5b
-void networkPreReloadFirewallRules(virNetworkDriverStatePtr driver, bool startup);
cd2d5b
+void networkPreReloadFirewallRules(virNetworkDriverStatePtr driver, bool startup, bool force);
cd2d5b
 void networkPostReloadFirewallRules(bool startup);
cd2d5b
 
cd2d5b
 int networkCheckRouteCollision(virNetworkDefPtr def);
cd2d5b
-- 
cd2d5b
2.31.1
cd2d5b