render / rpms / libvirt

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