1
0
mirror of https://gitlab.com/libvirt/libvirt.git synced 2025-03-07 17:28:15 +00:00
libvirt/src/util/virfirewall.c

753 lines
19 KiB
C
Raw Normal View History

/*
* virfirewall.c: integration with firewalls
*
* Copyright (C) 2013-2015 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 "virfirewall.h"
#include "virfirewalld.h"
#include "viralloc.h"
#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 22:19:42 -04:00
VIR_ENUM_IMPL(virFirewallBackend,
VIR_FIREWALL_BACKEND_LAST,
"iptables");
typedef struct _virFirewallGroup virFirewallGroup;
VIR_ENUM_DECL(virFirewallLayerCommand);
VIR_ENUM_IMPL(virFirewallLayerCommand,
VIR_FIREWALL_LAYER_LAST,
EBTABLES,
IPTABLES,
IP6TABLES,
);
struct _virFirewallCmd {
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;
virFirewallCmd **action;
size_t nrollback;
virFirewallCmd **rollback;
bool addingRollback;
};
struct _virFirewall {
int err;
size_t ngroups;
virFirewallGroup **groups;
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 22:19:42 -04:00
virFirewallBackend backend;
};
static virMutex fwCmdLock = VIR_MUTEX_INITIALIZER;
static virFirewallGroup *
virFirewallGroupNew(void)
{
return g_new0(virFirewallGroup, 1);
}
/**
* 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 22:19:42 -04:00
virFirewall *virFirewallNew(virFirewallBackend backend)
{
virFirewall *firewall = g_new0(virFirewall, 1);
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 22:19:42 -04:00
firewall->backend = backend;
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 22:19:42 -04:00
virFirewallBackend
virFirewallGetBackend(virFirewall *firewall)
{
return firewall->backend;
}
static void
virFirewallCmdFree(virFirewallCmd *fwCmd)
{
size_t i;
if (!fwCmd)
return;
for (i = 0; i < fwCmd->argsLen; i++)
g_free(fwCmd->args[i]);
g_free(fwCmd->args);
g_free(fwCmd);
}
static void
virFirewallGroupFree(virFirewallGroup *group)
{
size_t i;
if (!group)
return;
for (i = 0; i < group->naction; i++)
virFirewallCmdFree(group->action[i]);
g_free(group->action);
for (i = 0; i < group->nrollback; i++)
virFirewallCmdFree(group->rollback[i]);
g_free(group->rollback);
g_free(group);
}
/**
* virFirewallFree:
*
* Release all memory associated with the firewall
* ruleset
*/
void virFirewallFree(virFirewall *firewall)
{
size_t i;
if (!firewall)
return;
for (i = 0; i < firewall->ngroups; i++)
virFirewallGroupFree(firewall->groups[i]);
g_free(firewall->groups);
g_free(firewall);
}
#define VIR_FIREWALL_RETURN_IF_ERROR(firewall) \
do { \
if (!firewall || firewall->err) \
return; \
} while (0)
#define VIR_FIREWALL_CMD_RETURN_IF_ERROR(firewall, fwCmd)\
do { \
if (!firewall || firewall->err || !fwCmd) \
return; \
} while (0)
#define VIR_FIREWALL_RETURN_NULL_IF_ERROR(firewall) \
do { \
if (!firewall || firewall->err) \
return NULL; \
} while (0)
#define ADD_ARG(fwCmd, str) \
do { \
VIR_RESIZE_N(fwCmd->args, fwCmd->argsAlloc, fwCmd->argsLen, 1); \
fwCmd->args[fwCmd->argsLen++] = g_strdup(str); \
} while (0)
util: new functions to support adding individual firewall rollback commands In the past virFirewall required all rollback commands for a group (those commands necessary to "undo" any rules that had been added in that group in case of a later failure) to be manually added by switching into the virFirewall object into "rollback mode" and then re-calling the inverse of the exact virFirewallAddCmd*() APIs that had been called to add the original rules (ie. for each "iptables --insert" command, for rollback we would need to add a command with all arguments identical except that "--insert" would be replaced by "--delete"). Because nftables can't search for rules to remove by comparing all the arguments (it instead expects *only* a handle that is provided via stdout when the rule was originally added), we won't be able to follow the iptables method and manually construct the command to undo any given nft command by just duplicating all the args of the command (except the action). Instead we will need to be able to automatically create a rollback command at the time the rule-adding command is executed (e.g. an "nft delete rule" command that would include the rule handle returned in stdout by an "nft add rule" command). In order to make this happen, we need to be able to 1) learn whether the user of the virFirewall API desires this behavior (handled by a new transaction flag called VIR_FIREWALL_TRANSACTION_AUTO_ROLLBACK that can be retrieved with the new virFirewallTransactionGetFlags() API), and 2) add a new command to the current group's rollback command list (with the new virFirewallAddRollbackCmd()). We will actually use this capability in an upcoming patch. Signed-off-by: Laine Stump <laine@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 22:19:42 -04:00
static virFirewallCmd *
virFirewallAddCmdFullV(virFirewall *firewall,
virFirewallLayer layer,
bool ignoreErrors,
util: new functions to support adding individual firewall rollback commands In the past virFirewall required all rollback commands for a group (those commands necessary to "undo" any rules that had been added in that group in case of a later failure) to be manually added by switching into the virFirewall object into "rollback mode" and then re-calling the inverse of the exact virFirewallAddCmd*() APIs that had been called to add the original rules (ie. for each "iptables --insert" command, for rollback we would need to add a command with all arguments identical except that "--insert" would be replaced by "--delete"). Because nftables can't search for rules to remove by comparing all the arguments (it instead expects *only* a handle that is provided via stdout when the rule was originally added), we won't be able to follow the iptables method and manually construct the command to undo any given nft command by just duplicating all the args of the command (except the action). Instead we will need to be able to automatically create a rollback command at the time the rule-adding command is executed (e.g. an "nft delete rule" command that would include the rule handle returned in stdout by an "nft add rule" command). In order to make this happen, we need to be able to 1) learn whether the user of the virFirewall API desires this behavior (handled by a new transaction flag called VIR_FIREWALL_TRANSACTION_AUTO_ROLLBACK that can be retrieved with the new virFirewallTransactionGetFlags() API), and 2) add a new command to the current group's rollback command list (with the new virFirewallAddRollbackCmd()). We will actually use this capability in an upcoming patch. Signed-off-by: Laine Stump <laine@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 22:19:42 -04:00
bool isRollback,
virFirewallQueryCallback cb,
void *opaque,
va_list args)
{
virFirewallGroup *group;
virFirewallCmd *fwCmd;
char *str;
VIR_FIREWALL_RETURN_NULL_IF_ERROR(firewall);
if (firewall->ngroups == 0) {
firewall->err = EINVAL;
return NULL;
}
group = firewall->groups[firewall->currentGroup];
fwCmd = g_new0(virFirewallCmd, 1);
fwCmd->layer = layer;
while ((str = va_arg(args, char *)) != NULL)
ADD_ARG(fwCmd, str);
util: new functions to support adding individual firewall rollback commands In the past virFirewall required all rollback commands for a group (those commands necessary to "undo" any rules that had been added in that group in case of a later failure) to be manually added by switching into the virFirewall object into "rollback mode" and then re-calling the inverse of the exact virFirewallAddCmd*() APIs that had been called to add the original rules (ie. for each "iptables --insert" command, for rollback we would need to add a command with all arguments identical except that "--insert" would be replaced by "--delete"). Because nftables can't search for rules to remove by comparing all the arguments (it instead expects *only* a handle that is provided via stdout when the rule was originally added), we won't be able to follow the iptables method and manually construct the command to undo any given nft command by just duplicating all the args of the command (except the action). Instead we will need to be able to automatically create a rollback command at the time the rule-adding command is executed (e.g. an "nft delete rule" command that would include the rule handle returned in stdout by an "nft add rule" command). In order to make this happen, we need to be able to 1) learn whether the user of the virFirewall API desires this behavior (handled by a new transaction flag called VIR_FIREWALL_TRANSACTION_AUTO_ROLLBACK that can be retrieved with the new virFirewallTransactionGetFlags() API), and 2) add a new command to the current group's rollback command list (with the new virFirewallAddRollbackCmd()). We will actually use this capability in an upcoming patch. Signed-off-by: Laine Stump <laine@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 22:19:42 -04:00
if (isRollback || group->addingRollback) {
fwCmd->ignoreErrors = true; /* always ignore errors when rolling back */
util: new functions to support adding individual firewall rollback commands In the past virFirewall required all rollback commands for a group (those commands necessary to "undo" any rules that had been added in that group in case of a later failure) to be manually added by switching into the virFirewall object into "rollback mode" and then re-calling the inverse of the exact virFirewallAddCmd*() APIs that had been called to add the original rules (ie. for each "iptables --insert" command, for rollback we would need to add a command with all arguments identical except that "--insert" would be replaced by "--delete"). Because nftables can't search for rules to remove by comparing all the arguments (it instead expects *only* a handle that is provided via stdout when the rule was originally added), we won't be able to follow the iptables method and manually construct the command to undo any given nft command by just duplicating all the args of the command (except the action). Instead we will need to be able to automatically create a rollback command at the time the rule-adding command is executed (e.g. an "nft delete rule" command that would include the rule handle returned in stdout by an "nft add rule" command). In order to make this happen, we need to be able to 1) learn whether the user of the virFirewall API desires this behavior (handled by a new transaction flag called VIR_FIREWALL_TRANSACTION_AUTO_ROLLBACK that can be retrieved with the new virFirewallTransactionGetFlags() API), and 2) add a new command to the current group's rollback command list (with the new virFirewallAddRollbackCmd()). We will actually use this capability in an upcoming patch. Signed-off-by: Laine Stump <laine@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 22:19:42 -04:00
fwCmd->queryCB = NULL; /* rollback commands can't have a callback */
fwCmd->queryOpaque = NULL;
VIR_APPEND_ELEMENT_COPY(group->rollback, group->nrollback, fwCmd);
} else {
/* 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);
util: new functions to support adding individual firewall rollback commands In the past virFirewall required all rollback commands for a group (those commands necessary to "undo" any rules that had been added in that group in case of a later failure) to be manually added by switching into the virFirewall object into "rollback mode" and then re-calling the inverse of the exact virFirewallAddCmd*() APIs that had been called to add the original rules (ie. for each "iptables --insert" command, for rollback we would need to add a command with all arguments identical except that "--insert" would be replaced by "--delete"). Because nftables can't search for rules to remove by comparing all the arguments (it instead expects *only* a handle that is provided via stdout when the rule was originally added), we won't be able to follow the iptables method and manually construct the command to undo any given nft command by just duplicating all the args of the command (except the action). Instead we will need to be able to automatically create a rollback command at the time the rule-adding command is executed (e.g. an "nft delete rule" command that would include the rule handle returned in stdout by an "nft add rule" command). In order to make this happen, we need to be able to 1) learn whether the user of the virFirewall API desires this behavior (handled by a new transaction flag called VIR_FIREWALL_TRANSACTION_AUTO_ROLLBACK that can be retrieved with the new virFirewallTransactionGetFlags() API), and 2) add a new command to the current group's rollback command list (with the new virFirewallAddRollbackCmd()). We will actually use this capability in an upcoming patch. Signed-off-by: Laine Stump <laine@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 22:19:42 -04:00
fwCmd->queryCB = cb;
fwCmd->queryOpaque = opaque;
VIR_APPEND_ELEMENT_COPY(group->action, group->naction, fwCmd);
}
return fwCmd;
}
/**
* virFirewallAddCmdFull:
* @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
* rules by invoking the virFirewallAddCmd method, but
* 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
*/
virFirewallCmd *virFirewallAddCmdFull(virFirewall *firewall,
virFirewallLayer layer,
bool ignoreErrors,
virFirewallQueryCallback cb,
void *opaque,
...)
{
virFirewallCmd *fwCmd;
va_list args;
va_start(args, opaque);
util: new functions to support adding individual firewall rollback commands In the past virFirewall required all rollback commands for a group (those commands necessary to "undo" any rules that had been added in that group in case of a later failure) to be manually added by switching into the virFirewall object into "rollback mode" and then re-calling the inverse of the exact virFirewallAddCmd*() APIs that had been called to add the original rules (ie. for each "iptables --insert" command, for rollback we would need to add a command with all arguments identical except that "--insert" would be replaced by "--delete"). Because nftables can't search for rules to remove by comparing all the arguments (it instead expects *only* a handle that is provided via stdout when the rule was originally added), we won't be able to follow the iptables method and manually construct the command to undo any given nft command by just duplicating all the args of the command (except the action). Instead we will need to be able to automatically create a rollback command at the time the rule-adding command is executed (e.g. an "nft delete rule" command that would include the rule handle returned in stdout by an "nft add rule" command). In order to make this happen, we need to be able to 1) learn whether the user of the virFirewall API desires this behavior (handled by a new transaction flag called VIR_FIREWALL_TRANSACTION_AUTO_ROLLBACK that can be retrieved with the new virFirewallTransactionGetFlags() API), and 2) add a new command to the current group's rollback command list (with the new virFirewallAddRollbackCmd()). We will actually use this capability in an upcoming patch. Signed-off-by: Laine Stump <laine@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 22:19:42 -04: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);
va_end(args);
return fwCmd;
}
/**
* virFirewallRemoveCmd:
* @firewall: firewall ruleset to remove from
* @rule: the rule to remove
*
* Remove a rule from the current transaction
*/
void virFirewallRemoveCmd(virFirewall *firewall,
virFirewallCmd *fwCmd)
{
size_t i;
virFirewallGroup *group;
/* 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++) {
if (group->rollback[i] == fwCmd) {
VIR_DELETE_ELEMENT(group->rollback,
i,
group->nrollback);
virFirewallCmdFree(fwCmd);
break;
}
}
} else {
for (i = 0; i < group->naction; i++) {
if (group->action[i] == fwCmd) {
VIR_DELETE_ELEMENT(group->action,
i,
group->naction);
virFirewallCmdFree(fwCmd);
return;
}
}
}
}
void virFirewallCmdAddArg(virFirewall *firewall,
virFirewallCmd *fwCmd,
const char *arg)
{
VIR_FIREWALL_CMD_RETURN_IF_ERROR(firewall, fwCmd);
ADD_ARG(fwCmd, arg);
return;
}
void virFirewallCmdAddArgFormat(virFirewall *firewall,
virFirewallCmd *fwCmd,
const char *fmt, ...)
{
g_autofree char *arg = NULL;
va_list list;
VIR_FIREWALL_CMD_RETURN_IF_ERROR(firewall, fwCmd);
va_start(list, fmt);
arg = g_strdup_vprintf(fmt, list);
va_end(list);
ADD_ARG(fwCmd, arg);
return;
}
void virFirewallCmdAddArgSet(virFirewall *firewall,
virFirewallCmd *fwCmd,
const char *const *args)
{
VIR_FIREWALL_CMD_RETURN_IF_ERROR(firewall, fwCmd);
while (*args) {
ADD_ARG(fwCmd, *args);
args++;
}
return;
}
void virFirewallCmdAddArgList(virFirewall *firewall,
virFirewallCmd *fwCmd,
...)
{
va_list list;
const char *str;
VIR_FIREWALL_CMD_RETURN_IF_ERROR(firewall, fwCmd);
va_start(list, fwCmd);
while ((str = va_arg(list, char *)) != NULL)
ADD_ARG(fwCmd, str);
va_end(list);
return;
}
size_t virFirewallCmdGetArgCount(virFirewallCmd *fwCmd)
{
if (!fwCmd)
return 0;
return fwCmd->argsLen;
}
/**
* 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
*/
void virFirewallStartTransaction(virFirewall *firewall,
unsigned int flags)
{
virFirewallGroup *group;
VIR_FIREWALL_RETURN_IF_ERROR(firewall);
group = virFirewallGroupNew();
group->actionFlags = flags;
VIR_EXPAND_N(firewall->groups, firewall->ngroups, 1);
firewall->groups[firewall->ngroups - 1] = group;
firewall->currentGroup = firewall->ngroups - 1;
}
util: new functions to support adding individual firewall rollback commands In the past virFirewall required all rollback commands for a group (those commands necessary to "undo" any rules that had been added in that group in case of a later failure) to be manually added by switching into the virFirewall object into "rollback mode" and then re-calling the inverse of the exact virFirewallAddCmd*() APIs that had been called to add the original rules (ie. for each "iptables --insert" command, for rollback we would need to add a command with all arguments identical except that "--insert" would be replaced by "--delete"). Because nftables can't search for rules to remove by comparing all the arguments (it instead expects *only* a handle that is provided via stdout when the rule was originally added), we won't be able to follow the iptables method and manually construct the command to undo any given nft command by just duplicating all the args of the command (except the action). Instead we will need to be able to automatically create a rollback command at the time the rule-adding command is executed (e.g. an "nft delete rule" command that would include the rule handle returned in stdout by an "nft add rule" command). In order to make this happen, we need to be able to 1) learn whether the user of the virFirewall API desires this behavior (handled by a new transaction flag called VIR_FIREWALL_TRANSACTION_AUTO_ROLLBACK that can be retrieved with the new virFirewallTransactionGetFlags() API), and 2) add a new command to the current group's rollback command list (with the new virFirewallAddRollbackCmd()). We will actually use this capability in an upcoming patch. Signed-off-by: Laine Stump <laine@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 22:19:42 -04:00
/**
* virFirewallTransactionGetFlags:
* @firewall: the firewall to look at
*
* Returns the virFirewallTransactionFlags for the currently active
* group (transaction) in @firewall.
*/
static virFirewallTransactionFlags
util: new functions to support adding individual firewall rollback commands In the past virFirewall required all rollback commands for a group (those commands necessary to "undo" any rules that had been added in that group in case of a later failure) to be manually added by switching into the virFirewall object into "rollback mode" and then re-calling the inverse of the exact virFirewallAddCmd*() APIs that had been called to add the original rules (ie. for each "iptables --insert" command, for rollback we would need to add a command with all arguments identical except that "--insert" would be replaced by "--delete"). Because nftables can't search for rules to remove by comparing all the arguments (it instead expects *only* a handle that is provided via stdout when the rule was originally added), we won't be able to follow the iptables method and manually construct the command to undo any given nft command by just duplicating all the args of the command (except the action). Instead we will need to be able to automatically create a rollback command at the time the rule-adding command is executed (e.g. an "nft delete rule" command that would include the rule handle returned in stdout by an "nft add rule" command). In order to make this happen, we need to be able to 1) learn whether the user of the virFirewall API desires this behavior (handled by a new transaction flag called VIR_FIREWALL_TRANSACTION_AUTO_ROLLBACK that can be retrieved with the new virFirewallTransactionGetFlags() API), and 2) add a new command to the current group's rollback command list (with the new virFirewallAddRollbackCmd()). We will actually use this capability in an upcoming patch. Signed-off-by: Laine Stump <laine@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2024-04-19 22:19:42 -04:00
virFirewallTransactionGetFlags(virFirewall *firewall)
{
return firewall->groups[firewall->currentGroup]->actionFlags;
}
/**
* 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.
*/
void virFirewallStartRollback(virFirewall *firewall,
unsigned int flags)
{
virFirewallGroup *group;
VIR_FIREWALL_RETURN_IF_ERROR(firewall);
if (firewall->ngroups == 0) {
firewall->err = EINVAL;
return;
}
group = firewall->groups[firewall->ngroups-1];
group->rollbackFlags = flags;
group->addingRollback = true;
}
char *
virFirewallCmdToString(const char *cmd,
virFirewallCmd *fwCmd)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
size_t i;
virBufferAdd(&buf, cmd, -1);
for (i = 0; i < fwCmd->argsLen; i++) {
virBufferAddLit(&buf, " ");
virBufferAdd(&buf, fwCmd->args[i], -1);
}
return virBufferContentAndReset(&buf);
}
#define VIR_IPTABLES_ARG_IS_CREATE(arg) \
(STREQ(arg, "--insert") || STREQ(arg, "-I") || \
STREQ(arg, "--append") || STREQ(arg, "-A"))
static int
virFirewallCmdIptablesApply(virFirewall *firewall,
virFirewallCmd *fwCmd,
char **output)
{
const char *bin = virFirewallLayerCommandTypeToString(fwCmd->layer);
bool checkRollback = (virFirewallTransactionGetFlags(firewall) &
VIR_FIREWALL_TRANSACTION_AUTO_ROLLBACK);
bool needRollback = false;
g_autoptr(virCommand) cmd = NULL;
g_autofree char *cmdStr = NULL;
g_autofree char *error = NULL;
size_t i;
int status;
if (!bin) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unknown firewall layer %1$d"),
fwCmd->layer);
return -1;
}
cmd = virCommandNewArgList(bin, NULL);
/* 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;
}
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;
virCommandAddArg(cmd, fwCmd->args[i]);
}
cmdStr = virCommandToString(cmd, false);
VIR_INFO("Running firewall command '%s'", NULLSTR(cmdStr));
virCommandSetOutputBuffer(cmd, output);
virCommandSetErrorBuffer(cmd, &error);
if (virCommandRun(cmd, &status) < 0)
return -1;
if (status != 0) {
/* the command failed, decide whether or not to report it */
if (fwCmd->ignoreErrors) {
VIR_DEBUG("Ignoring error running command");
return 0;
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Failed to run firewall command %1$s: %2$s"),
NULLSTR(cmdStr), NULLSTR(error));
VIR_FREE(*output);
return -1;
}
}
/* 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));
}
return 0;
}
static int
virFirewallApplyCmd(virFirewall *firewall,
virFirewallCmd *fwCmd)
{
g_autofree char *output = NULL;
g_auto(GStrv) lines = NULL;
if (fwCmd->argsLen == 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Can't apply empty firewall command"));
return -1;
}
if (virFirewallCmdIptablesApply(firewall, fwCmd, &output) < 0)
return -1;
if (fwCmd->queryCB && output) {
if (!(lines = g_strsplit(output, "\n", -1)))
return -1;
VIR_DEBUG("Invoking query %p with '%s'", fwCmd->queryCB, output);
if (fwCmd->queryCB(firewall, fwCmd->layer, (const char *const *)lines, fwCmd->queryOpaque) < 0)
return -1;
if (firewall->err) {
virReportSystemError(firewall->err, "%s",
_("Unable to create firewall command"));
return -1;
}
}
return 0;
}
static int
virFirewallApplyGroup(virFirewall *firewall,
size_t idx)
{
virFirewallGroup *group = firewall->groups[idx];
size_t i;
VIR_INFO("Starting transaction for firewall=%p group=%p flags=0x%x",
firewall, group, group->actionFlags);
firewall->currentGroup = idx;
group->addingRollback = false;
for (i = 0; i < group->naction; i++) {
if (virFirewallApplyCmd(firewall, group->action[i]) < 0)
return -1;
}
return 0;
}
static void
virFirewallRollbackGroup(virFirewall *firewall,
size_t idx)
{
virFirewallGroup *group = firewall->groups[idx];
size_t i;
VIR_INFO("Starting rollback for group %p", group);
firewall->currentGroup = idx;
group->addingRollback = true;
for (i = 0; i < group->nrollback; i++)
ignore_value(virFirewallApplyCmd(firewall, group->rollback[i]));
}
int
virFirewallApply(virFirewall *firewall)
{
size_t i, j;
VIR_LOCK_GUARD lock = virLockGuardLock(&fwCmdLock);
if (!firewall || firewall->err) {
int err = EINVAL;
if (firewall)
err = firewall->err;
virReportSystemError(err, "%s", _("Unable to create firewall command"));
return -1;
}
VIR_DEBUG("Applying groups for %p", firewall);
for (i = 0; i < firewall->ngroups; i++) {
if (virFirewallApplyGroup(firewall, i) < 0) {
size_t first = i;
virErrorPtr saved_error;
VIR_DEBUG("Rolling back groups up to %zu for %p", i, firewall);
virErrorPreserveLast(&saved_error);
/*
* 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);
}
virErrorRestore(&saved_error);
VIR_DEBUG("Done rolling back groups for %p", firewall);
return -1;
}
}
VIR_DEBUG("Done applying groups for %p", firewall);
return 0;
}