/*
* virfirewall.c: integration with firewalls
*
* Copyright (C) 2013 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
* .
*
* Authors:
* Daniel P. Berrange
*/
#include
#define __VIR_FIREWALL_PRIV_H_ALLOW__
#include
#include "viralloc.h"
#include "virfirewallpriv.h"
#include "virerror.h"
#include "virutil.h"
#include "virstring.h"
#include "vircommand.h"
#include "virlog.h"
#include "virdbus.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);
VIR_ENUM_DECL(virFirewallLayerFirewallD)
VIR_ENUM_IMPL(virFirewallLayerFirewallD, VIR_FIREWALL_LAYER_LAST,
"eb", "ipv4", "ipv6")
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 void
virFirewallCheckUpdateLock(bool *lockflag,
const char *const*args)
{
virCommandPtr cmd = virCommandNewArgs(args);
if (virCommandRun(cmd, NULL) < 0) {
VIR_INFO("locking not supported by %s", args[0]);
} else {
VIR_INFO("using locking for %s", args[0]);
*lockflag = true;
}
virCommandFree(cmd);
}
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,
};
virFirewallCheckUpdateLock(&iptablesUseLock,
iptablesArgs);
virFirewallCheckUpdateLock(&ip6tablesUseLock,
ip6tablesArgs);
virFirewallCheckUpdateLock(&ebtablesUseLock,
ebtablesArgs);
}
static int
virFirewallValidateBackend(virFirewallBackend backend)
{
VIR_DEBUG("Validating backend %d", backend);
#if WITH_DBUS
if (backend == VIR_FIREWALL_BACKEND_AUTOMATIC ||
backend == VIR_FIREWALL_BACKEND_FIREWALLD) {
int rv = virDBusIsServiceRegistered(VIR_FIREWALL_FIREWALLD_SERVICE);
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;
}
}
#else
if (backend == VIR_FIREWALL_BACKEND_AUTOMATIC) {
VIR_DEBUG("DBus support disabled, trying direct backend");
backend = VIR_FIREWALL_BACKEND_DIRECT;
} else if (backend == VIR_FIREWALL_BACKEND_FIREWALLD) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("firewalld firewall backend requested, but DBus support disabled"));
return -1;
}
#endif
if (backend == VIR_FIREWALL_BACKEND_DIRECT) {
const char *commands[] = {
IPTABLES_PATH, IP6TABLES_PATH, EBTABLES_PATH
};
size_t i;
for (i = 0; i < ARRAY_CARDINALITY(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; \
\
if (VIR_STRDUP(rule->args[rule->argsLen++], str) < 0) \
goto no_memory; \
} 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;
}
/**
* virFirewallAddRule:
* @firewall: firewall ruleset to add to
* @layer: the firewall layer to change
* @...: NULL terminated list of strings for the rule
*
* Add any type of rule to the firewall ruleset.
*
* Returns the new rule
*/
virFirewallRulePtr
virFirewallAddRule(virFirewallPtr firewall,
virFirewallLayer layer,
...)
{
virFirewallRulePtr rule;
va_list args;
va_start(args, layer);
rule = virFirewallAddRuleFullV(firewall, layer, false, NULL, NULL, args);
va_end(args);
return rule;
}
/**
* 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, ...)
{
char *arg;
va_list list;
VIR_FIREWALL_RULE_RETURN_IF_ERROR(firewall, rule);
va_start(list, fmt);
if (virVasprintf(&arg, fmt, list) < 0)
goto no_memory;
ADD_ARG(rule, arg);
va_end(list);
VIR_FREE(arg);
return;
no_memory:
firewall->err = ENOMEM;
va_end(list);
VIR_FREE(arg);
}
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);
virCommandPtr cmd = NULL;
int status;
int ret = -1;
char *error = NULL;
if (!bin) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unknown firewall layer %d"),
rule->layer);
goto cleanup;
}
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)
goto cleanup;
if (status != 0) {
if (ignoreErrors) {
VIR_DEBUG("Ignoring error running command");
} else {
char *args = virCommandToString(cmd);
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Failed to apply firewall rules %s: %s"),
NULLSTR(args), NULLSTR(error));
VIR_FREE(args);
VIR_FREE(*output);
goto cleanup;
}
}
ret = 0;
cleanup:
VIR_FREE(error);
virCommandFree(cmd);
return ret;
}
#ifdef WITH_DBUS
static int
virFirewallApplyRuleFirewallD(virFirewallRulePtr rule,
bool ignoreErrors,
char **output)
{
const char *ipv = virFirewallLayerFirewallDTypeToString(rule->layer);
DBusConnection *sysbus = virDBusGetSystemBus();
DBusMessage *reply = NULL;
DBusError error;
int ret = -1;
if (!sysbus)
return -1;
dbus_error_init(&error);
if (!ipv) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unknown firewall layer %d"),
rule->layer);
goto cleanup;
}
if (virDBusCallMethod(sysbus,
&reply,
&error,
VIR_FIREWALL_FIREWALLD_SERVICE,
"/org/fedoraproject/FirewallD1",
"org.fedoraproject.FirewallD1.direct",
"passthrough",
"sa&s",
ipv,
(int)rule->argsLen,
rule->args) < 0)
goto cleanup;
if (dbus_error_is_set(&error)) {
/*
* As of firewalld-0.3.9.3-1.fc20.noarch the name and
* message fields in the error look like
*
* name="org.freedesktop.DBus.Python.dbus.exceptions.DBusException"
* message="COMMAND_FAILED: '/sbin/iptables --table filter --delete
* INPUT --in-interface virbr0 --protocol udp --destination-port 53
* --jump ACCEPT' failed: iptables: Bad rule (does a matching rule
* exist in that chain?)."
*
* We'd like to only ignore DBus errors precisely related to the failure
* of iptables/ebtables commands. A well designed DBus interface would
* return specific named exceptions not the top level generic python dbus
* exception name. With this current scheme our only option is todo a
* sub-string match for 'COMMAND_FAILED' on the message. eg like
*
* if (ignoreErrors &&
* STREQ(error.name,
* "org.freedesktop.DBus.Python.dbus.exceptions.DBusException") &&
* STRPREFIX(error.message, "COMMAND_FAILED"))
* ...
*
* But this risks our error detecting code being broken if firewalld changes
* ever alter the message string, so we're avoiding doing that.
*/
if (ignoreErrors) {
VIR_DEBUG("Ignoring error '%s': '%s'",
error.name, error.message);
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unable to apply rule '%s'"),
error.message);
goto cleanup;
}
} else {
if (virDBusMessageRead(reply, "s", output) < 0)
goto cleanup;
}
ret = 0;
cleanup:
dbus_error_free(&error);
if (reply)
dbus_message_unref(reply);
return ret;
}
#endif
static int
virFirewallApplyRule(virFirewallPtr firewall,
virFirewallRulePtr rule,
bool ignoreErrors)
{
char *output = NULL;
char **lines = NULL;
int ret = -1;
char *str = virFirewallRuleToString(rule);
VIR_INFO("Applying rule '%s'", NULLSTR(str));
VIR_FREE(str);
if (rule->ignoreErrors)
ignoreErrors = rule->ignoreErrors;
switch (currentBackend) {
case VIR_FIREWALL_BACKEND_DIRECT:
if (virFirewallApplyRuleDirect(rule, ignoreErrors, &output) < 0)
return -1;
break;
#if WITH_DBUS
case VIR_FIREWALL_BACKEND_FIREWALLD:
if (virFirewallApplyRuleFirewallD(rule, ignoreErrors, &output) < 0)
return -1;
break;
#endif
default:
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unexpected firewall engine backend"));
return -1;
}
if (rule->queryCB && output) {
if (!(lines = virStringSplit(output, "\n", -1)))
goto cleanup;
VIR_DEBUG("Invoking query %p with '%s'", rule->queryCB, output);
if (rule->queryCB(firewall, (const char *const *)lines, rule->queryOpaque) < 0)
goto cleanup;
if (firewall->err == ENOMEM) {
virReportOOMError();
goto cleanup;
}
if (firewall->err) {
virReportSystemError(firewall->err, "%s",
_("Unable to create rule"));
goto cleanup;
}
}
ret = 0;
cleanup:
virStringFreeList(lines);
VIR_FREE(output);
return ret;
}
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=%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 (!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 upto %zu for %p", i, firewall);
size_t first = i;
virErrorPtr saved_error = virSaveLastError();
/*
* 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);
}
virSetError(saved_error);
virFreeError(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;
}