[libvirt PATCH 16/28] util: add nftables backend to virnetfilter API used by network driver

Laine Stump posted 28 patches 1 year, 6 months ago
There is a newer version of this series
[libvirt PATCH 16/28] util: add nftables backend to virnetfilter API used by network driver
Posted by Laine Stump 1 year, 6 months ago
Signed-off-by: Laine Stump <laine@redhat.com>
---
 po/POTFILES                      |   1 +
 src/network/bridge_driver_conf.c |   4 +
 src/network/network.conf         |  17 +-
 src/util/meson.build             |   1 +
 src/util/virfirewall.c           |   3 +-
 src/util/virfirewall.h           |   1 +
 src/util/virnetfilter.c          |  48 +++
 src/util/virnftables.c           | 594 +++++++++++++++++++++++++++++++
 src/util/virnftables.h           | 118 ++++++
 9 files changed, 784 insertions(+), 3 deletions(-)
 create mode 100644 src/util/virnftables.c
 create mode 100644 src/util/virnftables.h

diff --git a/po/POTFILES b/po/POTFILES
index d20ac36062..4966f71eb3 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -304,6 +304,7 @@ src/util/virnetdevveth.c
 src/util/virnetdevvportprofile.c
 src/util/virnetfilter.c
 src/util/virnetlink.c
+src/util/virnftables.c
 src/util/virnodesuspend.c
 src/util/virnuma.c
 src/util/virnvme.c
