Signed-off-by: Laine Stump <laine@xxxxxxxxxx> --- 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