Signed-off-by: Laine Stump <laine(a)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