diff --git a/src/network/bridge_driver_conf.c b/src/network/bridge_driver_conf.c
index 9769ee06b5..d9f07cf448 100644
--- a/src/network/bridge_driver_conf.c
+++ b/src/network/bridge_driver_conf.c
@@ -98,6 +98,7 @@ virNetworkLoadDriverConfig(virNetworkDriverConfig *cfg G_GNUC_UNUSED,
          * for binaries used by the backends, and set accordingly.
          */
         g_autofree char *iptablesInPath = NULL;
+        g_autofree char *nftInPath = NULL;
 
         /* virFindFileInPath() uses g_find_program_in_path(),
          * which allows absolute paths, and verifies that
@@ -105,6 +106,9 @@ virNetworkLoadDriverConfig(virNetworkDriverConfig *cfg G_GNUC_UNUSED,
         */
         if ((iptablesInPath = virFindFileInPath(IPTABLES)))
             cfg->firewallBackend = VIR_FIREWALL_BACKEND_IPTABLES;
+        else if ((nftInPath = virFindFileInPath(NFT)))
+            cfg->firewallBackend = VIR_FIREWALL_BACKEND_NFTABLES;
+
 
         if (cfg->firewallBackend == VIR_FIREWALL_BACKEND_UNSET)
             VIR_INFO("firewall_backend not set, and no usable backend auto-detected");
diff --git a/src/network/network.conf b/src/network/network.conf
index 74c79e4cc6..630c4387a1 100644
--- a/src/network/network.conf
+++ b/src/network/network.conf
@@ -5,7 +5,20 @@
 # firewall_backend:
 #
 #   determines which subsystem to use to setup firewall packet
-#   filtering rules for virtual networks. Currently the only supported
-#   selection is "iptables".
+#   filtering rules for virtual networks.
+#
+#   Supported settings:
+#
+#     iptables - use iptables commands to construct the firewall
+#     nftables - use nft commands to construct the firewall
+#
+#   For backward compatibility, and to reduce surprises, the
+#   default setting is "iptables".
+#
+#   (NB: switching from one backend to another while there are active
+#   virtual networks *is* supported. The change will take place the
+#   next time that libvirtd/virtnetworkd is restarted - all existing
+#   virtual networks will have their old firewalls removed, and then
+#   reloaded using the new backend.)
 #
 #firewall_backend = "iptables"
diff --git a/src/util/meson.build b/src/util/meson.build
index aa570ed02a..c0e71760b1 100644
--- a/src/util/meson.build
+++ b/src/util/meson.build
@@ -71,6 +71,7 @@ util_sources = [
   'virnetdevvportprofile.c',
   'virnetfilter.c',
   'virnetlink.c',
+  'virnftables.c',
   'virnodesuspend.c',
   'virnuma.c',
   'virnvme.c',
diff --git a/src/util/virfirewall.c b/src/util/virfirewall.c
index fa21266fb2..17acc2adc3 100644
--- a/src/util/virfirewall.c
+++ b/src/util/virfirewall.c
@@ -39,7 +39,8 @@ VIR_LOG_INIT("util.firewall");
 VIR_ENUM_IMPL(virFirewallBackend,
               VIR_FIREWALL_BACKEND_LAST,
               "UNSET", /* not yet set */
-              "iptables");
+              "iptables",
+              "nftables");
 
 typedef struct _virFirewallGroup virFirewallGroup;
 
diff --git a/src/util/virfirewall.h b/src/util/virfirewall.h
index 020dd2bedb..4d03dc3b3b 100644
--- a/src/util/virfirewall.h
+++ b/src/util/virfirewall.h
@@ -46,6 +46,7 @@ typedef enum {
 typedef enum {
     VIR_FIREWALL_BACKEND_UNSET,
     VIR_FIREWALL_BACKEND_IPTABLES,
+    VIR_FIREWALL_BACKEND_NFTABLES,
 
     VIR_FIREWALL_BACKEND_LAST,
 } virFirewallBackend;
diff --git a/src/util/virnetfilter.c b/src/util/virnetfilter.c
index e6a748e877..0fc541687e 100644
--- a/src/util/virnetfilter.c
+++ b/src/util/virnetfilter.c
@@ -29,6 +29,7 @@
 #include "internal.h"
 #include "virnetfilter.h"
 #include "viriptables.h"
+#include "virnftables.h"
 #include "vircommand.h"
 #include "viralloc.h"
 #include "virerror.h"
@@ -75,6 +76,9 @@ virNetfilterApplyFirewallRule(virFirewall *fw,
     case VIR_FIREWALL_BACKEND_IPTABLES:
         return virIptablesApplyFirewallRule(fw, rule, output);
 
+    case VIR_FIREWALL_BACKEND_NFTABLES:
+        return virNftablesApplyFirewallRule(fw, rule, output);
+
     case VIR_FIREWALL_BACKEND_UNSET:
     case VIR_FIREWALL_BACKEND_LAST:
         virNetFilterBackendUnsetError();
@@ -101,6 +105,9 @@ virNetfilterSetupPrivateChains(virFirewallBackend backend,
     case VIR_FIREWALL_BACKEND_IPTABLES:
         return iptablesSetupPrivateChains(layer);
 
+    case VIR_FIREWALL_BACKEND_NFTABLES:
+        return virNftablesSetupPrivateChains(layer);
+
     case VIR_FIREWALL_BACKEND_UNSET:
     case VIR_FIREWALL_BACKEND_LAST:
         virNetFilterBackendUnsetError();
@@ -123,6 +130,10 @@ virNetfilterInput(virFirewall *fw,
         iptablesInput(fw, layer, iface, port, action, tcp);
         break;
 
+    case VIR_FIREWALL_BACKEND_NFTABLES:
+        virNftablesInput(fw, layer, iface, port, action, tcp);
+        break;
+
     case VIR_FIREWALL_BACKEND_UNSET:
     case VIR_FIREWALL_BACKEND_LAST:
         break;
@@ -143,6 +154,10 @@ virNetfilterOutput(virFirewall *fw,
         iptablesOutput(fw, layer, iface, port, action, tcp);
         break;
 
+    case VIR_FIREWALL_BACKEND_NFTABLES:
+        virNftablesOutput(fw, layer, iface, port, action, tcp);
+        break;
+
     case VIR_FIREWALL_BACKEND_UNSET:
     case VIR_FIREWALL_BACKEND_LAST:
         break;
@@ -163,6 +178,10 @@ virNetfilterForwardAllowOut(virFirewall *fw,
         return iptablesForwardAllowOut(fw, netaddr, prefix,
                                        iface, physdev, action);
 
+    case VIR_FIREWALL_BACKEND_NFTABLES:
+        return virNftablesForwardAllowOut(fw, netaddr, prefix,
+                                          iface, physdev, action);
+
     case VIR_FIREWALL_BACKEND_UNSET:
     case VIR_FIREWALL_BACKEND_LAST:
         virNetFilterBackendUnsetError();
@@ -185,6 +204,10 @@ virNetfilterForwardAllowRelatedIn(virFirewall *fw,
         return iptablesForwardAllowRelatedIn(fw, netaddr, prefix,
                                              iface, physdev, action);
 
+    case VIR_FIREWALL_BACKEND_NFTABLES:
+        return virNftablesForwardAllowRelatedIn(fw, netaddr, prefix,
+                                                iface, physdev, action);
+
     case VIR_FIREWALL_BACKEND_UNSET:
     case VIR_FIREWALL_BACKEND_LAST:
         virNetFilterBackendUnsetError();
@@ -207,6 +230,10 @@ virNetfilterForwardAllowIn(virFirewall *fw,
         return iptablesForwardAllowIn(fw, netaddr, prefix,
                                       iface, physdev, action);
 
+    case VIR_FIREWALL_BACKEND_NFTABLES:
+        return virNftablesForwardAllowIn(fw, netaddr, prefix,
+                                         iface, physdev, action);
+
     case VIR_FIREWALL_BACKEND_UNSET:
     case VIR_FIREWALL_BACKEND_LAST:
         virNetFilterBackendUnsetError();
@@ -227,6 +254,10 @@ virNetfilterForwardAllowCross(virFirewall *fw,
         iptablesForwardAllowCross(fw, layer, iface, action);
         break;
 
+    case VIR_FIREWALL_BACKEND_NFTABLES:
+        virNftablesForwardAllowCross(fw, layer, iface, action);
+        break;
+
     case VIR_FIREWALL_BACKEND_UNSET:
     case VIR_FIREWALL_BACKEND_LAST:
         break;
@@ -245,6 +276,10 @@ virNetfilterForwardRejectOut(virFirewall *fw,
         iptablesForwardRejectOut(fw, layer, iface, action);
         break;
 
+    case VIR_FIREWALL_BACKEND_NFTABLES:
+        virNftablesForwardRejectOut(fw, layer, iface, action);
+        break;
+
     case VIR_FIREWALL_BACKEND_UNSET:
     case VIR_FIREWALL_BACKEND_LAST:
         break;
@@ -263,6 +298,10 @@ virNetfilterForwardRejectIn(virFirewall *fw,
         iptablesForwardRejectIn(fw, layer, iface, action);
         break;
 
+    case VIR_FIREWALL_BACKEND_NFTABLES:
+        virNftablesForwardRejectIn(fw, layer, iface, action);
+        break;
+
     case VIR_FIREWALL_BACKEND_UNSET:
     case VIR_FIREWALL_BACKEND_LAST:
         break;
@@ -285,6 +324,11 @@ virNetfilterForwardMasquerade(virFirewall *fw,
         return iptablesForwardMasquerade(fw, netaddr, prefix, physdev,
                                          addr, port, protocol, action);
 
+
+    case VIR_FIREWALL_BACKEND_NFTABLES:
+        return virNftablesForwardMasquerade(fw, netaddr, prefix, physdev,
+                                            addr, port, protocol, action);
+
     case VIR_FIREWALL_BACKEND_UNSET:
     case VIR_FIREWALL_BACKEND_LAST:
         virNetFilterBackendUnsetError();
@@ -307,6 +351,10 @@ virNetfilterForwardDontMasquerade(virFirewall *fw,
         return iptablesForwardDontMasquerade(fw, netaddr, prefix,
                                              physdev, destaddr, action);
 
+    case VIR_FIREWALL_BACKEND_NFTABLES:
+        return virNftablesForwardDontMasquerade(fw, netaddr, prefix,
+                                                physdev, destaddr, action);
+
     case VIR_FIREWALL_BACKEND_UNSET:
     case VIR_FIREWALL_BACKEND_LAST:
         virNetFilterBackendUnsetError();
diff --git a/src/util/virnftables.c b/src/util/virnftables.c
new file mode 100644
index 0000000000..b43b14bb82
--- /dev/null
+++ b/src/util/virnftables.c
@@ -0,0 +1,594 @@
+/*
+ * virnftables.c: helper APIs for managing nftables filter rules
+ *
+ * Copyright (C) 2023 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdarg.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "internal.h"
+#include "virnetfilter.h"
+#include "virnftables.h"
+#include "virfirewalld.h"
+#include "vircommand.h"
+#include "viralloc.h"
+#include "virerror.h"
+#include "virfile.h"
+#include "virlog.h"
+#include "virthread.h"
+#include "virstring.h"
+#include "virutil.h"
+#include "virhash.h"
+
+VIR_LOG_INIT("util.nftables");
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+#define VIR_NFTABLES_PRIVATE_TABLE "libvirt"
+
+/* nftables backend uses the same binary (nft) for all layers, but
+ * IPv4 and IPv6 have their rules in separate classes of tables,
+ * either "ip" or "ip6". (there is also an "inet" class of tables that
+ * would examined for both IPv4 and IPv6 traffic, but since we want
+ * different rules for each family, we only use the family-specific
+ * table classes).
+ */
+VIR_ENUM_DECL(virNftablesLayer);
+VIR_ENUM_IMPL(virNftablesLayer,
+              VIR_FIREWALL_LAYER_LAST,
+              "",
+              "ip",
+              "ip6",
+);
+
+
+VIR_ENUM_DECL(virNftablesAction);
+VIR_ENUM_IMPL(virNftablesAction,
+              VIR_FIREWALL_ACTION_LAST,
+              "insert",
+              "append",
+              "delete",
+);
+
+
+int
+virNftablesApplyFirewallRule(virFirewall *firewall G_GNUC_UNUSED,
+                             virFirewallRule *rule,
+                             char **output)
+{
+    size_t count = virFirewallRuleGetArgCount(rule);
+    g_autoptr(virCommand) cmd = NULL;
+    g_autofree char *cmdStr = NULL;
+    g_autofree char *error = NULL;
+    size_t i;
+    int status;
+
+    if (count == 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("Can't apply empty firewall command"));
+        return -1;
+    }
+
+    cmd = virCommandNew(NFT);
+
+    for (i = 0; i < count; i++)
+        virCommandAddArg(cmd, virFirewallRuleGetArg(rule, i));
+
+    cmdStr = virCommandToString(cmd, false);
+    VIR_INFO("Applying rule '%s'", NULLSTR(cmdStr));
+
+    virCommandSetOutputBuffer(cmd, output);
+    virCommandSetErrorBuffer(cmd, &error);
+
+    if (virCommandRun(cmd, &status) < 0)
+        return -1;
+
+    if (status != 0) {
+        if (STREQ_NULLABLE(virFirewallRuleGetArg(rule, 0), "list")) {
+            /* nft returns error status when the target of a "list"
+             * command doesn't exist, but we always want to just have
+             * an empty result, so this is not actually an error.
+             */
+        } else if (virFirewallRuleGetIgnoreErrors(rule)) {
+            VIR_DEBUG("Ignoring error running command");
+        } else {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Failed to apply firewall command '%1$s': %2$s"),
+                           NULLSTR(cmdStr), NULLSTR(error));
+            VIR_FREE(*output);
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+
+typedef struct {
+    const char *parent;
+    const char *child;
+    const char *extraArgs;
+} virNftablesGlobalChain;
+
+typedef struct {
+    virFirewallLayer layer;
+    virNftablesGlobalChain *chains;
+    size_t nchains;
+    bool *changed;
+} virNftablesGlobalChainData;
+
+
+static int
+virNftablesPrivateChainCreate(virFirewall *fw,
+                              virFirewallLayer layer,
+                              const char *const *lines,
+                              void *opaque)
+{
+    virNftablesGlobalChainData *data = opaque;
+    g_autoptr(GHashTable) chains = virHashNew(NULL);
+    g_autoptr(GHashTable) links = virHashNew(NULL);
+    const char *const *line;
+    const char *chain = NULL;
+    size_t i;
+    bool tableMatch = false;
+    const char *layerStr = virNftablesLayerTypeToString(layer);
+    g_autofree char *tableStr = g_strdup_printf("table %s libvirt {",
+                                                virNftablesLayerTypeToString(layer));
+    line = lines;
+    while (line && *line) {
+        const char *pos = *line;
+
+        virSkipSpaces(&pos);
+        if (STREQ(pos, tableStr)) {
+            /* "table ip libvirt {" */
+
+            tableMatch = true;
+
+        } else if (STRPREFIX(pos, "chain ")) {
+            /* "chain LIBVIRT_OUT {" */
+
+            chain = pos + 6;
+            pos = strchr(chain, ' ');
+            if (pos) {
+                *(char *)pos = '\0';
+                if (virHashUpdateEntry(chains, chain, (void *)0x1) < 0)
+                    return -1;
+            }
+
+        } else if ((pos = strstr(pos, "jump "))) {
+            /* "counter packets 20189046 bytes 3473108889 jump LIBVIRT_OUT" */
+
+            pos += 5;
+            if (chain) {
+                if (virHashUpdateEntry(links, pos, (char *)chain) < 0)
+                    return -1;
+            }
+
+        }
+        line++;
+    }
+
+    if (!tableMatch) {
+        virFirewallAddRule(fw, layer, "add", "table",
+                           layerStr, VIR_NFTABLES_PRIVATE_TABLE, NULL);
+    }
+
+    for (i = 0; i < data->nchains; i++) {
+        if (!(tableMatch && virHashLookup(chains, data->chains[i].child))) {
+            virFirewallAddRule(fw, layer, "add", "chain",
+                               layerStr, VIR_NFTABLES_PRIVATE_TABLE,
+                               data->chains[i].child,
+                               data->chains[i].extraArgs, NULL);
+            *data->changed = true;
+        }
+
+        if (data->chains[i].parent) {
+            const char *from = virHashLookup(links, data->chains[i].child);
+
+            if (!from || STRNEQ(from, data->chains[i].parent)) {
+                virFirewallAddRule(fw, layer, "insert", "rule",
+                                   layerStr, VIR_NFTABLES_PRIVATE_TABLE,
+                                   data->chains[i].parent, "counter",
+                                   "jump", data->chains[i].child, NULL);
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+int
+virNftablesSetupPrivateChains(virFirewallLayer layer)
+{
+    bool changed = false;
+    virNftablesGlobalChain chains[] = {
+        /* chains for filter rules */
+        {NULL, "INPUT", "{ type filter hook input priority 0; policy accept; }"},
+        {NULL, "FORWARD", "{ type filter hook forward priority 0; policy accept; }"},
+        {NULL, "OUTPUT", "{ type filter hook output priority 0; policy accept; }"},
+        {"INPUT", VIR_NETFILTER_INPUT_CHAIN, NULL},
+        {"OUTPUT", VIR_NETFILTER_OUTPUT_CHAIN, NULL},
+        {"FORWARD", VIR_NETFILTER_FWD_OUT_CHAIN, NULL},
+        {"FORWARD", VIR_NETFILTER_FWD_IN_CHAIN, NULL},
+        {"FORWARD", VIR_NETFILTER_FWD_X_CHAIN, NULL},
+
+        /* chains for NAT rules */
+        {NULL, "POSTROUTING", "{ type nat hook postrouting priority 100; policy accept; }"},
+        {"POSTROUTING",  VIR_NETFILTER_NAT_POSTROUTE_CHAIN, NULL},
+    };
+    virNftablesGlobalChainData data =  { layer, chains, G_N_ELEMENTS(chains), &changed };
+
+    g_autoptr(virFirewall) fw = virFirewallNew(VIR_FIREWALL_BACKEND_NFTABLES);
+    const char *layerStr =  virNftablesLayerTypeToString(layer);
+
+    virFirewallStartTransaction(fw, 0);
+
+    /* the output of "nft list table ip[6] libvirt" will be parsed by
+     * the callback virNftablesPrivateChainCreate which will add any
+     * needed commands to add missing chains (or possibly even add the
+     * "ip[6] libvirt" table itself
+     */
+    virFirewallAddRuleFull(fw, layer, false,
+                           virNftablesPrivateChainCreate, &data,
+                           "list", "table",
+                           layerStr, VIR_NFTABLES_PRIVATE_TABLE, NULL);
+
+    if (virFirewallApply(fw) < 0)
+        return -1;
+
+    return changed ? 1 : 0;
+}
+
+
+void
+virNftablesInput(virFirewall *fw,
+                 virFirewallLayer layer,
+                 const char *iface,
+                 int port,
+                 virFirewallAction action,
+                 int tcp)
+{
+    g_autofree char *portstr = g_strdup_printf("%d", port);
+    const char *layerStr =  virNftablesLayerTypeToString(layer);
+
+    virFirewallAddRule(fw, layer,
+                       virNftablesActionTypeToString(action), "rule",
+                       layerStr, VIR_NFTABLES_PRIVATE_TABLE,
+                       VIR_NETFILTER_INPUT_CHAIN,
+                       "iifname", iface,
+                       tcp ? "tcp" : "udp",
+                       "dport", portstr,
+                       "counter", "accept",
+                       NULL);
+}
+
+void
+virNftablesOutput(virFirewall *fw,
+                  virFirewallLayer layer,
+                  const char *iface,
+                  int port,
+                  virFirewallAction action,
+                  int tcp)
+{
+    g_autofree char *portstr = g_strdup_printf("%d", port);
+    const char *layerStr = virNftablesLayerTypeToString(layer);
+
+    virFirewallAddRule(fw, layer,
+                       virNftablesActionTypeToString(action), "rule",
+                       layerStr, VIR_NFTABLES_PRIVATE_TABLE,
+                       VIR_NETFILTER_OUTPUT_CHAIN,
+                       "oifname", iface,
+                       tcp ? "tcp" : "udp",
+                       "dport", portstr,
+                       "counter", "accept",
+                       NULL);
+}
+
+
+/* Allow all traffic coming from the bridge, with a valid network address
+ * to proceed to WAN
+ */
+int
+virNftablesForwardAllowOut(virFirewall *fw,
+                           virSocketAddr *netaddr,
+                           unsigned int prefix,
+                           const char *iface,
+                           const char *physdev,
+                           virFirewallAction action)
+{
+    g_autofree char *networkstr = NULL;
+    virFirewallLayer layer = VIR_SOCKET_ADDR_FAMILY(netaddr) == AF_INET ?
+        VIR_FIREWALL_LAYER_IPV4 : VIR_FIREWALL_LAYER_IPV6;
+    const char *layerStr = virNftablesLayerTypeToString(layer);
+    virFirewallRule *rule;
+
+    if (!(networkstr = virSocketAddrFormatWithPrefix(netaddr, prefix, true)))
+        return -1;
+
+    rule = virFirewallAddRule(fw, layer,
+                              virNftablesActionTypeToString(action), "rule",
+                              layerStr, VIR_NFTABLES_PRIVATE_TABLE,
+                              VIR_NETFILTER_FWD_OUT_CHAIN,
+                              layerStr, "saddr", networkstr,
+                              "iifname", iface, NULL);
+
+    if (physdev && physdev[0])
+        virFirewallRuleAddArgList(fw, rule, "oifname", physdev, NULL);
+
+    virFirewallRuleAddArgList(fw, rule, "counter", "accept", NULL);
+
+    return 0;
+}
+
+
+/* Allow all traffic destined to the bridge, with a valid network address
+ * and associated with an existing connection
+ */
+int
+virNftablesForwardAllowRelatedIn(virFirewall *fw,
+                                 virSocketAddr *netaddr,
+                                 unsigned int prefix,
+                                 const char *iface,
+                                 const char *physdev,
+                                 virFirewallAction action)
+{
+    virFirewallLayer layer = VIR_SOCKET_ADDR_FAMILY(netaddr) == AF_INET ?
+        VIR_FIREWALL_LAYER_IPV4 : VIR_FIREWALL_LAYER_IPV6;
+    const char *layerStr =  virNftablesLayerTypeToString(layer);
+    g_autofree char *networkstr = NULL;
+    virFirewallRule *rule;
+
+    if (!(networkstr = virSocketAddrFormatWithPrefix(netaddr, prefix, true)))
+        return -1;
+
+    rule = virFirewallAddRule(fw, layer,
+                              virNftablesActionTypeToString(action), "rule",
+                              layerStr, VIR_NFTABLES_PRIVATE_TABLE,
+                              VIR_NETFILTER_FWD_IN_CHAIN, NULL);
+
+    if (physdev && physdev[0])
+        virFirewallRuleAddArgList(fw, rule, "iifname", physdev, NULL);
+
+    virFirewallRuleAddArgList(fw, rule, "oifname", iface,
+                              layerStr, "daddr", networkstr,
+                              "ct", "state", "related,established",
+                              "counter", "accept", NULL);
+    return 0;
+}
+
+
+/* Allow all traffic destined to the bridge, with a valid network address
+ */
+int
+virNftablesForwardAllowIn(virFirewall *fw,
+                          virSocketAddr *netaddr,
+                          unsigned int prefix,
+                          const char *iface,
+                          const char *physdev,
+                          virFirewallAction action)
+{
+    virFirewallLayer layer = VIR_SOCKET_ADDR_FAMILY(netaddr) == AF_INET ?
+        VIR_FIREWALL_LAYER_IPV4 : VIR_FIREWALL_LAYER_IPV6;
+    const char *layerStr =  virNftablesLayerTypeToString(layer);
+    g_autofree char *networkstr = NULL;
+    virFirewallRule *rule;
+
+    if (!(networkstr = virSocketAddrFormatWithPrefix(netaddr, prefix, true)))
+        return -1;
+
+    rule = virFirewallAddRule(fw, layer,
+                              virNftablesActionTypeToString(action), "rule",
+                              layerStr, VIR_NFTABLES_PRIVATE_TABLE,
+                              VIR_NETFILTER_FWD_IN_CHAIN,
+                              layerStr, "daddr", networkstr, NULL);
+
+    if (physdev && physdev[0])
+        virFirewallRuleAddArgList(fw, rule, "iifname", physdev, NULL);
+
+    virFirewallRuleAddArgList(fw, rule, "oifname", iface,
+                              "counter", "accept", NULL);
+    return 0;
+}
+
+
+void
+virNftablesForwardAllowCross(virFirewall *fw,
+                             virFirewallLayer layer,
+                             const char *iface,
+                             virFirewallAction action)
+{
+    const char *layerStr =  virNftablesLayerTypeToString(layer);
+
+    virFirewallAddRule(fw, layer,
+                       virNftablesActionTypeToString(action), "rule",
+                       layerStr, VIR_NFTABLES_PRIVATE_TABLE,
+                       VIR_NETFILTER_FWD_X_CHAIN,
+                       "iifname", iface,
+                       "oifname", iface,
+                       "counter", "accept",
+                       NULL);
+}
+
+
+void
+virNftablesForwardRejectOut(virFirewall *fw,
+                            virFirewallLayer layer,
+                            const char *iface,
+                            virFirewallAction action)
+{
+    const char *layerStr =  virNftablesLayerTypeToString(layer);
+
+    virFirewallAddRule(fw, layer,
+                       virNftablesActionTypeToString(action), "rule",
+                       layerStr, VIR_NFTABLES_PRIVATE_TABLE,
+                       VIR_NETFILTER_FWD_OUT_CHAIN,
+                       "iifname", iface,
+                       "counter", "reject",
+                       NULL);
+}
+
+
+void
+virNftablesForwardRejectIn(virFirewall *fw,
+                           virFirewallLayer layer,
+                           const char *iface,
+                           virFirewallAction action)
+{
+    const char *layerStr =  virNftablesLayerTypeToString(layer);
+
+    virFirewallAddRule(fw, layer,
+                       virNftablesActionTypeToString(action), "rule",
+                       layerStr, VIR_NFTABLES_PRIVATE_TABLE,
+                       VIR_NETFILTER_FWD_IN_CHAIN,
+                       "oifname", iface,
+                       "counter", "reject",
+                       NULL);
+}
+
+
+/* Masquerade all traffic coming from the network associated
+ * with the bridge
+ */
+int
+virNftablesForwardMasquerade(virFirewall *fw,
+                             virSocketAddr *netaddr,
+                             unsigned int prefix,
+                             const char *physdev,
+                             virSocketAddrRange *addr,
+                             virPortRange *port,
+                             const char *protocol,
+                             virFirewallAction action)
+{
+    g_autofree char *networkstr = NULL;
+    g_autofree char *addrStartStr = NULL;
+    g_autofree char *addrEndStr = NULL;
+    g_autofree char *portRangeStr = NULL;
+    g_autofree char *natRangeStr = NULL;
+    virFirewallRule *rule;
+    int af = VIR_SOCKET_ADDR_FAMILY(netaddr);
+    virFirewallLayer layer = af == AF_INET ?
+        VIR_FIREWALL_LAYER_IPV4 : VIR_FIREWALL_LAYER_IPV6;
+    const char *layerStr =  virNftablesLayerTypeToString(layer);
+
+    if (!(networkstr = virSocketAddrFormatWithPrefix(netaddr, prefix, true)))
+        return -1;
+
+    if (VIR_SOCKET_ADDR_IS_FAMILY(&addr->start, af)) {
+        if (!(addrStartStr = virSocketAddrFormat(&addr->start)))
+            return -1;
+        if (VIR_SOCKET_ADDR_IS_FAMILY(&addr->end, af)) {
+            if (!(addrEndStr = virSocketAddrFormat(&addr->end)))
+                return -1;
+        }
+    }
+
+    rule = virFirewallAddRule(fw, layer,
+                              virNftablesActionTypeToString(action), "rule",
+                              layerStr, VIR_NFTABLES_PRIVATE_TABLE,
+                              VIR_NETFILTER_NAT_POSTROUTE_CHAIN, NULL);
+
+    if (protocol && protocol[0]) {
+        virFirewallRuleAddArgList(fw, rule,
+                                  layerStr, "protocol", protocol, NULL);
+    }
+
+    virFirewallRuleAddArgList(fw, rule,
+                              layerStr, "saddr", networkstr,
+                              layerStr, "daddr", "!=", networkstr, NULL);
+
+    if (physdev && physdev[0])
+        virFirewallRuleAddArgList(fw, rule, "oifname", physdev, NULL);
+
+    if (protocol && protocol[0]) {
+        if (port->start == 0 && port->end == 0) {
+            port->start = 1024;
+            port->end = 65535;
+        }
+
+        if (port->start < port->end && port->end < 65536) {
+            portRangeStr = g_strdup_printf(":%u-%u", port->start, port->end);
+        } else {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("Invalid port range '%1$u-%2$u'."),
+                           port->start, port->end);
+            return -1;
+        }
+    }
+
+    /* Use snat if public address is specified */
+    if (addrStartStr && addrStartStr[0]) {
+        if (addrEndStr && addrEndStr[0]) {
+            natRangeStr = g_strdup_printf("%s-%s%s", addrStartStr, addrEndStr,
+                                          portRangeStr ? portRangeStr : "");
+        } else {
+            natRangeStr = g_strdup_printf("%s%s", addrStartStr,
+                                          portRangeStr ? portRangeStr : "");
+        }
+
+        virFirewallRuleAddArgList(fw, rule, "counter", "snat", "to", natRangeStr, NULL);
+    } else {
+        virFirewallRuleAddArgList(fw, rule, "counter", "masquerade", NULL);
+
+        if (portRangeStr && portRangeStr[0])
+            virFirewallRuleAddArgList(fw, rule, "to", portRangeStr, NULL);
+    }
+
+    return 0;
+}
+
+
+/* Don't masquerade traffic coming from the network associated with the bridge
+ * if said traffic targets @destaddr.
+ */
+int
+virNftablesForwardDontMasquerade(virFirewall *fw,
+                                 virSocketAddr *netaddr,
+                                 unsigned int prefix,
+                                 const char *physdev,
+                                 const char *destaddr,
+                                 virFirewallAction action)
+{
+    g_autofree char *networkstr = NULL;
+    virFirewallLayer layer = VIR_SOCKET_ADDR_FAMILY(netaddr) == AF_INET ?
+        VIR_FIREWALL_LAYER_IPV4 : VIR_FIREWALL_LAYER_IPV6;
+    const char *layerStr =  virNftablesLayerTypeToString(layer);
+    virFirewallRule *rule;
+
+    if (!(networkstr = virSocketAddrFormatWithPrefix(netaddr, prefix, true)))
+        return -1;
+
+    rule = virFirewallAddRule(fw, layer,
+                              virNftablesActionTypeToString(action), "rule",
+                              layerStr, VIR_NFTABLES_PRIVATE_TABLE,
+                              VIR_NETFILTER_NAT_POSTROUTE_CHAIN, NULL);
+
+    if (physdev && physdev[0])
+        virFirewallRuleAddArgList(fw, rule, "oifname", physdev, NULL);
+
+    virFirewallRuleAddArgList(fw, rule,
+                              layerStr, "saddr", networkstr,
+                              layerStr, "daddr", destaddr,
+                              "counter", "return", NULL);
+    return 0;
+}
diff --git a/src/util/virnftables.h b/src/util/virnftables.h
new file mode 100644
index 0000000000..5ea0f2452f
--- /dev/null
+++ b/src/util/virnftables.h
@@ -0,0 +1,118 @@
+/*
+ * virnftables.h: helper APIs for managing nftables packet filters
+ *
+ * Copyright (C) 2023 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "virsocketaddr.h"
+#include "virfirewall.h"
+#include "virnetfilter.h"
+
+/*  virNftablesApplyFirewallRule should be called only from virnetfilter.c */
+
+int
+virNftablesApplyFirewallRule(virFirewall *firewall,
+                             virFirewallRule *rule,
+                             char **output);
+
+
+/* All the following functions can either insert or delete the given
+ * type of filter rule, depending on whether action is
+ * VIR_NETFILTER_INSERT or VIR_NETFILTER_DELETE.
+ */
+
+int
+virNftablesSetupPrivateChains(virFirewallLayer layer);
+
+void
+virNftablesInput(virFirewall *fw,
+                 virFirewallLayer layer,
+                 const char *iface,
+                 int port,
+                 virFirewallAction action,
+                 int tcp);
+
+void
+virNftablesOutput(virFirewall *fw,
+                  virFirewallLayer layer,
+                  const char *iface,
+                  int port,
+                  virFirewallAction action,
+                  int tcp);
+
+int
+virNftablesForwardAllowOut(virFirewall *fw,
+                           virSocketAddr *netaddr,
+                           unsigned int prefix,
+                           const char *iface,
+                           const char *physdev,
+                           virFirewallAction action);
+
+int
+virNftablesForwardAllowRelatedIn(virFirewall *fw,
+                                 virSocketAddr *netaddr,
+                                 unsigned int prefix,
+                                 const char *iface,
+                                 const char *physdev,
+                                 virFirewallAction action);
+
+int
+virNftablesForwardAllowIn(virFirewall *fw,
+                          virSocketAddr *netaddr,
+                          unsigned int prefix,
+                          const char *iface,
+                          const char *physdev,
+                          virFirewallAction action);
+
+
+void
+virNftablesForwardAllowCross(virFirewall *fw,
+                             virFirewallLayer layer,
+                             const char *iface,
+                             virFirewallAction action);
+
+void
+virNftablesForwardRejectOut(virFirewall *fw,
+                            virFirewallLayer layer,
+                            const char *iface,
+                            virFirewallAction action);
+
+void
+virNftablesForwardRejectIn(virFirewall *fw,
+                           virFirewallLayer layer,
+                           const char *iface,
+                           virFirewallAction action);
+
+int
+virNftablesForwardMasquerade(virFirewall *fw,
+                             virSocketAddr *netaddr,
+                             unsigned int prefix,
+                             const char *physdev,
+                             virSocketAddrRange *addr,
+                             virPortRange *port,
+                             const char *protocol,
+                             virFirewallAction action);
+
+int
+virNftablesForwardDontMasquerade(virFirewall *fw,
+                                 virSocketAddr *netaddr,
+                                 unsigned int prefix,
+                                 const char *physdev,
+                                 const char *destaddr,
+                                 virFirewallAction action);
-- 
2.39.2