libvirt/src/util/virfirewall.c
Ján Tomko f331a9ef64 Remove virutil.h where possible
Historically, this file was a dump for most of our helper
functions and needed almost everywhere.
With the introduction of virfile.h and virstring.h,
and more importantly, virenum.h and the introduction
of GLib, that is no longer true.

Remove its include from C files that don't even use it.

Signed-off-by: Ján Tomko <jtomko@redhat.com>
Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com>
2020-02-24 23:15:49 +01:00

869 lines
22 KiB
C

/*
* 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>
#define LIBVIRT_VIRFIREWALLPRIV_H_ALLOW
#include "virfirewallpriv.h"
#include "virfirewalld.h"
#include "viralloc.h"
#include "virerror.h"
#include "virstring.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");
typedef struct _virFirewallGroup virFirewallGroup;
typedef virFirewallGroup *virFirewallGroupPtr;
VIR_ENUM_DECL(virFirewallLayerCommand);
VIR_ENUM_IMPL(virFirewallLayerCommand,
VIR_FIREWALL_LAYER_LAST,
EBTABLES_PATH,
IPTABLES_PATH,
IP6TABLES_PATH,
);
struct _virFirewallRule {
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;
virFirewallRulePtr *action;
size_t nrollback;
virFirewallRulePtr *rollback;
bool addingRollback;
};
struct _virFirewall {
int err;
size_t ngroups;
virFirewallGroupPtr *groups;
size_t currentGroup;
};
static virFirewallBackend currentBackend = VIR_FIREWALL_BACKEND_AUTOMATIC;
static virMutex ruleLock = VIR_MUTEX_INITIALIZER;
static int
virFirewallValidateBackend(virFirewallBackend backend);
static int
virFirewallOnceInit(void)
{
return virFirewallValidateBackend(currentBackend);
}
VIR_ONCE_GLOBAL_INIT(virFirewall);
static bool iptablesUseLock;
static bool ip6tablesUseLock;
static bool ebtablesUseLock;
static bool lockOverride; /* true to avoid lock probes */
void
virFirewallSetLockOverride(bool avoid)
{
lockOverride = avoid;
}
static void
virFirewallCheckUpdateLock(bool *lockflag,
const char *const*args)
{
int status; /* Ignore failed commands without logging them */
g_autoptr(virCommand) cmd = virCommandNewArgs(args);
if (virCommandRun(cmd, &status) < 0 || status) {
VIR_INFO("locking not supported by %s", args[0]);
} else {
VIR_INFO("using locking for %s", args[0]);
*lockflag = true;
}
}
static void
virFirewallCheckUpdateLocking(void)
{
const char *iptablesArgs[] = {
IPTABLES_PATH, "-w", "-L", "-n", NULL,
};
const char *ip6tablesArgs[] = {
IP6TABLES_PATH, "-w", "-L", "-n", NULL,
};
const char *ebtablesArgs[] = {
EBTABLES_PATH, "--concurrent", "-L", NULL,
};
if (lockOverride)
return;
virFirewallCheckUpdateLock(&iptablesUseLock,
iptablesArgs);
virFirewallCheckUpdateLock(&ip6tablesUseLock,
ip6tablesArgs);
virFirewallCheckUpdateLock(&ebtablesUseLock,
ebtablesArgs);
}
static int
virFirewallValidateBackend(virFirewallBackend backend)
{
VIR_DEBUG("Validating backend %d", backend);
if (backend == VIR_FIREWALL_BACKEND_AUTOMATIC ||
backend == VIR_FIREWALL_BACKEND_FIREWALLD) {
int rv = virFirewallDIsRegistered();
VIR_DEBUG("Firewalld is registered ? %d", rv);
if (rv < 0) {
if (rv == -2) {
if (backend == VIR_FIREWALL_BACKEND_FIREWALLD) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("firewalld firewall backend requested, but service is not running"));
return -1;
} else {
VIR_DEBUG("firewalld service not running, trying direct backend");
backend = VIR_FIREWALL_BACKEND_DIRECT;
}
} else {
return -1;
}
} else {
VIR_DEBUG("firewalld service running, using firewalld backend");
backend = VIR_FIREWALL_BACKEND_FIREWALLD;
}
}
if (backend == VIR_FIREWALL_BACKEND_DIRECT) {
const char *commands[] = {
IPTABLES_PATH, IP6TABLES_PATH, EBTABLES_PATH
};
size_t i;
for (i = 0; i < G_N_ELEMENTS(commands); i++) {
if (!virFileIsExecutable(commands[i])) {
virReportSystemError(errno,
_("direct firewall backend requested, but %s is not available"),
commands[i]);
return -1;
}
}
VIR_DEBUG("found iptables/ip6tables/ebtables, using direct backend");
}
currentBackend = backend;
virFirewallCheckUpdateLocking();
return 0;
}
int
virFirewallSetBackend(virFirewallBackend backend)
{
currentBackend = backend;
if (virFirewallInitialize() < 0)
return -1;
return virFirewallValidateBackend(backend);
}
static virFirewallGroupPtr
virFirewallGroupNew(void)
{
virFirewallGroupPtr group;
if (VIR_ALLOC(group) < 0)
return NULL;
return group;
}
/**
* 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
*/
virFirewallPtr virFirewallNew(void)
{
virFirewallPtr firewall;
if (virFirewallInitialize() < 0)
return NULL;
if (VIR_ALLOC(firewall) < 0)
return NULL;
return firewall;
}
static void
virFirewallRuleFree(virFirewallRulePtr rule)
{
size_t i;
if (!rule)
return;
for (i = 0; i < rule->argsLen; i++)
VIR_FREE(rule->args[i]);
VIR_FREE(rule->args);
VIR_FREE(rule);
}
static void
virFirewallGroupFree(virFirewallGroupPtr group)
{
size_t i;
if (!group)
return;
for (i = 0; i < group->naction; i++)
virFirewallRuleFree(group->action[i]);
VIR_FREE(group->action);
for (i = 0; i < group->nrollback; i++)
virFirewallRuleFree(group->rollback[i]);
VIR_FREE(group->rollback);
VIR_FREE(group);
}
/**
* virFirewallFree:
*
* Release all memory associated with the firewall
* ruleset
*/
void virFirewallFree(virFirewallPtr firewall)
{
size_t i;
if (!firewall)
return;
for (i = 0; i < firewall->ngroups; i++)
virFirewallGroupFree(firewall->groups[i]);
VIR_FREE(firewall->groups);
VIR_FREE(firewall);
}
#define VIR_FIREWALL_RETURN_IF_ERROR(firewall) \
do { \
if (!firewall || firewall->err) \
return; \
} while (0)
#define VIR_FIREWALL_RULE_RETURN_IF_ERROR(firewall, rule)\
do { \
if (!firewall || firewall->err || !rule) \
return; \
} while (0)
#define VIR_FIREWALL_RETURN_NULL_IF_ERROR(firewall) \
do { \
if (!firewall || firewall->err) \
return NULL; \
} while (0)
#define ADD_ARG(rule, str) \
do { \
if (VIR_RESIZE_N(rule->args, \
rule->argsAlloc, \
rule->argsLen, 1) < 0) \
goto no_memory; \
\
rule->args[rule->argsLen++] = g_strdup(str); \
} while (0)
static virFirewallRulePtr
virFirewallAddRuleFullV(virFirewallPtr firewall,
virFirewallLayer layer,
bool ignoreErrors,
virFirewallQueryCallback cb,
void *opaque,
va_list args)
{
virFirewallGroupPtr group;
virFirewallRulePtr rule;
char *str;
VIR_FIREWALL_RETURN_NULL_IF_ERROR(firewall);
if (firewall->ngroups == 0) {
firewall->err = EINVAL;
return NULL;
}
group = firewall->groups[firewall->currentGroup];
if (VIR_ALLOC(rule) < 0)
goto no_memory;
rule->layer = layer;
rule->queryCB = cb;
rule->queryOpaque = opaque;
rule->ignoreErrors = ignoreErrors;
switch (rule->layer) {
case VIR_FIREWALL_LAYER_ETHERNET:
if (ebtablesUseLock)
ADD_ARG(rule, "--concurrent");
break;
case VIR_FIREWALL_LAYER_IPV4:
if (iptablesUseLock)
ADD_ARG(rule, "-w");
break;
case VIR_FIREWALL_LAYER_IPV6:
if (ip6tablesUseLock)
ADD_ARG(rule, "-w");
break;
case VIR_FIREWALL_LAYER_LAST:
break;
}
while ((str = va_arg(args, char *)) != NULL)
ADD_ARG(rule, str);
if (group->addingRollback) {
if (VIR_APPEND_ELEMENT_COPY(group->rollback,
group->nrollback,
rule) < 0)
goto no_memory;
} else {
if (VIR_APPEND_ELEMENT_COPY(group->action,
group->naction,
rule) < 0)
goto no_memory;
}
return rule;
no_memory:
firewall->err = ENOMEM;
virFirewallRuleFree(rule);
return NULL;
}
/**
* virFirewallAddRuleFull:
* @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 virFirewallAddRule 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
*/
virFirewallRulePtr virFirewallAddRuleFull(virFirewallPtr firewall,
virFirewallLayer layer,
bool ignoreErrors,
virFirewallQueryCallback cb,
void *opaque,
...)
{
virFirewallRulePtr rule;
va_list args;
va_start(args, opaque);
rule = virFirewallAddRuleFullV(firewall, layer, ignoreErrors, cb, opaque, args);
va_end(args);
return rule;
}
/**
* virFirewallRemoveRule:
* @firewall: firewall ruleset to remove from
* @rule: the rule to remove
*
* Remove a rule from the current transaction
*/
void virFirewallRemoveRule(virFirewallPtr firewall,
virFirewallRulePtr rule)
{
size_t i;
virFirewallGroupPtr 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] == rule) {
VIR_DELETE_ELEMENT(group->rollback,
i,
group->nrollback);
virFirewallRuleFree(rule);
break;
}
}
} else {
for (i = 0; i < group->naction; i++) {
if (group->action[i] == rule) {
VIR_DELETE_ELEMENT(group->action,
i,
group->naction);
virFirewallRuleFree(rule);
return;
}
}
}
}
void virFirewallRuleAddArg(virFirewallPtr firewall,
virFirewallRulePtr rule,
const char *arg)
{
VIR_FIREWALL_RULE_RETURN_IF_ERROR(firewall, rule);
ADD_ARG(rule, arg);
return;
no_memory:
firewall->err = ENOMEM;
}
void virFirewallRuleAddArgFormat(virFirewallPtr firewall,
virFirewallRulePtr rule,
const char *fmt, ...)
{
g_autofree char *arg = NULL;
va_list list;
VIR_FIREWALL_RULE_RETURN_IF_ERROR(firewall, rule);
va_start(list, fmt);
arg = g_strdup_vprintf(fmt, list);
va_end(list);
ADD_ARG(rule, arg);
return;
no_memory:
firewall->err = ENOMEM;
}
void virFirewallRuleAddArgSet(virFirewallPtr firewall,
virFirewallRulePtr rule,
const char *const *args)
{
VIR_FIREWALL_RULE_RETURN_IF_ERROR(firewall, rule);
while (*args) {
ADD_ARG(rule, *args);
args++;
}
return;
no_memory:
firewall->err = ENOMEM;
}
void virFirewallRuleAddArgList(virFirewallPtr firewall,
virFirewallRulePtr rule,
...)
{
va_list list;
const char *str;
VIR_FIREWALL_RULE_RETURN_IF_ERROR(firewall, rule);
va_start(list, rule);
while ((str = va_arg(list, char *)) != NULL)
ADD_ARG(rule, str);
va_end(list);
return;
no_memory:
firewall->err = ENOMEM;
va_end(list);
}
size_t virFirewallRuleGetArgCount(virFirewallRulePtr rule)
{
if (!rule)
return 0;
return rule->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(virFirewallPtr firewall,
unsigned int flags)
{
virFirewallGroupPtr group;
VIR_FIREWALL_RETURN_IF_ERROR(firewall);
if (!(group = virFirewallGroupNew())) {
firewall->err = ENOMEM;
return;
}
group->actionFlags = flags;
if (VIR_EXPAND_N(firewall->groups,
firewall->ngroups, 1) < 0) {
firewall->err = ENOMEM;
virFirewallGroupFree(group);
return;
}
firewall->groups[firewall->ngroups - 1] = group;
firewall->currentGroup = firewall->ngroups - 1;
}
/**
* 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(virFirewallPtr firewall,
unsigned int flags)
{
virFirewallGroupPtr 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;
}
static char *
virFirewallRuleToString(virFirewallRulePtr rule)
{
const char *bin = virFirewallLayerCommandTypeToString(rule->layer);
virBuffer buf = VIR_BUFFER_INITIALIZER;
size_t i;
virBufferAdd(&buf, bin, -1);
for (i = 0; i < rule->argsLen; i++) {
virBufferAddLit(&buf, " ");
virBufferAdd(&buf, rule->args[i], -1);
}
return virBufferContentAndReset(&buf);
}
static int
virFirewallApplyRuleDirect(virFirewallRulePtr rule,
bool ignoreErrors,
char **output)
{
size_t i;
const char *bin = virFirewallLayerCommandTypeToString(rule->layer);
g_autoptr(virCommand) cmd = NULL;
int status;
g_autofree char *error = NULL;
if (!bin) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unknown firewall layer %d"),
rule->layer);
return -1;
}
cmd = virCommandNewArgList(bin, NULL);
for (i = 0; i < rule->argsLen; i++)
virCommandAddArg(cmd, rule->args[i]);
virCommandSetOutputBuffer(cmd, output);
virCommandSetErrorBuffer(cmd, &error);
if (virCommandRun(cmd, &status) < 0)
return -1;
if (status != 0) {
if (ignoreErrors) {
VIR_DEBUG("Ignoring error running command");
} else {
g_autofree char *args = virCommandToString(cmd, false);
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Failed to apply firewall rules %s: %s"),
NULLSTR(args), NULLSTR(error));
VIR_FREE(*output);
return -1;
}
}
return 0;
}
static int
virFirewallApplyRuleFirewallD(virFirewallRulePtr rule,
bool ignoreErrors,
char **output)
{
/* wrapper necessary because virFirewallRule is a private struct */
return virFirewallDApplyRule(rule->layer, rule->args, rule->argsLen, ignoreErrors, output);
}
static int
virFirewallApplyRule(virFirewallPtr firewall,
virFirewallRulePtr rule,
bool ignoreErrors)
{
g_autofree char *output = NULL;
g_autofree char *str = virFirewallRuleToString(rule);
VIR_AUTOSTRINGLIST lines = NULL;
VIR_INFO("Applying rule '%s'", NULLSTR(str));
if (rule->ignoreErrors)
ignoreErrors = rule->ignoreErrors;
switch (currentBackend) {
case VIR_FIREWALL_BACKEND_DIRECT:
if (virFirewallApplyRuleDirect(rule, ignoreErrors, &output) < 0)
return -1;
break;
case VIR_FIREWALL_BACKEND_FIREWALLD:
if (virFirewallApplyRuleFirewallD(rule, ignoreErrors, &output) < 0)
return -1;
break;
case VIR_FIREWALL_BACKEND_AUTOMATIC:
case VIR_FIREWALL_BACKEND_LAST:
default:
virReportEnumRangeError(virFirewallBackend, currentBackend);
return -1;
}
if (rule->queryCB && output) {
if (!(lines = virStringSplit(output, "\n", -1)))
return -1;
VIR_DEBUG("Invoking query %p with '%s'", rule->queryCB, output);
if (rule->queryCB(firewall, rule->layer, (const char *const *)lines, rule->queryOpaque) < 0)
return -1;
if (firewall->err == ENOMEM) {
virReportOOMError();
return -1;
}
if (firewall->err) {
virReportSystemError(firewall->err, "%s",
_("Unable to create rule"));
return -1;
}
}
return 0;
}
static int
virFirewallApplyGroup(virFirewallPtr firewall,
size_t idx)
{
virFirewallGroupPtr group = firewall->groups[idx];
bool ignoreErrors = (group->actionFlags & VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS);
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 (virFirewallApplyRule(firewall,
group->action[i],
ignoreErrors) < 0)
return -1;
}
return 0;
}
static void
virFirewallRollbackGroup(virFirewallPtr firewall,
size_t idx)
{
virFirewallGroupPtr 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(virFirewallApplyRule(firewall,
group->rollback[i],
true));
}
}
int
virFirewallApply(virFirewallPtr firewall)
{
size_t i, j;
int ret = -1;
virMutexLock(&ruleLock);
if (currentBackend == VIR_FIREWALL_BACKEND_AUTOMATIC) {
/* a specific backend should have been set when the firewall
* object was created. If not, it means none was found.
*/
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Failed to initialize a valid firewall backend"));
goto cleanup;
}
if (!firewall || firewall->err == ENOMEM) {
virReportOOMError();
goto cleanup;
}
if (firewall->err) {
virReportSystemError(firewall->err, "%s",
_("Unable to create rule"));
goto cleanup;
}
VIR_DEBUG("Applying groups for %p", firewall);
for (i = 0; i < firewall->ngroups; i++) {
if (virFirewallApplyGroup(firewall, i) < 0) {
VIR_DEBUG("Rolling back groups up to %zu for %p", i, firewall);
size_t first = i;
virErrorPtr saved_error;
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);
goto cleanup;
}
}
VIR_DEBUG("Done applying groups for %p", firewall);
ret = 0;
cleanup:
virMutexUnlock(&ruleLock);
return ret;
}