2013-03-04 10:30:40 -06:00
|
|
|
/*
|
|
|
|
* virfirewall.c: integration with firewalls
|
|
|
|
*
|
2015-05-21 12:36:18 -05:00
|
|
|
* Copyright (C) 2013-2015 Red Hat, Inc.
|
2013-03-04 10:30:40 -06:00
|
|
|
*
|
|
|
|
* 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>
|
|
|
|
|
2021-11-16 13:27:00 -06:00
|
|
|
#include "virfirewall.h"
|
2019-01-09 13:11:32 -06:00
|
|
|
#include "virfirewalld.h"
|
2019-04-01 09:28:05 -05:00
|
|
|
#include "viralloc.h"
|
2013-03-04 10:30:40 -06:00
|
|
|
#include "virerror.h"
|
|
|
|
#include "vircommand.h"
|
|
|
|
#include "virlog.h"
|
|
|
|
#include "virfile.h"
|
|
|
|
#include "virthread.h"
|
|
|
|
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_FIREWALL
|
|
|
|
|
|
|
|
VIR_LOG_INIT("util.firewall");
|
|
|
|
|
util/network: new virFirewallBackend enum
(This paragraph is for historical reference only, described only to
avoid confusion of past use of the name with its new use) In a past
life, virFirewallBackend had been a private static in virfirewall.c
that was set at daemon init time, and used to globally (i.e. for all
drivers in the daemon) determine whether to directly execute iptables
commands, or to run them indirectly via the firewalld passthrough
API. This was removed in commit d566cc55, since we decided that using
the firewalld passthrough API is never appropriate.
Now the same enum, virFirewallBackend, is being reintroduced, with a
different meaning and usage pattern. It will be used to pick between
using nftables commands or iptables commands (in either case directly
handled by libvirt, *not* via firewalld). Additionally, rather than
being a static known only within virfirewall.c and applying to all
firewall commands for all drivers, each virFirewall object will have
its own backend setting, which will be set during virFirewallNew() by
the driver who wants to add a firewall rule.
This will allow the nwfilter and network drivers to each have their
own backend setting, even when they coexist in a single unified
daemon. At least as important as that, it will also allow an instance
of the network driver to remove iptables rules that had been added by
a previous instance, and then add nftables rules for the new instance
(in the case that an admin, or possibly an update, switches the driver
backend from iptables to nftable)
Initially, the enum will only have one usable value -
VIR_FIREWALL_BACKEND_IPTABLES, and that will be hardcoded into all
calls to virFirewallNew(). The other enum value (along with a method
of setting it for each driver) will be added later, when it can be
used (when the nftables backend is in the code).
Signed-off-by: Laine Stump <laine@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 21:19:42 -05:00
|
|
|
VIR_ENUM_IMPL(virFirewallBackend,
|
|
|
|
VIR_FIREWALL_BACKEND_LAST,
|
network: add an nftables backend for network driver's firewall construction
Support using nftables to setup the firewall for each virtual network,
rather than iptables. The initial implementation of the nftables
backend creates (almost) exactly the same ruleset as the iptables
backend, determined by running the following commands on a host that
has an active virtual network:
iptables-save >iptables.txt
iptables-restore-translate -f iptables.txt
(and the similar ip6tables-save/ip6tables-restore-translate for an
IPv6 network). Correctness of the new backend was checked by comparing
the output of:
nft list ruleset
when the backend is set to iptables and when it is set to nftables.
This page was used as a guide:
https://wiki.nftables.org/wiki-nftables/index.php/Moving_from_iptables_to_nftables
The only differences between the rules created by the nftables backed
vs. the iptables backend (aside from a few inconsequential changes in
display order of some chains/options) are:
1) When we add nftables rules, rather than adding them in the
system-created "filter" and "nat" tables, we add them in a private
table (ie only we should be using it) created by us called "libvirt"
(the system-created "filter" and "nat" tables can't be used because
adding any rules to those tables directly with nft will cause failure
of any legacy application attempting to use iptables when it tries to
list the iptables rules (e.g. "iptables -S").
(NB: in nftables only a single table is required for both nat and
filter rules - the chains for each are differentiated by specifying
different "hook" locations for the toplevel chain of each)
2) Since the rules that were added to allow tftp/dns/dhcp traffic from
the guests to the host are unnecessary in the context of nftables,
those rules aren't added.
(Longer explanation: In the case of iptables, all rules were in a
single table, and it was always assumed that there would be some
"catch-all" REJECT rule added by "someone else" in the case that a
packet didn't match any specific rules, so libvirt added these
specific rules to ensure that, no matter what other rules were added
by any other subsystem, the guests would still have functional
tftp/dns/dhcp. For nftables though, the rules added by each subsystem
are in a separate table, and in order for traffic to be accepted, it
must be accepted by *all* tables, so just adding the specific rules to
libvirt's table doesn't help anything (as the default for the libvirt
table is ACCEPT anyway) and it just isn't practical/possible for
libvirt to find *all* other tables and add rules in all of them to
make sure the traffic is accepted. libvirt does this for firewalld (it
creates a "libvirt" zone that allows tftp/dns/dhcp, and adds all
virtual network bridges to that zone), however, so in that case no
extra work is required of the sysadmin.)
3) nftables doesn't support the "checksum mangle" rule (or any
equivalent functionality) that we have historically added to our
iptables rules, so the nftables rules we add have nothing related to
checksum mangling.
(NB: The result of (3) is that if you a) have a very old guest (RHEL5
era or earlier) and b) that guest is using a virtio-net network
device, and c) the virtio-net device is using vhost packet processing
(the default) then DHCP on the guest will fail. You can work around
this by adding <driver name='qemu'/> to the <interface> XML for the
guest).
There are certainly much better nftables rulesets that could be used
instead of those implemented here, and everything is in place to make
future changes to the rules that are used simple and free of surprises
(e.g. the rules that are added have coresponding "removal" commands
added to the network status so that we will always remove exactly the
rules that were previously added rather than trying to remove the
rules that "the current build of libvirt would have added" (which will
be incorrect the first time we run a libvirt with a newly modified
ruleset). For this initial implementation though, I wanted the
nftables rules to be as identical to the iptables rules as possible,
just to make it easier to verify that everything is working.
The backend can be manually chosen using the firewall_backend setting
in /etc/libvirt/network.conf. libvirtd/virtnetworkd will read this
setting when it starts; if there is no explicit setting, it will check
for availability of FIREWALL_BACKEND_DEFAULT_1 and then
FIREWALL_BACKEND_DEFAULT_2 (which are set at build time in
meson_options.txt or by adding -Dfirewall_backend_default_n=blah to
the meson commandline), and use the first backend that is available
(ie, that has the necessary programs installed). The standard
meson_options.txt is set to check for nftables first, and then
iptables.
Although it should be very safe to change the default backend from
iptables to nftables, that change is left for a later patch, to show
how the change in default can be undone if someone really needs to do
that.
Signed-off-by: Laine Stump <laine@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Tested-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 21:19:43 -05:00
|
|
|
"iptables",
|
|
|
|
"nftables");
|
util/network: new virFirewallBackend enum
(This paragraph is for historical reference only, described only to
avoid confusion of past use of the name with its new use) In a past
life, virFirewallBackend had been a private static in virfirewall.c
that was set at daemon init time, and used to globally (i.e. for all
drivers in the daemon) determine whether to directly execute iptables
commands, or to run them indirectly via the firewalld passthrough
API. This was removed in commit d566cc55, since we decided that using
the firewalld passthrough API is never appropriate.
Now the same enum, virFirewallBackend, is being reintroduced, with a
different meaning and usage pattern. It will be used to pick between
using nftables commands or iptables commands (in either case directly
handled by libvirt, *not* via firewalld). Additionally, rather than
being a static known only within virfirewall.c and applying to all
firewall commands for all drivers, each virFirewall object will have
its own backend setting, which will be set during virFirewallNew() by
the driver who wants to add a firewall rule.
This will allow the nwfilter and network drivers to each have their
own backend setting, even when they coexist in a single unified
daemon. At least as important as that, it will also allow an instance
of the network driver to remove iptables rules that had been added by
a previous instance, and then add nftables rules for the new instance
(in the case that an admin, or possibly an update, switches the driver
backend from iptables to nftable)
Initially, the enum will only have one usable value -
VIR_FIREWALL_BACKEND_IPTABLES, and that will be hardcoded into all
calls to virFirewallNew(). The other enum value (along with a method
of setting it for each driver) will be added later, when it can be
used (when the nftables backend is in the code).
Signed-off-by: Laine Stump <laine@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 21:19:42 -05:00
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
VIR_ENUM_DECL(virFirewallLayer);
|
|
|
|
VIR_ENUM_IMPL(virFirewallLayer,
|
|
|
|
VIR_FIREWALL_LAYER_LAST,
|
|
|
|
"ethernet",
|
|
|
|
"ipv4",
|
|
|
|
"ipv6",
|
|
|
|
);
|
|
|
|
|
2013-03-04 10:30:40 -06:00
|
|
|
typedef struct _virFirewallGroup virFirewallGroup;
|
|
|
|
|
2019-01-20 10:04:56 -06:00
|
|
|
VIR_ENUM_DECL(virFirewallLayerCommand);
|
2019-03-16 13:20:32 -05:00
|
|
|
VIR_ENUM_IMPL(virFirewallLayerCommand,
|
|
|
|
VIR_FIREWALL_LAYER_LAST,
|
2021-09-14 03:40:42 -05:00
|
|
|
EBTABLES,
|
|
|
|
IPTABLES,
|
|
|
|
IP6TABLES,
|
2019-01-20 10:30:15 -06:00
|
|
|
);
|
2013-03-04 10:30:40 -06:00
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
struct _virFirewallCmd {
|
2013-03-04 10:30:40 -06:00
|
|
|
virFirewallLayer layer;
|
|
|
|
|
|
|
|
virFirewallQueryCallback queryCB;
|
|
|
|
void *queryOpaque;
|
|
|
|
bool ignoreErrors;
|
|
|
|
|
|
|
|
size_t argsAlloc;
|
|
|
|
size_t argsLen;
|
|
|
|
char **args;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct _virFirewallGroup {
|
|
|
|
unsigned int actionFlags;
|
|
|
|
unsigned int rollbackFlags;
|
|
|
|
|
|
|
|
size_t naction;
|
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallCmd **action;
|
2013-03-04 10:30:40 -06:00
|
|
|
|
|
|
|
size_t nrollback;
|
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallCmd **rollback;
|
2013-03-04 10:30:40 -06:00
|
|
|
|
|
|
|
bool addingRollback;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct _virFirewall {
|
|
|
|
int err;
|
|
|
|
|
2024-04-24 21:11:02 -05:00
|
|
|
char *name;
|
2013-03-04 10:30:40 -06:00
|
|
|
size_t ngroups;
|
2021-03-11 01:16:13 -06:00
|
|
|
virFirewallGroup **groups;
|
2013-03-04 10:30:40 -06:00
|
|
|
size_t currentGroup;
|
util/network: new virFirewallBackend enum
(This paragraph is for historical reference only, described only to
avoid confusion of past use of the name with its new use) In a past
life, virFirewallBackend had been a private static in virfirewall.c
that was set at daemon init time, and used to globally (i.e. for all
drivers in the daemon) determine whether to directly execute iptables
commands, or to run them indirectly via the firewalld passthrough
API. This was removed in commit d566cc55, since we decided that using
the firewalld passthrough API is never appropriate.
Now the same enum, virFirewallBackend, is being reintroduced, with a
different meaning and usage pattern. It will be used to pick between
using nftables commands or iptables commands (in either case directly
handled by libvirt, *not* via firewalld). Additionally, rather than
being a static known only within virfirewall.c and applying to all
firewall commands for all drivers, each virFirewall object will have
its own backend setting, which will be set during virFirewallNew() by
the driver who wants to add a firewall rule.
This will allow the nwfilter and network drivers to each have their
own backend setting, even when they coexist in a single unified
daemon. At least as important as that, it will also allow an instance
of the network driver to remove iptables rules that had been added by
a previous instance, and then add nftables rules for the new instance
(in the case that an admin, or possibly an update, switches the driver
backend from iptables to nftable)
Initially, the enum will only have one usable value -
VIR_FIREWALL_BACKEND_IPTABLES, and that will be hardcoded into all
calls to virFirewallNew(). The other enum value (along with a method
of setting it for each driver) will be added later, when it can be
used (when the nftables backend is in the code).
Signed-off-by: Laine Stump <laine@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallBackend backend;
|
2013-03-04 10:30:40 -06:00
|
|
|
};
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
static virMutex fwCmdLock = VIR_MUTEX_INITIALIZER;
|
2013-03-04 10:30:40 -06:00
|
|
|
|
2021-03-11 01:16:13 -06:00
|
|
|
static virFirewallGroup *
|
2013-03-04 10:30:40 -06:00
|
|
|
virFirewallGroupNew(void)
|
|
|
|
{
|
2021-10-22 03:56:01 -05:00
|
|
|
return g_new0(virFirewallGroup, 1);
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* virFirewallNew:
|
|
|
|
*
|
|
|
|
* Creates a new firewall ruleset for changing rules
|
|
|
|
* of @layer. This should be followed by a call to
|
|
|
|
* virFirewallStartTransaction before adding
|
|
|
|
* any rules
|
|
|
|
*
|
|
|
|
* Returns the new firewall ruleset
|
|
|
|
*/
|
util/network: new virFirewallBackend enum
(This paragraph is for historical reference only, described only to
avoid confusion of past use of the name with its new use) In a past
life, virFirewallBackend had been a private static in virfirewall.c
that was set at daemon init time, and used to globally (i.e. for all
drivers in the daemon) determine whether to directly execute iptables
commands, or to run them indirectly via the firewalld passthrough
API. This was removed in commit d566cc55, since we decided that using
the firewalld passthrough API is never appropriate.
Now the same enum, virFirewallBackend, is being reintroduced, with a
different meaning and usage pattern. It will be used to pick between
using nftables commands or iptables commands (in either case directly
handled by libvirt, *not* via firewalld). Additionally, rather than
being a static known only within virfirewall.c and applying to all
firewall commands for all drivers, each virFirewall object will have
its own backend setting, which will be set during virFirewallNew() by
the driver who wants to add a firewall rule.
This will allow the nwfilter and network drivers to each have their
own backend setting, even when they coexist in a single unified
daemon. At least as important as that, it will also allow an instance
of the network driver to remove iptables rules that had been added by
a previous instance, and then add nftables rules for the new instance
(in the case that an admin, or possibly an update, switches the driver
backend from iptables to nftable)
Initially, the enum will only have one usable value -
VIR_FIREWALL_BACKEND_IPTABLES, and that will be hardcoded into all
calls to virFirewallNew(). The other enum value (along with a method
of setting it for each driver) will be added later, when it can be
used (when the nftables backend is in the code).
Signed-off-by: Laine Stump <laine@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 21:19:42 -05:00
|
|
|
virFirewall *virFirewallNew(virFirewallBackend backend)
|
2013-03-04 10:30:40 -06:00
|
|
|
{
|
2021-11-17 12:20:53 -06:00
|
|
|
virFirewall *firewall = g_new0(virFirewall, 1);
|
2013-03-04 10:30:40 -06:00
|
|
|
|
util/network: new virFirewallBackend enum
(This paragraph is for historical reference only, described only to
avoid confusion of past use of the name with its new use) In a past
life, virFirewallBackend had been a private static in virfirewall.c
that was set at daemon init time, and used to globally (i.e. for all
drivers in the daemon) determine whether to directly execute iptables
commands, or to run them indirectly via the firewalld passthrough
API. This was removed in commit d566cc55, since we decided that using
the firewalld passthrough API is never appropriate.
Now the same enum, virFirewallBackend, is being reintroduced, with a
different meaning and usage pattern. It will be used to pick between
using nftables commands or iptables commands (in either case directly
handled by libvirt, *not* via firewalld). Additionally, rather than
being a static known only within virfirewall.c and applying to all
firewall commands for all drivers, each virFirewall object will have
its own backend setting, which will be set during virFirewallNew() by
the driver who wants to add a firewall rule.
This will allow the nwfilter and network drivers to each have their
own backend setting, even when they coexist in a single unified
daemon. At least as important as that, it will also allow an instance
of the network driver to remove iptables rules that had been added by
a previous instance, and then add nftables rules for the new instance
(in the case that an admin, or possibly an update, switches the driver
backend from iptables to nftable)
Initially, the enum will only have one usable value -
VIR_FIREWALL_BACKEND_IPTABLES, and that will be hardcoded into all
calls to virFirewallNew(). The other enum value (along with a method
of setting it for each driver) will be added later, when it can be
used (when the nftables backend is in the code).
Signed-off-by: Laine Stump <laine@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 21:19:42 -05:00
|
|
|
firewall->backend = backend;
|
2013-03-04 10:30:40 -06:00
|
|
|
return firewall;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
util/network: new virFirewallBackend enum
(This paragraph is for historical reference only, described only to
avoid confusion of past use of the name with its new use) In a past
life, virFirewallBackend had been a private static in virfirewall.c
that was set at daemon init time, and used to globally (i.e. for all
drivers in the daemon) determine whether to directly execute iptables
commands, or to run them indirectly via the firewalld passthrough
API. This was removed in commit d566cc55, since we decided that using
the firewalld passthrough API is never appropriate.
Now the same enum, virFirewallBackend, is being reintroduced, with a
different meaning and usage pattern. It will be used to pick between
using nftables commands or iptables commands (in either case directly
handled by libvirt, *not* via firewalld). Additionally, rather than
being a static known only within virfirewall.c and applying to all
firewall commands for all drivers, each virFirewall object will have
its own backend setting, which will be set during virFirewallNew() by
the driver who wants to add a firewall rule.
This will allow the nwfilter and network drivers to each have their
own backend setting, even when they coexist in a single unified
daemon. At least as important as that, it will also allow an instance
of the network driver to remove iptables rules that had been added by
a previous instance, and then add nftables rules for the new instance
(in the case that an admin, or possibly an update, switches the driver
backend from iptables to nftable)
Initially, the enum will only have one usable value -
VIR_FIREWALL_BACKEND_IPTABLES, and that will be hardcoded into all
calls to virFirewallNew(). The other enum value (along with a method
of setting it for each driver) will be added later, when it can be
used (when the nftables backend is in the code).
Signed-off-by: Laine Stump <laine@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallBackend
|
|
|
|
virFirewallGetBackend(virFirewall *firewall)
|
|
|
|
{
|
|
|
|
return firewall->backend;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-04-24 21:11:02 -05:00
|
|
|
const char *
|
|
|
|
virFirewallGetName(virFirewall *firewall)
|
|
|
|
{
|
|
|
|
return firewall->name;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
virFirewallSetName(virFirewall *firewall,
|
|
|
|
const char *name)
|
|
|
|
{
|
|
|
|
g_free(firewall->name);
|
|
|
|
firewall->name = g_strdup(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-03-04 10:30:40 -06:00
|
|
|
static void
|
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallCmdFree(virFirewallCmd *fwCmd)
|
2013-03-04 10:30:40 -06:00
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
if (!fwCmd)
|
2013-03-04 10:30:40 -06:00
|
|
|
return;
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
for (i = 0; i < fwCmd->argsLen; i++)
|
|
|
|
g_free(fwCmd->args[i]);
|
|
|
|
g_free(fwCmd->args);
|
|
|
|
g_free(fwCmd);
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2021-03-11 01:16:13 -06:00
|
|
|
virFirewallGroupFree(virFirewallGroup *group)
|
2013-03-04 10:30:40 -06:00
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
if (!group)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (i = 0; i < group->naction; i++)
|
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallCmdFree(group->action[i]);
|
2021-02-03 13:32:34 -06:00
|
|
|
g_free(group->action);
|
2013-03-04 10:30:40 -06:00
|
|
|
|
|
|
|
for (i = 0; i < group->nrollback; i++)
|
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallCmdFree(group->rollback[i]);
|
2021-02-03 13:32:34 -06:00
|
|
|
g_free(group->rollback);
|
2013-03-04 10:30:40 -06:00
|
|
|
|
2021-02-03 13:32:34 -06:00
|
|
|
g_free(group);
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* virFirewallFree:
|
|
|
|
*
|
|
|
|
* Release all memory associated with the firewall
|
|
|
|
* ruleset
|
|
|
|
*/
|
2021-03-11 01:16:13 -06:00
|
|
|
void virFirewallFree(virFirewall *firewall)
|
2013-03-04 10:30:40 -06:00
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
if (!firewall)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (i = 0; i < firewall->ngroups; i++)
|
|
|
|
virFirewallGroupFree(firewall->groups[i]);
|
|
|
|
|
2024-04-24 21:11:02 -05:00
|
|
|
g_free(firewall->groups);
|
|
|
|
g_free(firewall->name);
|
2021-02-03 13:32:34 -06:00
|
|
|
g_free(firewall);
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
|
|
|
|
2017-11-03 07:09:47 -05:00
|
|
|
#define VIR_FIREWALL_RETURN_IF_ERROR(firewall) \
|
|
|
|
do { \
|
|
|
|
if (!firewall || firewall->err) \
|
|
|
|
return; \
|
2013-03-04 10:30:40 -06:00
|
|
|
} while (0)
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
#define VIR_FIREWALL_CMD_RETURN_IF_ERROR(firewall, fwCmd)\
|
2017-11-03 07:09:47 -05:00
|
|
|
do { \
|
2024-04-19 21:19:42 -05:00
|
|
|
if (!firewall || firewall->err || !fwCmd) \
|
2017-11-03 07:09:47 -05:00
|
|
|
return; \
|
2013-03-04 10:30:40 -06:00
|
|
|
} while (0)
|
|
|
|
|
2017-11-03 07:09:47 -05:00
|
|
|
#define VIR_FIREWALL_RETURN_NULL_IF_ERROR(firewall) \
|
|
|
|
do { \
|
|
|
|
if (!firewall || firewall->err) \
|
|
|
|
return NULL; \
|
2013-03-04 10:30:40 -06:00
|
|
|
} while (0)
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
#define ADD_ARG(fwCmd, str) \
|
2017-11-03 07:09:47 -05:00
|
|
|
do { \
|
2024-04-19 21:19:42 -05:00
|
|
|
VIR_RESIZE_N(fwCmd->args, fwCmd->argsAlloc, fwCmd->argsLen, 1); \
|
|
|
|
fwCmd->args[fwCmd->argsLen++] = g_strdup(str); \
|
2013-03-04 10:30:40 -06:00
|
|
|
} while (0)
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
static virFirewallCmd *
|
|
|
|
virFirewallAddCmdFullV(virFirewall *firewall,
|
|
|
|
virFirewallLayer layer,
|
|
|
|
bool ignoreErrors,
|
2024-04-19 21:19:42 -05:00
|
|
|
bool isRollback,
|
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallQueryCallback cb,
|
|
|
|
void *opaque,
|
|
|
|
va_list args)
|
2013-03-04 10:30:40 -06:00
|
|
|
{
|
2021-03-11 01:16:13 -06:00
|
|
|
virFirewallGroup *group;
|
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallCmd *fwCmd;
|
2013-03-04 10:30:40 -06:00
|
|
|
char *str;
|
|
|
|
|
|
|
|
VIR_FIREWALL_RETURN_NULL_IF_ERROR(firewall);
|
|
|
|
|
|
|
|
if (firewall->ngroups == 0) {
|
2014-04-30 01:51:29 -05:00
|
|
|
firewall->err = EINVAL;
|
2013-03-04 10:30:40 -06:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
group = firewall->groups[firewall->currentGroup];
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
fwCmd = g_new0(virFirewallCmd, 1);
|
|
|
|
fwCmd->layer = layer;
|
2013-03-04 10:30:40 -06:00
|
|
|
|
2014-11-13 08:28:18 -06:00
|
|
|
while ((str = va_arg(args, char *)) != NULL)
|
2024-04-19 21:19:42 -05:00
|
|
|
ADD_ARG(fwCmd, str);
|
2013-03-04 10:30:40 -06:00
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
if (isRollback || group->addingRollback) {
|
2024-04-19 21:19:42 -05:00
|
|
|
fwCmd->ignoreErrors = true; /* always ignore errors when rolling back */
|
2024-04-19 21:19:42 -05:00
|
|
|
fwCmd->queryCB = NULL; /* rollback commands can't have a callback */
|
|
|
|
fwCmd->queryOpaque = NULL;
|
2024-04-19 21:19:42 -05:00
|
|
|
VIR_APPEND_ELEMENT_COPY(group->rollback, group->nrollback, fwCmd);
|
2013-03-04 10:30:40 -06:00
|
|
|
} else {
|
2024-04-19 21:19:42 -05:00
|
|
|
/* when not rolling back, ignore errors if this group (transaction)
|
|
|
|
* was started with VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS *or*
|
|
|
|
* if this specific rule was created with ignoreErrors == true
|
|
|
|
*/
|
|
|
|
fwCmd->ignoreErrors = ignoreErrors || (group->actionFlags & VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS);
|
2024-04-19 21:19:42 -05:00
|
|
|
fwCmd->queryCB = cb;
|
|
|
|
fwCmd->queryOpaque = opaque;
|
2024-04-19 21:19:42 -05:00
|
|
|
VIR_APPEND_ELEMENT_COPY(group->action, group->naction, fwCmd);
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
return fwCmd;
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2024-04-19 21:19:42 -05:00
|
|
|
* virFirewallAddCmdFull:
|
2013-03-04 10:30:40 -06:00
|
|
|
* @firewall: firewall ruleset to add to
|
|
|
|
* @layer: the firewall layer to change
|
|
|
|
* @ignoreErrors: true to ignore failure of the command
|
|
|
|
* @cb: callback to invoke with result of query
|
|
|
|
* @opaque: data passed into @cb
|
|
|
|
* @...: NULL terminated list of strings for the rule
|
|
|
|
*
|
|
|
|
* Add any type of rule to the firewall ruleset. Any output
|
|
|
|
* generated by the addition will be fed into the query
|
|
|
|
* callback @cb. This callback is permitted to create new
|
2024-04-19 21:19:42 -05:00
|
|
|
* rules by invoking the virFirewallAddCmd method, but
|
2013-03-04 10:30:40 -06:00
|
|
|
* is not permitted to start new transactions.
|
|
|
|
*
|
|
|
|
* If @ignoreErrors is set to TRUE, then any failure of
|
|
|
|
* the command is ignored. If it is set to FALSE, then
|
|
|
|
* the behaviour upon failure is determined by the flags
|
|
|
|
* set when the transaction was started.
|
|
|
|
*
|
|
|
|
* Returns the new rule
|
|
|
|
*/
|
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallCmd *virFirewallAddCmdFull(virFirewall *firewall,
|
|
|
|
virFirewallLayer layer,
|
|
|
|
bool ignoreErrors,
|
|
|
|
virFirewallQueryCallback cb,
|
|
|
|
void *opaque,
|
|
|
|
...)
|
2013-03-04 10:30:40 -06:00
|
|
|
{
|
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallCmd *fwCmd;
|
2013-03-04 10:30:40 -06:00
|
|
|
va_list args;
|
|
|
|
va_start(args, opaque);
|
2024-04-19 21:19:42 -05:00
|
|
|
fwCmd = virFirewallAddCmdFullV(firewall, layer, ignoreErrors, false, cb, opaque, args);
|
|
|
|
va_end(args);
|
|
|
|
return fwCmd;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* virFirewallAddRollbackCmd:
|
|
|
|
* @firewall: firewall commands to add to
|
|
|
|
* @layer: the firewall layer to change
|
|
|
|
* @...: NULL terminated list of strings for the command
|
|
|
|
*
|
|
|
|
* Add a command to the current firewall command group "rollback".
|
|
|
|
* Rollback commands always ignore errors and don't support any
|
|
|
|
* callbacks.
|
|
|
|
*
|
|
|
|
* Returns the new Command
|
|
|
|
*/
|
|
|
|
virFirewallCmd *
|
|
|
|
virFirewallAddRollbackCmd(virFirewall *firewall,
|
|
|
|
virFirewallLayer layer,
|
|
|
|
...)
|
|
|
|
{
|
|
|
|
virFirewallCmd *fwCmd;
|
|
|
|
va_list args;
|
|
|
|
va_start(args, layer);
|
|
|
|
fwCmd = virFirewallAddCmdFullV(firewall, layer, true, true, NULL, NULL, args);
|
2013-03-04 10:30:40 -06:00
|
|
|
va_end(args);
|
2024-04-19 21:19:42 -05:00
|
|
|
return fwCmd;
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2024-04-19 21:19:42 -05:00
|
|
|
* virFirewallRemoveCmd:
|
2013-03-04 10:30:40 -06:00
|
|
|
* @firewall: firewall ruleset to remove from
|
|
|
|
* @rule: the rule to remove
|
|
|
|
*
|
|
|
|
* Remove a rule from the current transaction
|
|
|
|
*/
|
2024-04-19 21:19:42 -05:00
|
|
|
void virFirewallRemoveCmd(virFirewall *firewall,
|
|
|
|
virFirewallCmd *fwCmd)
|
2013-03-04 10:30:40 -06:00
|
|
|
{
|
|
|
|
size_t i;
|
2021-03-11 01:16:13 -06:00
|
|
|
virFirewallGroup *group;
|
2013-03-04 10:30:40 -06:00
|
|
|
|
|
|
|
/* Explicitly not checking firewall->err too,
|
|
|
|
* because if rule was partially created
|
|
|
|
* before hitting error we must still remove
|
|
|
|
* it to avoid leaking 'rule'
|
|
|
|
*/
|
|
|
|
if (!firewall)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (firewall->ngroups == 0)
|
|
|
|
return;
|
|
|
|
group = firewall->groups[firewall->currentGroup];
|
|
|
|
|
|
|
|
if (group->addingRollback) {
|
|
|
|
for (i = 0; i < group->nrollback; i++) {
|
2024-04-19 21:19:42 -05:00
|
|
|
if (group->rollback[i] == fwCmd) {
|
2013-03-04 10:30:40 -06:00
|
|
|
VIR_DELETE_ELEMENT(group->rollback,
|
|
|
|
i,
|
|
|
|
group->nrollback);
|
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallCmdFree(fwCmd);
|
2013-03-04 10:30:40 -06:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (i = 0; i < group->naction; i++) {
|
2024-04-19 21:19:42 -05:00
|
|
|
if (group->action[i] == fwCmd) {
|
2013-03-04 10:30:40 -06:00
|
|
|
VIR_DELETE_ELEMENT(group->action,
|
|
|
|
i,
|
|
|
|
group->naction);
|
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallCmdFree(fwCmd);
|
2013-03-04 10:30:40 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
void virFirewallCmdAddArg(virFirewall *firewall,
|
|
|
|
virFirewallCmd *fwCmd,
|
|
|
|
const char *arg)
|
2013-03-04 10:30:40 -06:00
|
|
|
{
|
2024-04-19 21:19:42 -05:00
|
|
|
VIR_FIREWALL_CMD_RETURN_IF_ERROR(firewall, fwCmd);
|
2013-03-04 10:30:40 -06:00
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
ADD_ARG(fwCmd, arg);
|
2013-03-04 10:30:40 -06:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
void virFirewallCmdAddArgFormat(virFirewall *firewall,
|
|
|
|
virFirewallCmd *fwCmd,
|
|
|
|
const char *fmt, ...)
|
2013-03-04 10:30:40 -06:00
|
|
|
{
|
2019-10-15 08:16:31 -05:00
|
|
|
g_autofree char *arg = NULL;
|
2013-03-04 10:30:40 -06:00
|
|
|
va_list list;
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
VIR_FIREWALL_CMD_RETURN_IF_ERROR(firewall, fwCmd);
|
2013-03-04 10:30:40 -06:00
|
|
|
|
|
|
|
va_start(list, fmt);
|
2019-10-22 07:11:15 -05:00
|
|
|
arg = g_strdup_vprintf(fmt, list);
|
|
|
|
va_end(list);
|
2013-03-04 10:30:40 -06:00
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
ADD_ARG(fwCmd, arg);
|
2013-03-04 10:30:40 -06:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
void virFirewallCmdAddArgSet(virFirewall *firewall,
|
|
|
|
virFirewallCmd *fwCmd,
|
|
|
|
const char *const *args)
|
2013-03-04 10:30:40 -06:00
|
|
|
{
|
2024-04-19 21:19:42 -05:00
|
|
|
VIR_FIREWALL_CMD_RETURN_IF_ERROR(firewall, fwCmd);
|
2013-03-04 10:30:40 -06:00
|
|
|
|
|
|
|
while (*args) {
|
2024-04-19 21:19:42 -05:00
|
|
|
ADD_ARG(fwCmd, *args);
|
2013-03-04 10:30:40 -06:00
|
|
|
args++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
void virFirewallCmdAddArgList(virFirewall *firewall,
|
|
|
|
virFirewallCmd *fwCmd,
|
|
|
|
...)
|
2013-03-04 10:30:40 -06:00
|
|
|
{
|
|
|
|
va_list list;
|
|
|
|
const char *str;
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
VIR_FIREWALL_CMD_RETURN_IF_ERROR(firewall, fwCmd);
|
2013-03-04 10:30:40 -06:00
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
va_start(list, fwCmd);
|
2013-03-04 10:30:40 -06:00
|
|
|
|
2014-11-13 08:28:18 -06:00
|
|
|
while ((str = va_arg(list, char *)) != NULL)
|
2024-04-19 21:19:42 -05:00
|
|
|
ADD_ARG(fwCmd, str);
|
2013-03-04 10:30:40 -06:00
|
|
|
|
|
|
|
va_end(list);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
size_t virFirewallCmdGetArgCount(virFirewallCmd *fwCmd)
|
2013-03-04 10:30:40 -06:00
|
|
|
{
|
2024-04-19 21:19:42 -05:00
|
|
|
if (!fwCmd)
|
2013-03-04 10:30:40 -06:00
|
|
|
return 0;
|
2024-04-19 21:19:42 -05:00
|
|
|
return fwCmd->argsLen;
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* virFirewallStartTransaction:
|
|
|
|
* @firewall: the firewall ruleset
|
|
|
|
* @flags: bitset of virFirewallTransactionFlags
|
|
|
|
*
|
|
|
|
* Start a new transaction with associated rollback
|
|
|
|
* block.
|
|
|
|
*
|
|
|
|
* Should be followed by calls to add various rules to
|
|
|
|
* the transaction. Then virFirwallStartRollback should
|
|
|
|
* be used to provide rules to rollback upon transaction
|
|
|
|
* failure
|
|
|
|
*/
|
2021-03-11 01:16:13 -06:00
|
|
|
void virFirewallStartTransaction(virFirewall *firewall,
|
2013-03-04 10:30:40 -06:00
|
|
|
unsigned int flags)
|
|
|
|
{
|
2021-03-11 01:16:13 -06:00
|
|
|
virFirewallGroup *group;
|
2013-03-04 10:30:40 -06:00
|
|
|
|
|
|
|
VIR_FIREWALL_RETURN_IF_ERROR(firewall);
|
|
|
|
|
2021-02-23 10:36:36 -06:00
|
|
|
group = virFirewallGroupNew();
|
2013-03-04 10:30:40 -06:00
|
|
|
group->actionFlags = flags;
|
|
|
|
|
2021-03-19 18:37:03 -05:00
|
|
|
VIR_EXPAND_N(firewall->groups, firewall->ngroups, 1);
|
2013-03-04 10:30:40 -06:00
|
|
|
firewall->groups[firewall->ngroups - 1] = group;
|
|
|
|
firewall->currentGroup = firewall->ngroups - 1;
|
|
|
|
}
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* virFirewallTransactionGetFlags:
|
|
|
|
* @firewall: the firewall to look at
|
|
|
|
*
|
|
|
|
* Returns the virFirewallTransactionFlags for the currently active
|
|
|
|
* group (transaction) in @firewall.
|
|
|
|
*/
|
2024-04-19 21:19:42 -05:00
|
|
|
static virFirewallTransactionFlags
|
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallTransactionGetFlags(virFirewall *firewall)
|
|
|
|
{
|
|
|
|
return firewall->groups[firewall->currentGroup]->actionFlags;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-03-04 10:30:40 -06:00
|
|
|
/**
|
|
|
|
* virFirewallBeginRollback:
|
|
|
|
* @firewall: the firewall ruleset
|
|
|
|
* @flags: bitset of virFirewallRollbackFlags
|
|
|
|
*
|
|
|
|
* Mark the beginning of a set of rules able to rollback
|
|
|
|
* changes in this and all earlier transactions.
|
|
|
|
*
|
|
|
|
* Should be followed by calls to add various rules needed
|
|
|
|
* to rollback state. Then virFirewallStartTransaction
|
|
|
|
* should be used to indicate the beginning of the next
|
|
|
|
* transactional ruleset.
|
|
|
|
*/
|
2021-03-11 01:16:13 -06:00
|
|
|
void virFirewallStartRollback(virFirewall *firewall,
|
2013-03-04 10:30:40 -06:00
|
|
|
unsigned int flags)
|
|
|
|
{
|
2021-03-11 01:16:13 -06:00
|
|
|
virFirewallGroup *group;
|
2013-03-04 10:30:40 -06:00
|
|
|
|
|
|
|
VIR_FIREWALL_RETURN_IF_ERROR(firewall);
|
|
|
|
|
|
|
|
if (firewall->ngroups == 0) {
|
2014-04-30 01:51:29 -05:00
|
|
|
firewall->err = EINVAL;
|
2013-03-04 10:30:40 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
group = firewall->groups[firewall->ngroups-1];
|
|
|
|
group->rollbackFlags = flags;
|
|
|
|
group->addingRollback = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-04-21 16:42:30 -05:00
|
|
|
char *
|
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallCmdToString(const char *cmd,
|
|
|
|
virFirewallCmd *fwCmd)
|
2013-03-04 10:30:40 -06:00
|
|
|
{
|
2020-07-02 21:30:20 -05:00
|
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
2013-03-04 10:30:40 -06:00
|
|
|
size_t i;
|
|
|
|
|
2022-04-21 16:42:30 -05:00
|
|
|
virBufferAdd(&buf, cmd, -1);
|
2024-04-19 21:19:42 -05:00
|
|
|
for (i = 0; i < fwCmd->argsLen; i++) {
|
2013-03-04 10:30:40 -06:00
|
|
|
virBufferAddLit(&buf, " ");
|
2024-04-19 21:19:42 -05:00
|
|
|
virBufferAdd(&buf, fwCmd->args[i], -1);
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return virBufferContentAndReset(&buf);
|
|
|
|
}
|
|
|
|
|
2022-04-21 16:42:30 -05:00
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
#define VIR_IPTABLES_ARG_IS_CREATE(arg) \
|
|
|
|
(STREQ(arg, "--insert") || STREQ(arg, "-I") || \
|
|
|
|
STREQ(arg, "--append") || STREQ(arg, "-A"))
|
|
|
|
|
|
|
|
|
2013-03-04 10:30:40 -06:00
|
|
|
static int
|
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallCmdIptablesApply(virFirewall *firewall,
|
|
|
|
virFirewallCmd *fwCmd,
|
|
|
|
char **output)
|
2013-03-04 10:30:40 -06:00
|
|
|
{
|
2024-04-19 21:19:42 -05:00
|
|
|
const char *bin = virFirewallLayerCommandTypeToString(fwCmd->layer);
|
2024-04-19 21:19:42 -05:00
|
|
|
bool checkRollback = (virFirewallTransactionGetFlags(firewall) &
|
|
|
|
VIR_FIREWALL_TRANSACTION_AUTO_ROLLBACK);
|
|
|
|
bool needRollback = false;
|
2019-10-15 07:47:50 -05:00
|
|
|
g_autoptr(virCommand) cmd = NULL;
|
2022-03-13 13:21:02 -05:00
|
|
|
g_autofree char *cmdStr = NULL;
|
2019-10-15 08:16:31 -05:00
|
|
|
g_autofree char *error = NULL;
|
2024-04-19 21:19:42 -05:00
|
|
|
size_t i;
|
|
|
|
int status;
|
2013-03-04 10:30:40 -06:00
|
|
|
|
|
|
|
if (!bin) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
2023-03-09 08:13:35 -06:00
|
|
|
_("Unknown firewall layer %1$d"),
|
2024-04-19 21:19:42 -05:00
|
|
|
fwCmd->layer);
|
2018-07-24 10:52:17 -05:00
|
|
|
return -1;
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
cmd = virCommandNewArgList(bin, NULL);
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
/* lock to assure nobody else is messing with the tables while we are */
|
|
|
|
switch (fwCmd->layer) {
|
|
|
|
case VIR_FIREWALL_LAYER_ETHERNET:
|
|
|
|
virCommandAddArg(cmd, "--concurrent");
|
|
|
|
break;
|
|
|
|
case VIR_FIREWALL_LAYER_IPV4:
|
|
|
|
case VIR_FIREWALL_LAYER_IPV6:
|
|
|
|
virCommandAddArg(cmd, "-w");
|
|
|
|
break;
|
|
|
|
case VIR_FIREWALL_LAYER_LAST:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
for (i = 0; i < fwCmd->argsLen; i++) {
|
|
|
|
/* the -I/-A arg could be at any position in the list */
|
|
|
|
if (checkRollback && VIR_IPTABLES_ARG_IS_CREATE(fwCmd->args[i]))
|
|
|
|
needRollback = true;
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
virCommandAddArg(cmd, fwCmd->args[i]);
|
2024-04-19 21:19:42 -05:00
|
|
|
}
|
2013-03-04 10:30:40 -06:00
|
|
|
|
2022-03-13 13:21:02 -05:00
|
|
|
cmdStr = virCommandToString(cmd, false);
|
2024-04-19 21:19:42 -05:00
|
|
|
VIR_INFO("Running firewall command '%s'", NULLSTR(cmdStr));
|
2022-03-13 13:21:02 -05:00
|
|
|
|
2013-03-04 10:30:40 -06:00
|
|
|
virCommandSetOutputBuffer(cmd, output);
|
|
|
|
virCommandSetErrorBuffer(cmd, &error);
|
|
|
|
|
|
|
|
if (virCommandRun(cmd, &status) < 0)
|
2018-07-24 10:52:17 -05:00
|
|
|
return -1;
|
2013-03-04 10:30:40 -06:00
|
|
|
|
|
|
|
if (status != 0) {
|
2024-04-19 21:19:42 -05:00
|
|
|
/* the command failed, decide whether or not to report it */
|
2024-04-19 21:19:42 -05:00
|
|
|
if (fwCmd->ignoreErrors) {
|
2013-03-04 10:30:40 -06:00
|
|
|
VIR_DEBUG("Ignoring error running command");
|
2024-04-19 21:19:42 -05:00
|
|
|
return 0;
|
2013-03-04 10:30:40 -06:00
|
|
|
} else {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
2024-04-19 21:19:42 -05:00
|
|
|
_("Failed to run firewall command %1$s: %2$s"),
|
2022-03-13 13:21:02 -05:00
|
|
|
NULLSTR(cmdStr), NULLSTR(error));
|
2013-03-04 10:30:40 -06:00
|
|
|
VIR_FREE(*output);
|
2018-07-24 10:52:17 -05:00
|
|
|
return -1;
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
/* the command was successful, see if we need to add a
|
|
|
|
* rollback command
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (needRollback) {
|
|
|
|
virFirewallCmd *rollback
|
|
|
|
= virFirewallAddRollbackCmd(firewall, fwCmd->layer, NULL);
|
|
|
|
g_autofree char *rollbackStr = NULL;
|
|
|
|
|
|
|
|
for (i = 0; i < fwCmd->argsLen; i++) {
|
|
|
|
/* iptables --delete wants the entire commandline that
|
|
|
|
* was used for --insert but with s/insert/delete/
|
|
|
|
*/
|
|
|
|
if (VIR_IPTABLES_ARG_IS_CREATE(fwCmd->args[i])) {
|
|
|
|
virFirewallCmdAddArg(firewall, rollback, "--delete");
|
|
|
|
} else {
|
|
|
|
virFirewallCmdAddArg(firewall, rollback, fwCmd->args[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rollbackStr = virFirewallCmdToString(virFirewallLayerCommandTypeToString(fwCmd->layer),
|
|
|
|
rollback);
|
|
|
|
VIR_DEBUG("Recording Rollback command '%s'", NULLSTR(rollbackStr));
|
|
|
|
}
|
|
|
|
|
2018-07-24 10:52:17 -05:00
|
|
|
return 0;
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
network: add an nftables backend for network driver's firewall construction
Support using nftables to setup the firewall for each virtual network,
rather than iptables. The initial implementation of the nftables
backend creates (almost) exactly the same ruleset as the iptables
backend, determined by running the following commands on a host that
has an active virtual network:
iptables-save >iptables.txt
iptables-restore-translate -f iptables.txt
(and the similar ip6tables-save/ip6tables-restore-translate for an
IPv6 network). Correctness of the new backend was checked by comparing
the output of:
nft list ruleset
when the backend is set to iptables and when it is set to nftables.
This page was used as a guide:
https://wiki.nftables.org/wiki-nftables/index.php/Moving_from_iptables_to_nftables
The only differences between the rules created by the nftables backed
vs. the iptables backend (aside from a few inconsequential changes in
display order of some chains/options) are:
1) When we add nftables rules, rather than adding them in the
system-created "filter" and "nat" tables, we add them in a private
table (ie only we should be using it) created by us called "libvirt"
(the system-created "filter" and "nat" tables can't be used because
adding any rules to those tables directly with nft will cause failure
of any legacy application attempting to use iptables when it tries to
list the iptables rules (e.g. "iptables -S").
(NB: in nftables only a single table is required for both nat and
filter rules - the chains for each are differentiated by specifying
different "hook" locations for the toplevel chain of each)
2) Since the rules that were added to allow tftp/dns/dhcp traffic from
the guests to the host are unnecessary in the context of nftables,
those rules aren't added.
(Longer explanation: In the case of iptables, all rules were in a
single table, and it was always assumed that there would be some
"catch-all" REJECT rule added by "someone else" in the case that a
packet didn't match any specific rules, so libvirt added these
specific rules to ensure that, no matter what other rules were added
by any other subsystem, the guests would still have functional
tftp/dns/dhcp. For nftables though, the rules added by each subsystem
are in a separate table, and in order for traffic to be accepted, it
must be accepted by *all* tables, so just adding the specific rules to
libvirt's table doesn't help anything (as the default for the libvirt
table is ACCEPT anyway) and it just isn't practical/possible for
libvirt to find *all* other tables and add rules in all of them to
make sure the traffic is accepted. libvirt does this for firewalld (it
creates a "libvirt" zone that allows tftp/dns/dhcp, and adds all
virtual network bridges to that zone), however, so in that case no
extra work is required of the sysadmin.)
3) nftables doesn't support the "checksum mangle" rule (or any
equivalent functionality) that we have historically added to our
iptables rules, so the nftables rules we add have nothing related to
checksum mangling.
(NB: The result of (3) is that if you a) have a very old guest (RHEL5
era or earlier) and b) that guest is using a virtio-net network
device, and c) the virtio-net device is using vhost packet processing
(the default) then DHCP on the guest will fail. You can work around
this by adding <driver name='qemu'/> to the <interface> XML for the
guest).
There are certainly much better nftables rulesets that could be used
instead of those implemented here, and everything is in place to make
future changes to the rules that are used simple and free of surprises
(e.g. the rules that are added have coresponding "removal" commands
added to the network status so that we will always remove exactly the
rules that were previously added rather than trying to remove the
rules that "the current build of libvirt would have added" (which will
be incorrect the first time we run a libvirt with a newly modified
ruleset). For this initial implementation though, I wanted the
nftables rules to be as identical to the iptables rules as possible,
just to make it easier to verify that everything is working.
The backend can be manually chosen using the firewall_backend setting
in /etc/libvirt/network.conf. libvirtd/virtnetworkd will read this
setting when it starts; if there is no explicit setting, it will check
for availability of FIREWALL_BACKEND_DEFAULT_1 and then
FIREWALL_BACKEND_DEFAULT_2 (which are set at build time in
meson_options.txt or by adding -Dfirewall_backend_default_n=blah to
the meson commandline), and use the first backend that is available
(ie, that has the necessary programs installed). The standard
meson_options.txt is set to check for nftables first, and then
iptables.
Although it should be very safe to change the default backend from
iptables to nftables, that change is left for a later patch, to show
how the change in default can be undone if someone really needs to do
that.
Signed-off-by: Laine Stump <laine@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Tested-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 21:19:43 -05:00
|
|
|
#define VIR_NFTABLES_ARG_IS_CREATE(arg) \
|
|
|
|
(STREQ(arg, "insert") || STREQ(arg, "add") || STREQ(arg, "create"))
|
|
|
|
|
|
|
|
static int
|
|
|
|
virFirewallCmdNftablesApply(virFirewall *firewall G_GNUC_UNUSED,
|
|
|
|
virFirewallCmd *fwCmd,
|
|
|
|
char **output)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
size_t i;
|
|
|
|
int status;
|
|
|
|
|
|
|
|
cmd = virCommandNew(NFT);
|
|
|
|
|
|
|
|
if ((virFirewallTransactionGetFlags(firewall) & VIR_FIREWALL_TRANSACTION_AUTO_ROLLBACK) &&
|
|
|
|
fwCmd->argsLen > 1) {
|
|
|
|
/* skip any leading options to get to command verb */
|
|
|
|
for (i = 0; i < fwCmd->argsLen - 1; i++) {
|
|
|
|
if (fwCmd->args[i][0] != '-')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i + 1 < fwCmd->argsLen &&
|
|
|
|
VIR_NFTABLES_ARG_IS_CREATE(fwCmd->args[i])) {
|
|
|
|
|
|
|
|
cmdIdx = i;
|
|
|
|
objectType = fwCmd->args[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 < fwCmd->argsLen; i++)
|
|
|
|
virCommandAddArg(cmd, fwCmd->args[i]);
|
|
|
|
|
|
|
|
cmdStr = virCommandToString(cmd, false);
|
|
|
|
VIR_INFO("Applying '%s'", NULLSTR(cmdStr));
|
|
|
|
|
|
|
|
virCommandSetOutputBuffer(cmd, output);
|
|
|
|
virCommandSetErrorBuffer(cmd, &error);
|
|
|
|
|
|
|
|
if (virCommandRun(cmd, &status) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (status != 0) {
|
|
|
|
if (STREQ_NULLABLE(fwCmd->args[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 (fwCmd->ignoreErrors) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* there was an error, so we won't be building any rollback command,
|
|
|
|
* but the error should be ignored, so we return success
|
|
|
|
*/
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (needRollback) {
|
|
|
|
virFirewallCmd *rollback = virFirewallAddRollbackCmd(firewall, fwCmd->layer, 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 command is created from the original command 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 original nft command
|
|
|
|
*/
|
|
|
|
virFirewallCmdAddArgList(firewall, rollback, "delete", objectType,
|
|
|
|
fwCmd->args[cmdIdx + 2], /* ip/ip6/inet */
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
if (STREQ_NULLABLE(objectType, "rule") ||
|
|
|
|
STREQ_NULLABLE(objectType, "chain")) {
|
|
|
|
/* include table name in command */
|
|
|
|
virFirewallCmdAddArg(firewall, rollback, fwCmd->args[cmdIdx + 3]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (STREQ_NULLABLE(objectType, "rule")) {
|
|
|
|
/* include chain name in command */
|
|
|
|
virFirewallCmdAddArg(firewall, rollback, fwCmd->args[cmdIdx + 4]);
|
|
|
|
}
|
|
|
|
|
|
|
|
virFirewallCmdAddArgList(firewall, rollback, "handle", handleStr, NULL);
|
|
|
|
|
|
|
|
rollbackStr = virFirewallCmdToString(NFT, rollback);
|
|
|
|
VIR_DEBUG("Recording Rollback command '%s'", NULLSTR(rollbackStr));
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-03-04 10:30:40 -06:00
|
|
|
static int
|
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallApplyCmd(virFirewall *firewall,
|
2024-04-19 21:19:42 -05:00
|
|
|
virFirewallCmd *fwCmd)
|
2013-03-04 10:30:40 -06:00
|
|
|
{
|
2019-10-15 08:16:31 -05:00
|
|
|
g_autofree char *output = NULL;
|
2020-12-01 02:21:32 -06:00
|
|
|
g_auto(GStrv) lines = NULL;
|
2013-03-04 10:30:40 -06:00
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
if (fwCmd->argsLen == 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Can't apply empty firewall command"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
network: add an nftables backend for network driver's firewall construction
Support using nftables to setup the firewall for each virtual network,
rather than iptables. The initial implementation of the nftables
backend creates (almost) exactly the same ruleset as the iptables
backend, determined by running the following commands on a host that
has an active virtual network:
iptables-save >iptables.txt
iptables-restore-translate -f iptables.txt
(and the similar ip6tables-save/ip6tables-restore-translate for an
IPv6 network). Correctness of the new backend was checked by comparing
the output of:
nft list ruleset
when the backend is set to iptables and when it is set to nftables.
This page was used as a guide:
https://wiki.nftables.org/wiki-nftables/index.php/Moving_from_iptables_to_nftables
The only differences between the rules created by the nftables backed
vs. the iptables backend (aside from a few inconsequential changes in
display order of some chains/options) are:
1) When we add nftables rules, rather than adding them in the
system-created "filter" and "nat" tables, we add them in a private
table (ie only we should be using it) created by us called "libvirt"
(the system-created "filter" and "nat" tables can't be used because
adding any rules to those tables directly with nft will cause failure
of any legacy application attempting to use iptables when it tries to
list the iptables rules (e.g. "iptables -S").
(NB: in nftables only a single table is required for both nat and
filter rules - the chains for each are differentiated by specifying
different "hook" locations for the toplevel chain of each)
2) Since the rules that were added to allow tftp/dns/dhcp traffic from
the guests to the host are unnecessary in the context of nftables,
those rules aren't added.
(Longer explanation: In the case of iptables, all rules were in a
single table, and it was always assumed that there would be some
"catch-all" REJECT rule added by "someone else" in the case that a
packet didn't match any specific rules, so libvirt added these
specific rules to ensure that, no matter what other rules were added
by any other subsystem, the guests would still have functional
tftp/dns/dhcp. For nftables though, the rules added by each subsystem
are in a separate table, and in order for traffic to be accepted, it
must be accepted by *all* tables, so just adding the specific rules to
libvirt's table doesn't help anything (as the default for the libvirt
table is ACCEPT anyway) and it just isn't practical/possible for
libvirt to find *all* other tables and add rules in all of them to
make sure the traffic is accepted. libvirt does this for firewalld (it
creates a "libvirt" zone that allows tftp/dns/dhcp, and adds all
virtual network bridges to that zone), however, so in that case no
extra work is required of the sysadmin.)
3) nftables doesn't support the "checksum mangle" rule (or any
equivalent functionality) that we have historically added to our
iptables rules, so the nftables rules we add have nothing related to
checksum mangling.
(NB: The result of (3) is that if you a) have a very old guest (RHEL5
era or earlier) and b) that guest is using a virtio-net network
device, and c) the virtio-net device is using vhost packet processing
(the default) then DHCP on the guest will fail. You can work around
this by adding <driver name='qemu'/> to the <interface> XML for the
guest).
There are certainly much better nftables rulesets that could be used
instead of those implemented here, and everything is in place to make
future changes to the rules that are used simple and free of surprises
(e.g. the rules that are added have coresponding "removal" commands
added to the network status so that we will always remove exactly the
rules that were previously added rather than trying to remove the
rules that "the current build of libvirt would have added" (which will
be incorrect the first time we run a libvirt with a newly modified
ruleset). For this initial implementation though, I wanted the
nftables rules to be as identical to the iptables rules as possible,
just to make it easier to verify that everything is working.
The backend can be manually chosen using the firewall_backend setting
in /etc/libvirt/network.conf. libvirtd/virtnetworkd will read this
setting when it starts; if there is no explicit setting, it will check
for availability of FIREWALL_BACKEND_DEFAULT_1 and then
FIREWALL_BACKEND_DEFAULT_2 (which are set at build time in
meson_options.txt or by adding -Dfirewall_backend_default_n=blah to
the meson commandline), and use the first backend that is available
(ie, that has the necessary programs installed). The standard
meson_options.txt is set to check for nftables first, and then
iptables.
Although it should be very safe to change the default backend from
iptables to nftables, that change is left for a later patch, to show
how the change in default can be undone if someone really needs to do
that.
Signed-off-by: Laine Stump <laine@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Tested-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 21:19:43 -05:00
|
|
|
switch (virFirewallGetBackend(firewall)) {
|
|
|
|
case VIR_FIREWALL_BACKEND_IPTABLES:
|
|
|
|
if (virFirewallCmdIptablesApply(firewall, fwCmd, &output) < 0)
|
|
|
|
return -1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_FIREWALL_BACKEND_NFTABLES:
|
|
|
|
if (virFirewallCmdNftablesApply(firewall, fwCmd, &output) < 0)
|
|
|
|
return -1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_FIREWALL_BACKEND_LAST:
|
|
|
|
default:
|
|
|
|
virReportEnumRangeError(virFirewallBackend,
|
|
|
|
virFirewallGetBackend(firewall));
|
2013-03-04 10:30:40 -06:00
|
|
|
return -1;
|
network: add an nftables backend for network driver's firewall construction
Support using nftables to setup the firewall for each virtual network,
rather than iptables. The initial implementation of the nftables
backend creates (almost) exactly the same ruleset as the iptables
backend, determined by running the following commands on a host that
has an active virtual network:
iptables-save >iptables.txt
iptables-restore-translate -f iptables.txt
(and the similar ip6tables-save/ip6tables-restore-translate for an
IPv6 network). Correctness of the new backend was checked by comparing
the output of:
nft list ruleset
when the backend is set to iptables and when it is set to nftables.
This page was used as a guide:
https://wiki.nftables.org/wiki-nftables/index.php/Moving_from_iptables_to_nftables
The only differences between the rules created by the nftables backed
vs. the iptables backend (aside from a few inconsequential changes in
display order of some chains/options) are:
1) When we add nftables rules, rather than adding them in the
system-created "filter" and "nat" tables, we add them in a private
table (ie only we should be using it) created by us called "libvirt"
(the system-created "filter" and "nat" tables can't be used because
adding any rules to those tables directly with nft will cause failure
of any legacy application attempting to use iptables when it tries to
list the iptables rules (e.g. "iptables -S").
(NB: in nftables only a single table is required for both nat and
filter rules - the chains for each are differentiated by specifying
different "hook" locations for the toplevel chain of each)
2) Since the rules that were added to allow tftp/dns/dhcp traffic from
the guests to the host are unnecessary in the context of nftables,
those rules aren't added.
(Longer explanation: In the case of iptables, all rules were in a
single table, and it was always assumed that there would be some
"catch-all" REJECT rule added by "someone else" in the case that a
packet didn't match any specific rules, so libvirt added these
specific rules to ensure that, no matter what other rules were added
by any other subsystem, the guests would still have functional
tftp/dns/dhcp. For nftables though, the rules added by each subsystem
are in a separate table, and in order for traffic to be accepted, it
must be accepted by *all* tables, so just adding the specific rules to
libvirt's table doesn't help anything (as the default for the libvirt
table is ACCEPT anyway) and it just isn't practical/possible for
libvirt to find *all* other tables and add rules in all of them to
make sure the traffic is accepted. libvirt does this for firewalld (it
creates a "libvirt" zone that allows tftp/dns/dhcp, and adds all
virtual network bridges to that zone), however, so in that case no
extra work is required of the sysadmin.)
3) nftables doesn't support the "checksum mangle" rule (or any
equivalent functionality) that we have historically added to our
iptables rules, so the nftables rules we add have nothing related to
checksum mangling.
(NB: The result of (3) is that if you a) have a very old guest (RHEL5
era or earlier) and b) that guest is using a virtio-net network
device, and c) the virtio-net device is using vhost packet processing
(the default) then DHCP on the guest will fail. You can work around
this by adding <driver name='qemu'/> to the <interface> XML for the
guest).
There are certainly much better nftables rulesets that could be used
instead of those implemented here, and everything is in place to make
future changes to the rules that are used simple and free of surprises
(e.g. the rules that are added have coresponding "removal" commands
added to the network status so that we will always remove exactly the
rules that were previously added rather than trying to remove the
rules that "the current build of libvirt would have added" (which will
be incorrect the first time we run a libvirt with a newly modified
ruleset). For this initial implementation though, I wanted the
nftables rules to be as identical to the iptables rules as possible,
just to make it easier to verify that everything is working.
The backend can be manually chosen using the firewall_backend setting
in /etc/libvirt/network.conf. libvirtd/virtnetworkd will read this
setting when it starts; if there is no explicit setting, it will check
for availability of FIREWALL_BACKEND_DEFAULT_1 and then
FIREWALL_BACKEND_DEFAULT_2 (which are set at build time in
meson_options.txt or by adding -Dfirewall_backend_default_n=blah to
the meson commandline), and use the first backend that is available
(ie, that has the necessary programs installed). The standard
meson_options.txt is set to check for nftables first, and then
iptables.
Although it should be very safe to change the default backend from
iptables to nftables, that change is left for a later patch, to show
how the change in default can be undone if someone really needs to do
that.
Signed-off-by: Laine Stump <laine@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Tested-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 21:19:43 -05:00
|
|
|
}
|
2013-03-04 10:30:40 -06:00
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
if (fwCmd->queryCB && output) {
|
2021-02-05 11:35:07 -06:00
|
|
|
if (!(lines = g_strsplit(output, "\n", -1)))
|
2018-07-24 10:52:17 -05:00
|
|
|
return -1;
|
2013-03-04 10:30:40 -06:00
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
VIR_DEBUG("Invoking query %p with '%s'", fwCmd->queryCB, output);
|
|
|
|
if (fwCmd->queryCB(firewall, fwCmd->layer, (const char *const *)lines, fwCmd->queryOpaque) < 0)
|
2018-07-24 10:52:17 -05:00
|
|
|
return -1;
|
2013-03-04 10:30:40 -06:00
|
|
|
|
|
|
|
if (firewall->err) {
|
|
|
|
virReportSystemError(firewall->err, "%s",
|
2024-04-19 21:19:42 -05:00
|
|
|
_("Unable to create firewall command"));
|
2018-07-24 10:52:17 -05:00
|
|
|
return -1;
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-07-24 10:52:17 -05:00
|
|
|
return 0;
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2021-03-11 01:16:13 -06:00
|
|
|
virFirewallApplyGroup(virFirewall *firewall,
|
2013-03-04 10:30:40 -06:00
|
|
|
size_t idx)
|
|
|
|
{
|
2021-03-11 01:16:13 -06:00
|
|
|
virFirewallGroup *group = firewall->groups[idx];
|
2024-04-19 21:19:42 -05:00
|
|
|
|
2013-03-04 10:30:40 -06:00
|
|
|
size_t i;
|
|
|
|
|
2017-09-25 05:43:33 -05:00
|
|
|
VIR_INFO("Starting transaction for firewall=%p group=%p flags=0x%x",
|
2014-11-11 06:34:57 -06:00
|
|
|
firewall, group, group->actionFlags);
|
2013-03-04 10:30:40 -06:00
|
|
|
firewall->currentGroup = idx;
|
|
|
|
group->addingRollback = false;
|
|
|
|
for (i = 0; i < group->naction; i++) {
|
2024-04-19 21:19:42 -05:00
|
|
|
if (virFirewallApplyCmd(firewall, group->action[i]) < 0)
|
2013-03-04 10:30:40 -06:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2021-03-11 01:16:13 -06:00
|
|
|
virFirewallRollbackGroup(virFirewall *firewall,
|
2013-03-04 10:30:40 -06:00
|
|
|
size_t idx)
|
|
|
|
{
|
2021-03-11 01:16:13 -06:00
|
|
|
virFirewallGroup *group = firewall->groups[idx];
|
2013-03-04 10:30:40 -06:00
|
|
|
size_t i;
|
|
|
|
|
|
|
|
VIR_INFO("Starting rollback for group %p", group);
|
|
|
|
firewall->currentGroup = idx;
|
|
|
|
group->addingRollback = true;
|
2024-04-19 21:19:42 -05:00
|
|
|
for (i = 0; i < group->nrollback; i++)
|
2024-04-19 21:19:42 -05:00
|
|
|
ignore_value(virFirewallApplyCmd(firewall, group->rollback[i]));
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
2021-03-11 01:16:13 -06:00
|
|
|
virFirewallApply(virFirewall *firewall)
|
2013-03-04 10:30:40 -06:00
|
|
|
{
|
|
|
|
size_t i, j;
|
2024-04-19 21:19:42 -05:00
|
|
|
VIR_LOCK_GUARD lock = virLockGuardLock(&fwCmdLock);
|
2013-03-04 10:30:40 -06:00
|
|
|
|
2021-02-23 10:39:50 -06:00
|
|
|
if (!firewall || firewall->err) {
|
2021-03-05 03:38:49 -06:00
|
|
|
int err = EINVAL;
|
|
|
|
|
|
|
|
if (firewall)
|
|
|
|
err = firewall->err;
|
|
|
|
|
2024-04-19 21:19:42 -05:00
|
|
|
virReportSystemError(err, "%s", _("Unable to create firewall command"));
|
2022-02-08 07:59:30 -06:00
|
|
|
return -1;
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
VIR_DEBUG("Applying groups for %p", firewall);
|
|
|
|
for (i = 0; i < firewall->ngroups; i++) {
|
|
|
|
if (virFirewallApplyGroup(firewall, i) < 0) {
|
|
|
|
size_t first = i;
|
2018-12-06 11:33:39 -06:00
|
|
|
virErrorPtr saved_error;
|
|
|
|
|
2020-08-03 10:28:06 -05:00
|
|
|
VIR_DEBUG("Rolling back groups up to %zu for %p", i, firewall);
|
|
|
|
|
2018-12-06 11:33:39 -06:00
|
|
|
virErrorPreserveLast(&saved_error);
|
2013-03-04 10:30:40 -06:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Look at any inheritance markers to figure out
|
|
|
|
* what the first rollback group we need to apply is
|
|
|
|
*/
|
|
|
|
for (j = 0; j < i; j++) {
|
|
|
|
VIR_DEBUG("Checking inheritance of group %zu", i - j);
|
|
|
|
if (firewall->groups[i - j]->rollbackFlags &
|
|
|
|
VIR_FIREWALL_ROLLBACK_INHERIT_PREVIOUS)
|
|
|
|
first = (i - j) - 1;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Now apply all rollback groups in order
|
|
|
|
*/
|
|
|
|
for (j = first; j <= i; j++) {
|
|
|
|
VIR_DEBUG("Rolling back group %zu", j);
|
|
|
|
virFirewallRollbackGroup(firewall, j);
|
|
|
|
}
|
|
|
|
|
2018-12-06 11:33:39 -06:00
|
|
|
virErrorRestore(&saved_error);
|
2013-03-04 10:30:40 -06:00
|
|
|
VIR_DEBUG("Done rolling back groups for %p", firewall);
|
2022-02-08 07:59:30 -06:00
|
|
|
return -1;
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
VIR_DEBUG("Done applying groups for %p", firewall);
|
|
|
|
|
2022-02-08 07:59:30 -06:00
|
|
|
return 0;
|
2013-03-04 10:30:40 -06:00
|
|
|
}
|
2024-04-19 21:19:42 -05:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* virFirewallNewFromRollback:
|
|
|
|
|
|
|
|
* @original: the original virFirewall object containing the rollback
|
|
|
|
* of interest
|
|
|
|
* @fwRemoval: a firewall object that, when applied, will remove @original
|
|
|
|
*
|
|
|
|
* Copy the rollback rules from the current virFirewall object as a
|
|
|
|
* new virFirewall. This virFirewall can then be saved to apply later
|
|
|
|
* and counteract everything done by the original.
|
|
|
|
*
|
|
|
|
* Returns 0 on success, -1 on error
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
virFirewallNewFromRollback(virFirewall *original,
|
|
|
|
virFirewall **fwRemoval)
|
|
|
|
{
|
|
|
|
size_t g;
|
|
|
|
g_autoptr(virFirewall) firewall = NULL;
|
|
|
|
|
|
|
|
if (original->err) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("error in original firewall object"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
firewall = virFirewallNew(original->backend);
|
|
|
|
|
|
|
|
/* add the rollback commands in reverse order of actions/groups of
|
|
|
|
* what was applied in the original firewall.
|
|
|
|
*/
|
|
|
|
for (g = original->ngroups; g > 0; g--) {
|
|
|
|
size_t r;
|
|
|
|
virFirewallGroup *group = original->groups[g - 1];
|
|
|
|
|
|
|
|
if (group->nrollback == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
virFirewallStartTransaction(firewall, VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS);
|
|
|
|
|
|
|
|
for (r = group->nrollback; r > 0; r--) {
|
|
|
|
size_t i;
|
|
|
|
virFirewallCmd *origCmd = group->rollback[r - 1];
|
|
|
|
virFirewallCmd *rbCmd = virFirewallAddCmd(firewall, origCmd->layer, NULL);
|
|
|
|
|
|
|
|
for (i = 0; i < origCmd->argsLen; i++)
|
|
|
|
ADD_ARG(rbCmd, origCmd->args[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firewall->ngroups == 0)
|
|
|
|
VIR_DEBUG("original firewall object is empty");
|
|
|
|
else
|
|
|
|
*fwRemoval = g_steal_pointer(&firewall);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2024-04-19 21:19:42 -05:00
|
|
|
|
|
|
|
|
|
|
|
/* virFirewallGetFlagsFromNode:
|
|
|
|
* @node: the xmlNode to check for an ignoreErrors attribute
|
|
|
|
*
|
|
|
|
* A short helper to get the setting of the ignorErrors attribute from
|
|
|
|
* an xmlNode. Returns -1 on error (with error reported), or the
|
|
|
|
* VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS bit set/reset according to
|
|
|
|
* the value of the attribute.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
virFirewallGetFlagsFromNode(xmlNodePtr node)
|
|
|
|
{
|
|
|
|
virTristateBool ignoreErrors;
|
|
|
|
|
|
|
|
if (virXMLPropTristateBool(node, "ignoreErrors", VIR_XML_PROP_NONE, &ignoreErrors) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (ignoreErrors == VIR_TRISTATE_BOOL_YES)
|
|
|
|
return VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* virFirewallParseXML:
|
|
|
|
* @firewall: pointer to virFirewall* to fill in with new virFirewall object
|
|
|
|
*
|
|
|
|
* Construct a new virFirewall object according to the XML in
|
|
|
|
* xmlNodePtr. Return 0 (and new object) on success, or -1 (with
|
|
|
|
* error reported) on error.
|
|
|
|
*
|
|
|
|
* Example of <firewall> element XML:
|
|
|
|
*
|
|
|
|
* <firewall backend='iptables|nftables'>
|
|
|
|
* <group ignoreErrors='yes|no'>
|
|
|
|
* <action layer='ethernet|ipv4|ipv6' ignoreErrors='yes|no'>
|
|
|
|
* <args>
|
|
|
|
* <item>arg1</item>
|
|
|
|
* <item>arg2</item>
|
|
|
|
* ...
|
|
|
|
* </args>
|
|
|
|
* </action>
|
|
|
|
* <action ...>
|
|
|
|
* ...
|
|
|
|
</action>
|
|
|
|
* ...
|
|
|
|
* </group>
|
|
|
|
* ...
|
|
|
|
* </firewall>
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
virFirewallParseXML(virFirewall **firewall,
|
|
|
|
xmlNodePtr node,
|
|
|
|
xmlXPathContextPtr ctxt)
|
|
|
|
{
|
|
|
|
g_autoptr(virFirewall) newfw = NULL;
|
|
|
|
virFirewallBackend backend;
|
|
|
|
g_autofree xmlNodePtr *groupNodes = NULL;
|
|
|
|
ssize_t ngroups;
|
|
|
|
size_t g;
|
|
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt);
|
|
|
|
|
|
|
|
ctxt->node = node;
|
|
|
|
|
|
|
|
if (virXMLPropEnum(node, "backend", virFirewallBackendTypeFromString,
|
|
|
|
VIR_XML_PROP_REQUIRED, &backend) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
newfw = virFirewallNew(backend);
|
|
|
|
|
|
|
|
newfw->name = virXMLPropString(node, "name");
|
|
|
|
|
|
|
|
ngroups = virXPathNodeSet("./group", ctxt, &groupNodes);
|
|
|
|
if (ngroups < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
for (g = 0; g < ngroups; g++) {
|
|
|
|
int flags = 0;
|
|
|
|
g_autofree xmlNodePtr *actionNodes = NULL;
|
|
|
|
ssize_t nactions;
|
|
|
|
size_t a;
|
|
|
|
|
|
|
|
ctxt->node = groupNodes[g];
|
|
|
|
nactions = virXPathNodeSet("./action", ctxt, &actionNodes);
|
|
|
|
if (nactions < 0)
|
|
|
|
return -1;
|
|
|
|
if (nactions == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if ((flags = virFirewallGetFlagsFromNode(groupNodes[g])) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
virFirewallStartTransaction(newfw, flags);
|
|
|
|
|
|
|
|
for (a = 0; a < nactions; a++) {
|
|
|
|
g_autofree xmlNodePtr *argsNodes = NULL;
|
|
|
|
ssize_t nargs;
|
|
|
|
size_t i;
|
|
|
|
virFirewallLayer layer;
|
|
|
|
virFirewallCmd *action;
|
|
|
|
bool ignoreErrors;
|
|
|
|
|
|
|
|
ctxt->node = actionNodes[a];
|
|
|
|
|
|
|
|
if (!(ctxt->node = virXPathNode("./args", ctxt)))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if ((flags = virFirewallGetFlagsFromNode(actionNodes[a])) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
ignoreErrors = flags & VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS;
|
|
|
|
|
|
|
|
if (virXMLPropEnum(actionNodes[a], "layer",
|
|
|
|
virFirewallLayerTypeFromString,
|
|
|
|
VIR_XML_PROP_REQUIRED, &layer) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
nargs = virXPathNodeSet("./item", ctxt, &argsNodes);
|
|
|
|
if (nargs < 0)
|
|
|
|
return -1;
|
|
|
|
if (nargs == 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Invalid firewall command has 0 arguments"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
action = virFirewallAddCmdFull(newfw, layer, ignoreErrors,
|
|
|
|
NULL, NULL, NULL);
|
|
|
|
for (i = 0; i < nargs; i++) {
|
|
|
|
|
|
|
|
char *arg = virXMLNodeContentString(argsNodes[i]);
|
|
|
|
if (!arg)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
virFirewallCmdAddArg(newfw, action, arg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*firewall = g_steal_pointer(&newfw);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* virFirewallFormat:
|
|
|
|
* @buf: output buffer
|
|
|
|
* @firewall: the virFirewall object to format as XML
|
|
|
|
*
|
|
|
|
* Format virFirewall object @firewall into @buf as XML.
|
|
|
|
* Returns 0 on success, -1 on failure.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
virFirewallFormat(virBuffer *buf,
|
|
|
|
virFirewall *firewall)
|
|
|
|
{
|
|
|
|
size_t g;
|
|
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
|
|
|
|
virBufferEscapeString(&attrBuf, " name='%s'", firewall->name);
|
|
|
|
virBufferAsprintf(&attrBuf, " backend='%s'",
|
|
|
|
virFirewallBackendTypeToString(virFirewallGetBackend(firewall)));
|
|
|
|
|
|
|
|
for (g = 0; g < firewall->ngroups; g++) {
|
|
|
|
virFirewallGroup *group = firewall->groups[g];
|
|
|
|
bool groupIgnoreErrors = (group->actionFlags &
|
|
|
|
VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS);
|
|
|
|
size_t a;
|
|
|
|
|
|
|
|
virBufferAddLit(&childBuf, "<group");
|
|
|
|
if (groupIgnoreErrors)
|
|
|
|
virBufferAddLit(&childBuf, " ignoreErrors='yes'");
|
|
|
|
virBufferAddLit(&childBuf, ">\n");
|
|
|
|
virBufferAdjustIndent(&childBuf, 2);
|
|
|
|
|
|
|
|
for (a = 0; a < group->naction; a++) {
|
|
|
|
virFirewallCmd *action = group->action[a];
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
virBufferAsprintf(&childBuf, "<action layer='%s'",
|
|
|
|
virFirewallLayerTypeToString(action->layer));
|
|
|
|
/* if the entire group has ignoreErrors='yes', then it's
|
|
|
|
* redundant to have it for an action of the group
|
|
|
|
*/
|
|
|
|
if (action->ignoreErrors && !groupIgnoreErrors)
|
|
|
|
virBufferAddLit(&childBuf, " ignoreErrors='yes'");
|
|
|
|
virBufferAddLit(&childBuf, ">\n");
|
|
|
|
|
|
|
|
virBufferAdjustIndent(&childBuf, 2);
|
|
|
|
virBufferAddLit(&childBuf, "<args>\n");
|
|
|
|
virBufferAdjustIndent(&childBuf, 2);
|
|
|
|
for (i = 0; i < virFirewallCmdGetArgCount(action); i++)
|
|
|
|
virBufferEscapeString(&childBuf, "<item>%s</item>\n", action->args[i]);
|
|
|
|
virBufferAdjustIndent(&childBuf, -2);
|
|
|
|
virBufferAddLit(&childBuf, "</args>\n");
|
|
|
|
virBufferAdjustIndent(&childBuf, -2);
|
|
|
|
virBufferAddLit(&childBuf, "</action>\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
virBufferAdjustIndent(&childBuf, -2);
|
|
|
|
virBufferAddLit(&childBuf, "</group>\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
virXMLFormatElement(buf, "firewall", &attrBuf, &childBuf);
|
|
|
|
return 0;
|
|
|
|
}
|