[libvirt PATCH 21/28] util: implement rollback rule autosave for nftables backend

Laine Stump laine at redhat.com
Mon May 1 03:19:36 UTC 2023


Determining the correct rollback rule for nftables is more complicated
than iptables - nftables give each new table/chain/rule a handle, and
the nft delete command to delete the object must contain that handle
(rather than just replicating the entire original commandline as is
done for iptables).

The handle is obtained by adding an extra "-ae" option to the original
nft commandline, and then parsing stdout of the command looking for "#
handle n" (where "n" is a decimal integer).

This code isn't yet used anywhere, since
VIR_FIREWALL_TRANSACTION_AUTO_ROLLBACK isn't being set.

Signed-off-by: Laine Stump <laine at redhat.com>
---
 src/util/virnftables.c | 106 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 105 insertions(+), 1 deletion(-)

diff --git a/src/util/virnftables.c b/src/util/virnftables.c
index b43b14bb82..0cc09caaed 100644
--- a/src/util/virnftables.c
+++ b/src/util/virnftables.c
@@ -71,12 +71,18 @@ VIR_ENUM_IMPL(virNftablesAction,
 );
 
 
+#define VIR_ARG_IS_INSERT(arg) \
+    (STREQ(arg, "insert") || STREQ(arg, "add") || STREQ(arg, "create"))
+
 int
 virNftablesApplyFirewallRule(virFirewall *firewall G_GNUC_UNUSED,
                              virFirewallRule *rule,
                              char **output)
 {
     size_t count = virFirewallRuleGetArgCount(rule);
+    bool needRollback = false;
+    size_t cmdIdx = 0;
+    const char *objectType = NULL;
     g_autoptr(virCommand) cmd = NULL;
     g_autofree char *cmdStr = NULL;
     g_autofree char *error = NULL;
@@ -91,11 +97,45 @@ virNftablesApplyFirewallRule(virFirewall *firewall G_GNUC_UNUSED,
 
     cmd = virCommandNew(NFT);
 
+    if ((virFirewallTransactionGetFlags(firewall)
+         & VIR_FIREWALL_TRANSACTION_AUTO_ROLLBACK)
+        && count > 1) {
+        /* skip any leading options to get to command verb */
+        for (i = 0; i < count - 1; i++) {
+            if (virFirewallRuleGetArg(rule, i)[0] != '-')
+                break;
+        }
+
+        if (i + 1 < count
+            && VIR_ARG_IS_INSERT(virFirewallRuleGetArg(rule, i))) {
+
+            cmdIdx = i;
+            objectType = virFirewallRuleGetArg(rule, i + 1);
+
+            /* we currently only handle auto-rollback for rules,
+             * chains, and tables, and those all can be "rolled
+             * back" by a delete command using the handle that is
+             * returned when "-ae" is added to the add/insert
+             * command.
+             */
+            if (STREQ_NULLABLE(objectType, "rule")
+                || STREQ_NULLABLE(objectType, "chain")
+                || STREQ_NULLABLE(objectType, "table")) {
+
+                needRollback = true;
+                /* this option to nft instructs it to add the
+                 * "handle" of the created object to stdout
+                 */
+                virCommandAddArg(cmd, "-ae");
+            }
+        }
+    }
+
     for (i = 0; i < count; i++)
         virCommandAddArg(cmd, virFirewallRuleGetArg(rule, i));
 
     cmdStr = virCommandToString(cmd, false);
-    VIR_INFO("Applying rule '%s'", NULLSTR(cmdStr));
+    VIR_INFO("Applying '%s'", NULLSTR(cmdStr));
 
     virCommandSetOutputBuffer(cmd, output);
     virCommandSetErrorBuffer(cmd, &error);
@@ -118,8 +158,72 @@ virNftablesApplyFirewallRule(virFirewall *firewall G_GNUC_UNUSED,
             VIR_FREE(*output);
             return -1;
         }
+
+        /* there was an error, so we won't be building any rollback rule,
+         * but the error should be ignored, so we return success
+         */
+        return 0;
     }
 
+    if (needRollback) {
+        virFirewallRule *rollback
+            = virFirewallAddRollbackRule(firewall,
+                                         virFirewallRuleGetLayer(rule), NULL);
+        const char *handleStart = NULL;
+        size_t handleLen = 0;
+        g_autofree char *handleStr = NULL;
+        g_autofree char *rollbackStr = NULL;
+
+        /* Search for "# handle n" in stdout of the nft add command -
+         * that is the handle of the table/rule/chain that will later
+         * need to be deleted.
+         */
+
+        if ((handleStart = strstr(*output, "# handle "))) {
+            handleStart += 9; /* move past "# handle " */
+            handleLen = strspn(handleStart, "0123456789");
+        }
+
+        if (!handleLen) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("couldn't register rollback command - command '%1$s' had no valid handle in output ('%2$s')"),
+                           NULLSTR(cmdStr), NULLSTR(*output));
+            return -1;
+        }
+
+        handleStr = g_strdup_printf("%.*s", (int)handleLen, handleStart);
+
+        /* The rollback rule is created from the original rule like this:
+         *
+         * 1) skip any leading options
+         * 2) replace add/insert with delete
+         * 3) keep the type of item being added (rule/chain/table)
+         * 4) keep the class (ip/ip6/inet)
+         * 5) for chain/rule, keep the table name
+         * 6) for rule, keep the chain name
+         * 7) add "handle n" where "n" is parsed from the
+         *    stdout of the nft command
+         */
+        virFirewallRuleAddArgList(firewall, rollback, "delete", objectType,
+                                  virFirewallRuleGetArg(rule, cmdIdx + 2), /* ip/ip6/inet */
+                                  NULL);
+
+        if (STREQ_NULLABLE(objectType, "rule")
+            || STREQ_NULLABLE(objectType, "chain")) {
+            /* include table name in command */
+            virFirewallRuleAddArg(firewall, rollback,
+                                  virFirewallRuleGetArg(rule, cmdIdx + 3));
+        }
+        if (STREQ_NULLABLE(objectType, "rule")) {
+            /* include chain name in command */
+            virFirewallRuleAddArg(firewall, rollback,
+                                  virFirewallRuleGetArg(rule, cmdIdx + 4));
+        }
+        virFirewallRuleAddArgList(firewall, rollback, "handle", handleStr, NULL);
+
+        rollbackStr = virFirewallRuleToString(NFT, rollback);
+        VIR_DEBUG("Recording Rollback command '%s'", NULLSTR(rollbackStr));
+    }
     return 0;
 }
 
-- 
2.39.2



More information about the libvir-list mailing list