diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h
index 495c1212ab5ffa2e697fda3e95c82421ae3ee6c7..be90797c436f9cbfbf54defd9fde8a4dc1ad1002 100644
--- a/include/libvirt/virterror.h
+++ b/include/libvirt/virterror.h
@@ -122,6 +122,7 @@ typedef enum {
VIR_FROM_SYSTEMD = 56, /* Error from systemd code */
VIR_FROM_BHYVE = 57, /* Error from bhyve driver */
VIR_FROM_CRYPTO = 58, /* Error from crypto code */
+ VIR_FROM_FIREWALL = 59, /* Error from firewall */
# ifdef VIR_ENUM_SENTINELS
VIR_ERR_DOMAIN_LAST
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 122b85383fbe7a84077302c87f0971cb41bfd9fa..e35eb8272ed3d05b2959ba6e2c81963ec1c871cd 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -161,6 +161,7 @@ src/util/virdbus.c
src/util/virdnsmasq.c
src/util/vireventpoll.c
src/util/virfile.c
+src/util/virfirewall.c
src/util/virhash.c
src/util/virhook.c
src/util/virhostdev.c
diff --git a/src/Makefile.am b/src/Makefile.am
index a5a7ff320b7b463c86b61c485df859ed67ad5a73..0f295ed36eb18de7bf73f3805b4ab7c77eaa2a37 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -108,6 +108,8 @@ UTIL_SOURCES = \
util/virevent.c util/virevent.h \
util/vireventpoll.c util/vireventpoll.h \
util/virfile.c util/virfile.h \
+ util/virfirewall.c util/virfirewall.h \
+ util/virfirewallpriv.h \
util/virhash.c util/virhash.h \
util/virhashcode.c util/virhashcode.h \
util/virhook.c util/virhook.h \
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index c5ffe05b10efa870065b03aaf445bc6ef777de75..4c1ea3cacb07f82e191a21a481875a861fe2b2bd 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -1275,6 +1275,23 @@ virFileWriteStr;
virFindFileInPath;
+# util/virfirewall.h
+virFirewallAddRule;
+virFirewallAddRuleFull;
+virFirewallApply;
+virFirewallFree;
+virFirewallNew;
+virFirewallRemoveRule;
+virFirewallRuleAddArg;
+virFirewallRuleAddArgFormat;
+virFirewallRuleAddArgList;
+virFirewallRuleAddArgSet;
+virFirewallRuleGetArgCount;
+virFirewallSetBackend;
+virFirewallStartRollback;
+virFirewallStartTransaction;
+
+
# util/virhash.h
virHashAddEntry;
virHashCreate;
diff --git a/src/util/virerror.c b/src/util/virerror.c
index cbbaa83fcf7e6e736f031d448dc7f6f12c1c36dc..e0bc970e3217d14d9c526d3a98003cccb38cf83b 100644
--- a/src/util/virerror.c
+++ b/src/util/virerror.c
@@ -129,6 +129,7 @@ VIR_ENUM_IMPL(virErrorDomain, VIR_ERR_DOMAIN_LAST,
"Systemd",
"Bhyve",
"Crypto",
+ "Firewall",
)
diff --git a/src/util/virfirewall.c b/src/util/virfirewall.c
new file mode 100644
index 0000000000000000000000000000000000000000..cee0b1e375748ba19a3d6e0fe87e9544aa6c19f6
--- /dev/null
+++ b/src/util/virfirewall.c
@@ -0,0 +1,932 @@
+/*
+ * 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 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;
+ 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 (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 = ENODATA;
+ 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;
+
+ 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 = ENODATA;
+ 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 %p flags=%x",
+ 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 (virFirewallInitialize() < 0)
+ 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 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;
+}
diff --git a/src/util/virfirewall.h b/src/util/virfirewall.h
new file mode 100644
index 0000000000000000000000000000000000000000..5ca66fa5a300e536648047932f064e6a1c7ec1a7
--- /dev/null
+++ b/src/util/virfirewall.h
@@ -0,0 +1,109 @@
+ /*
+ * virfirewall.h: integration with firewalls
+ *
+ * Copyright (C) 2014 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
+ */
+
+#ifndef __VIR_FIREWALL_H__
+# define __VIR_FIREWALL_H__
+
+# include "internal.h"
+
+typedef struct _virFirewall virFirewall;
+typedef virFirewall *virFirewallPtr;
+
+typedef struct _virFirewallRule virFirewallRule;
+typedef virFirewallRule *virFirewallRulePtr;
+
+typedef enum {
+ VIR_FIREWALL_LAYER_ETHERNET,
+ VIR_FIREWALL_LAYER_IPV4,
+ VIR_FIREWALL_LAYER_IPV6,
+
+ VIR_FIREWALL_LAYER_LAST,
+} virFirewallLayer;
+
+virFirewallPtr virFirewallNew(void);
+
+void virFirewallFree(virFirewallPtr firewall);
+
+virFirewallRulePtr virFirewallAddRule(virFirewallPtr firewall,
+ virFirewallLayer layer,
+ ...)
+ ATTRIBUTE_SENTINEL;
+
+typedef int (*virFirewallQueryCallback)(virFirewallPtr firewall,
+ const char *const *lines,
+ void *opaque);
+
+virFirewallRulePtr virFirewallAddRuleFull(virFirewallPtr firewall,
+ virFirewallLayer layer,
+ bool ignoreErrors,
+ virFirewallQueryCallback cb,
+ void *opaque,
+ ...)
+ ATTRIBUTE_NONNULL(3) ATTRIBUTE_SENTINEL;
+
+void virFirewallRemoveRule(virFirewallPtr firewall,
+ virFirewallRulePtr rule);
+
+void virFirewallRuleAddArg(virFirewallPtr firewall,
+ virFirewallRulePtr rule,
+ const char *arg)
+ ATTRIBUTE_NONNULL(3);
+
+void virFirewallRuleAddArgFormat(virFirewallPtr firewall,
+ virFirewallRulePtr rule,
+ const char *fmt, ...)
+ ATTRIBUTE_NONNULL(3) ATTRIBUTE_FMT_PRINTF(3, 4);
+
+void virFirewallRuleAddArgSet(virFirewallPtr firewall,
+ virFirewallRulePtr rule,
+ const char *const *args)
+ ATTRIBUTE_NONNULL(3);
+
+void virFirewallRuleAddArgList(virFirewallPtr firewall,
+ virFirewallRulePtr rule,
+ ...)
+ ATTRIBUTE_SENTINEL;
+
+size_t virFirewallRuleGetArgCount(virFirewallRulePtr rule);
+
+typedef enum {
+ /* Ignore all errors when applying rules, so no
+ * rollback block will be required */
+ VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS = (1 << 0),
+} virFirewallTransactionFlags;
+
+void virFirewallStartTransaction(virFirewallPtr firewall,
+ unsigned int flags);
+
+typedef enum {
+ /* Execute previous rollback block before this
+ * one, to chain cleanup */
+ VIR_FIREWALL_ROLLBACK_INHERIT_PREVIOUS = (1 << 0),
+} virFirewallRollbackFlags;
+
+void virFirewallStartRollback(virFirewallPtr firewall,
+ unsigned int flags);
+
+int virFirewallApply(virFirewallPtr firewall);
+
+#endif /* __VIR_FIREWALL_H__ */
diff --git a/src/util/virfirewallpriv.h b/src/util/virfirewallpriv.h
new file mode 100644
index 0000000000000000000000000000000000000000..130aaa1b73f0be7a25738fac47b7453a101579b8
--- /dev/null
+++ b/src/util/virfirewallpriv.h
@@ -0,0 +1,45 @@
+/*
+ * virfirewallpriv.h: integration with firewalls private APIs
+ *
+ * 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
+ */
+
+#ifndef __VIR_FIREWALL_PRIV_H_ALLOW__
+# error "virfirewallpriv.h may only be included by virfirewall.c or test suites"
+#endif
+
+#ifndef __VIR_FIREWALL_PRIV_H__
+# define __VIR_FIREWALL_PRIV_H__
+
+# include "virfirewall.h"
+
+# define VIR_FIREWALL_FIREWALLD_SERVICE "org.fedoraproject.FirewallD1"
+
+typedef enum {
+ VIR_FIREWALL_BACKEND_AUTOMATIC,
+ VIR_FIREWALL_BACKEND_DIRECT,
+ VIR_FIREWALL_BACKEND_FIREWALLD,
+
+ VIR_FIREWALL_BACKEND_LAST,
+} virFirewallBackend;
+
+int virFirewallSetBackend(virFirewallBackend backend);
+
+#endif /* __VIR_FIREWALL_PRIV_H__ */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 4a3b8ac87c2108ffcf42a599eab385390c02c4ee..19c1efcabfb60e51feca4a77da04cb7cc578293e 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -152,6 +152,7 @@ test_programs = virshtest sockettest \
virpcitest \
virendiantest \
virfiletest \
+ virfirewalltest \
viriscsitest \
virkeycodetest \
virlockspacetest \
@@ -1006,6 +1007,11 @@ virfiletest_SOURCES = \
virfiletest.c testutils.h testutils.c
virfiletest_LDADD = $(LDADDS)
+virfirewalltest_SOURCES = \
+ virfirewalltest.c testutils.h testutils.c
+virfirewalltest_LDADD = $(LDADDS)
+virfirewalltest_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+
jsontest_SOURCES = \
jsontest.c testutils.h testutils.c
jsontest_LDADD = $(LDADDS)
diff --git a/tests/testutils.c b/tests/testutils.c
index 7d27582f5a43ffad4489d1f874ce15679efae9b6..feda22bd00873d9f31059e58f67eb099bced0920 100644
--- a/tests/testutils.c
+++ b/tests/testutils.c
@@ -459,10 +459,20 @@ int virtTestDifference(FILE *stream,
const char *expect,
const char *actual)
{
- const char *expectStart = expect;
- const char *expectEnd = expect + (strlen(expect)-1);
- const char *actualStart = actual;
- const char *actualEnd = actual + (strlen(actual)-1);
+ const char *expectStart;
+ const char *expectEnd;
+ const char *actualStart;
+ const char *actualEnd;
+
+ if (!expect)
+ expect = "";
+ if (!actual)
+ actual = "";
+
+ expectStart = expect;
+ expectEnd = expect + (strlen(expect)-1);
+ actualStart = actual;
+ actualEnd = actual + (strlen(actual)-1);
if (!virTestGetDebug())
return 0;
diff --git a/tests/virfirewalltest.c b/tests/virfirewalltest.c
new file mode 100644
index 0000000000000000000000000000000000000000..805fa44b7323129ca347e13d1e48e19178c2c49a
--- /dev/null
+++ b/tests/virfirewalltest.c
@@ -0,0 +1,1186 @@
+/*
+ * Copyright (C) 2013-2014 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
+ * .
+ *
+ * Author: Daniel P. Berrange
+ */
+
+#include
+
+#define __VIR_FIREWALL_PRIV_H_ALLOW__
+#define __VIR_COMMAND_PRIV_H_ALLOW__
+
+#include "testutils.h"
+#include "virbuffer.h"
+#include "vircommandpriv.h"
+#include "virfirewallpriv.h"
+#include "virmock.h"
+#include "virdbuspriv.h"
+
+#define VIR_FROM_THIS VIR_FROM_FIREWALL
+
+#if WITH_DBUS
+# include
+#endif
+
+static bool fwDisabled = true;
+static virBufferPtr fwBuf;
+static bool fwError;
+
+#define TEST_FILTER_TABLE_LIST \
+ "Chain INPUT (policy ACCEPT)\n" \
+ "target prot opt source destination\n" \
+ "\n" \
+ "Chain FORWARD (policy ACCEPT)\n" \
+ "target prot opt source destination\n" \
+ "\n" \
+ "Chain OUTPUT (policy ACCEPT)\n" \
+ "target prot opt source destination\n"
+
+#define TEST_NAT_TABLE_LIST \
+ "Chain PREROUTING (policy ACCEPT)\n" \
+ "target prot opt source destination\n" \
+ "\n" \
+ "Chain INPUT (policy ACCEPT)\n" \
+ "target prot opt source destination\n" \
+ "\n" \
+ "Chain OUTPUT (policy ACCEPT)\n" \
+ "target prot opt source destination\n" \
+ "\n" \
+ "Chain POSTROUTING (policy ACCEPT)\n" \
+ "target prot opt source destination\n"
+
+#if WITH_DBUS
+VIR_MOCK_IMPL_RET_ARGS(dbus_connection_send_with_reply_and_block,
+ DBusMessage *,
+ DBusConnection *, connection,
+ DBusMessage *, message,
+ int, timeout_milliseconds,
+ DBusError *, error)
+{
+ DBusMessage *reply = NULL;
+ const char *service = dbus_message_get_destination(message);
+ const char *member = dbus_message_get_member(message);
+ size_t i;
+ size_t nargs = 0;
+ char **args = NULL;
+ char *type = NULL;
+
+ VIR_MOCK_IMPL_INIT_REAL(dbus_connection_send_with_reply_and_block);
+
+ if (STREQ(service, "org.freedesktop.DBus") &&
+ STREQ(member, "ListNames")) {
+ const char *svc1 = "org.foo.bar.wizz";
+ const char *svc2 = VIR_FIREWALL_FIREWALLD_SERVICE;
+ DBusMessageIter iter;
+ DBusMessageIter sub;
+ reply = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_RETURN);
+ dbus_message_iter_init_append(reply, &iter);
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ "s", &sub);
+
+ if (!dbus_message_iter_append_basic(&sub,
+ DBUS_TYPE_STRING,
+ &svc1))
+ goto error;
+ if (!fwDisabled &&
+ !dbus_message_iter_append_basic(&sub,
+ DBUS_TYPE_STRING,
+ &svc2))
+ goto error;
+ dbus_message_iter_close_container(&iter, &sub);
+ } else if (STREQ(service, VIR_FIREWALL_FIREWALLD_SERVICE) &&
+ STREQ(member, "passthrough")) {
+ bool isAdd = false;
+ bool doError = false;
+
+ if (virDBusMessageDecode(message,
+ "sa&s",
+ &type,
+ &nargs,
+ &args) < 0)
+ goto error;
+
+ for (i = 0; i < nargs; i++) {
+ /* Fake failure on the command with this IP addr */
+ if (STREQ(args[i], "-A")) {
+ isAdd = true;
+ } else if (isAdd && STREQ(args[i], "192.168.122.255")) {
+ doError = true;
+ }
+ }
+
+ if (fwBuf) {
+ if (STREQ(type, "ipv4"))
+ virBufferAddLit(fwBuf, IPTABLES_PATH);
+ else if (STREQ(type, "ipv4"))
+ virBufferAddLit(fwBuf, IP6TABLES_PATH);
+ else
+ virBufferAddLit(fwBuf, EBTABLES_PATH);
+ }
+ for (i = 0; i < nargs; i++) {
+ if (fwBuf) {
+ virBufferAddLit(fwBuf, " ");
+ virBufferEscapeShell(fwBuf, args[i]);
+ }
+ }
+ if (fwBuf)
+ virBufferAddLit(fwBuf, "\n");
+ if (doError) {
+ dbus_set_error_const(error,
+ "org.firewalld.error",
+ "something bad happened");
+ } else {
+ if (nargs == 1 &&
+ STREQ(type, "ipv4") &&
+ STREQ(args[0], "-L")) {
+ if (virDBusCreateReply(&reply,
+ "s", TEST_FILTER_TABLE_LIST) < 0)
+ goto error;
+ } else if (nargs == 3 &&
+ STREQ(type, "ipv4") &&
+ STREQ(args[0], "-t") &&
+ STREQ(args[1], "nat") &&
+ STREQ(args[2], "-L")) {
+ if (virDBusCreateReply(&reply,
+ "s", TEST_NAT_TABLE_LIST) < 0)
+ goto error;
+ } else {
+ if (virDBusCreateReply(&reply,
+ "s", "success") < 0)
+ goto error;
+ }
+ }
+ } else {
+ reply = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_RETURN);
+ }
+
+ cleanup:
+ VIR_FREE(type);
+ for (i = 0; i < nargs; i++)
+ VIR_FREE(args[i]);
+ VIR_FREE(args);
+ return reply;
+
+ error:
+ if (reply)
+ dbus_message_unref(reply);
+ reply = NULL;
+ if (error && !dbus_error_is_set(error))
+ dbus_set_error_const(error,
+ "org.firewalld.error",
+ "something unexpected happened");
+
+ goto cleanup;
+}
+#endif
+
+struct testFirewallData {
+ virFirewallBackend tryBackend;
+ virFirewallBackend expectBackend;
+ bool fwDisabled;
+};
+
+static int
+testFirewallSingleGroup(const void *opaque)
+{
+ virBuffer cmdbuf = VIR_BUFFER_INITIALIZER;
+ virFirewallPtr fw = NULL;
+ int ret = -1;
+ const char *actual = NULL;
+ const char *expected =
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.1 --jump ACCEPT\n"
+ IPTABLES_PATH " -A INPUT --source-host '!192.168.122.1' --jump REJECT\n";
+ const struct testFirewallData *data = opaque;
+
+ fwDisabled = data->fwDisabled;
+ if (virFirewallSetBackend(data->tryBackend) < 0)
+ goto cleanup;
+
+ if (data->expectBackend == VIR_FIREWALL_BACKEND_DIRECT)
+ virCommandSetDryRun(&cmdbuf, NULL, NULL);
+ else
+ fwBuf = &cmdbuf;
+
+ fw = virFirewallNew();
+
+ virFirewallStartTransaction(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.1",
+ "--jump", "ACCEPT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "!192.168.122.1",
+ "--jump", "REJECT", NULL);
+
+ if (virFirewallApply(fw) < 0)
+ goto cleanup;
+
+ if (virBufferError(&cmdbuf))
+ goto cleanup;
+
+ actual = virBufferCurrentContent(&cmdbuf);
+
+ if (STRNEQ_NULLABLE(expected, actual)) {
+ fprintf(stderr, "Unexected command execution\n");
+ virtTestDifference(stderr, expected, actual);
+ goto cleanup;
+ }
+
+ ret = 0;
+ cleanup:
+ virBufferFreeAndReset(&cmdbuf);
+ fwBuf = NULL;
+ virCommandSetDryRun(NULL, NULL, NULL);
+ virFirewallFree(fw);
+ return ret;
+}
+
+
+static int
+testFirewallRemoveRule(const void *opaque)
+{
+ virBuffer cmdbuf = VIR_BUFFER_INITIALIZER;
+ virFirewallPtr fw = NULL;
+ int ret = -1;
+ const char *actual = NULL;
+ const char *expected =
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.1 --jump ACCEPT\n"
+ IPTABLES_PATH " -A INPUT --source-host '!192.168.122.1' --jump REJECT\n";
+ const struct testFirewallData *data = opaque;
+ virFirewallRulePtr fwrule;
+
+ fwDisabled = data->fwDisabled;
+ if (virFirewallSetBackend(data->tryBackend) < 0)
+ goto cleanup;
+
+ if (data->expectBackend == VIR_FIREWALL_BACKEND_DIRECT)
+ virCommandSetDryRun(&cmdbuf, NULL, NULL);
+ else
+ fwBuf = &cmdbuf;
+
+ fw = virFirewallNew();
+
+ virFirewallStartTransaction(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.1",
+ "--jump", "ACCEPT", NULL);
+
+ fwrule = virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT", NULL);
+ virFirewallRuleAddArg(fw, fwrule, "--source-host");
+ virFirewallRemoveRule(fw, fwrule);
+
+ fwrule = virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT", NULL);
+ virFirewallRuleAddArg(fw, fwrule, "--source-host");
+ virFirewallRuleAddArgFormat(fw, fwrule, "%s", "!192.168.122.1");
+ virFirewallRuleAddArgList(fw, fwrule, "--jump", "REJECT", NULL);
+
+ if (virFirewallApply(fw) < 0)
+ goto cleanup;
+
+ if (virBufferError(&cmdbuf))
+ goto cleanup;
+
+ actual = virBufferCurrentContent(&cmdbuf);
+
+ if (STRNEQ_NULLABLE(expected, actual)) {
+ fprintf(stderr, "Unexected command execution\n");
+ virtTestDifference(stderr, expected, actual);
+ goto cleanup;
+ }
+
+ ret = 0;
+ cleanup:
+ virBufferFreeAndReset(&cmdbuf);
+ fwBuf = NULL;
+ virCommandSetDryRun(NULL, NULL, NULL);
+ virFirewallFree(fw);
+ return ret;
+}
+
+
+static int
+testFirewallManyGroups(const void *opaque ATTRIBUTE_UNUSED)
+{
+ virBuffer cmdbuf = VIR_BUFFER_INITIALIZER;
+ virFirewallPtr fw = NULL;
+ int ret = -1;
+ const char *actual = NULL;
+ const char *expected =
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.1 --jump ACCEPT\n"
+ IPTABLES_PATH " -A INPUT --source-host '!192.168.122.1' --jump REJECT\n"
+ IPTABLES_PATH " -A OUTPUT --source-host 192.168.122.1 --jump ACCEPT\n"
+ IPTABLES_PATH " -A OUTPUT --jump DROP\n";
+ const struct testFirewallData *data = opaque;
+
+ fwDisabled = data->fwDisabled;
+ if (virFirewallSetBackend(data->tryBackend) < 0)
+ goto cleanup;
+
+ if (data->expectBackend == VIR_FIREWALL_BACKEND_DIRECT)
+ virCommandSetDryRun(&cmdbuf, NULL, NULL);
+ else
+ fwBuf = &cmdbuf;
+
+ fw = virFirewallNew();
+
+ virFirewallStartTransaction(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.1",
+ "--jump", "ACCEPT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "!192.168.122.1",
+ "--jump", "REJECT", NULL);
+
+ virFirewallStartTransaction(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "OUTPUT",
+ "--source-host", "192.168.122.1",
+ "--jump", "ACCEPT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "OUTPUT",
+ "--jump", "DROP", NULL);
+
+
+ if (virFirewallApply(fw) < 0)
+ goto cleanup;
+
+ if (virBufferError(&cmdbuf))
+ goto cleanup;
+
+ actual = virBufferCurrentContent(&cmdbuf);
+
+ if (STRNEQ_NULLABLE(expected, actual)) {
+ fprintf(stderr, "Unexected command execution\n");
+ virtTestDifference(stderr, expected, actual);
+ goto cleanup;
+ }
+
+ ret = 0;
+ cleanup:
+ virBufferFreeAndReset(&cmdbuf);
+ fwBuf = NULL;
+ virCommandSetDryRun(NULL, NULL, NULL);
+ virFirewallFree(fw);
+ return ret;
+}
+
+static void
+testFirewallRollbackHook(const char *const*args,
+ const char *const*env ATTRIBUTE_UNUSED,
+ const char *input ATTRIBUTE_UNUSED,
+ char **output ATTRIBUTE_UNUSED,
+ char **error ATTRIBUTE_UNUSED,
+ int *status,
+ void *opaque ATTRIBUTE_UNUSED)
+{
+ bool isAdd = false;
+ while (*args) {
+ /* Fake failure on the command with this IP addr */
+ if (STREQ(*args, "-A")) {
+ isAdd = true;
+ } else if (isAdd && STREQ(*args, "192.168.122.255")) {
+ *status = 127;
+ break;
+ }
+ args++;
+ }
+}
+
+static int
+testFirewallIgnoreFailGroup(const void *opaque ATTRIBUTE_UNUSED)
+{
+ virBuffer cmdbuf = VIR_BUFFER_INITIALIZER;
+ virFirewallPtr fw = NULL;
+ int ret = -1;
+ const char *actual = NULL;
+ const char *expected =
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.1 --jump ACCEPT\n"
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.255 --jump REJECT\n"
+ IPTABLES_PATH " -A OUTPUT --source-host 192.168.122.1 --jump ACCEPT\n"
+ IPTABLES_PATH " -A OUTPUT --jump DROP\n";
+ const struct testFirewallData *data = opaque;
+
+ fwDisabled = data->fwDisabled;
+ if (virFirewallSetBackend(data->tryBackend) < 0)
+ goto cleanup;
+
+ if (data->expectBackend == VIR_FIREWALL_BACKEND_DIRECT) {
+ virCommandSetDryRun(&cmdbuf, testFirewallRollbackHook, NULL);
+ } else {
+ fwBuf = &cmdbuf;
+ fwError = true;
+ }
+
+ fw = virFirewallNew();
+
+ virFirewallStartTransaction(fw, VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.1",
+ "--jump", "ACCEPT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.255",
+ "--jump", "REJECT", NULL);
+
+ virFirewallStartTransaction(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "OUTPUT",
+ "--source-host", "192.168.122.1",
+ "--jump", "ACCEPT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "OUTPUT",
+ "--jump", "DROP", NULL);
+
+
+ if (virFirewallApply(fw) < 0)
+ goto cleanup;
+
+ if (virBufferError(&cmdbuf))
+ goto cleanup;
+
+ actual = virBufferCurrentContent(&cmdbuf);
+
+ if (STRNEQ_NULLABLE(expected, actual)) {
+ fprintf(stderr, "Unexected command execution\n");
+ virtTestDifference(stderr, expected, actual);
+ goto cleanup;
+ }
+
+ ret = 0;
+ cleanup:
+ virBufferFreeAndReset(&cmdbuf);
+ fwBuf = NULL;
+ virCommandSetDryRun(NULL, NULL, NULL);
+ virFirewallFree(fw);
+ return ret;
+}
+
+
+static int
+testFirewallIgnoreFailRule(const void *opaque ATTRIBUTE_UNUSED)
+{
+ virBuffer cmdbuf = VIR_BUFFER_INITIALIZER;
+ virFirewallPtr fw = NULL;
+ int ret = -1;
+ const char *actual = NULL;
+ const char *expected =
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.1 --jump ACCEPT\n"
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.255 --jump REJECT\n"
+ IPTABLES_PATH " -A OUTPUT --source-host 192.168.122.1 --jump ACCEPT\n"
+ IPTABLES_PATH " -A OUTPUT --jump DROP\n";
+ const struct testFirewallData *data = opaque;
+
+ fwDisabled = data->fwDisabled;
+ if (virFirewallSetBackend(data->tryBackend) < 0)
+ goto cleanup;
+
+ if (data->expectBackend == VIR_FIREWALL_BACKEND_DIRECT) {
+ virCommandSetDryRun(&cmdbuf, testFirewallRollbackHook, NULL);
+ } else {
+ fwBuf = &cmdbuf;
+ fwError = true;
+ }
+
+ fw = virFirewallNew();
+
+ virFirewallStartTransaction(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.1",
+ "--jump", "ACCEPT", NULL);
+
+ virFirewallAddRuleFull(fw, VIR_FIREWALL_LAYER_IPV4,
+ true, NULL, NULL,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.255",
+ "--jump", "REJECT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "OUTPUT",
+ "--source-host", "192.168.122.1",
+ "--jump", "ACCEPT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "OUTPUT",
+ "--jump", "DROP", NULL);
+
+
+ if (virFirewallApply(fw) < 0)
+ goto cleanup;
+
+ if (virBufferError(&cmdbuf))
+ goto cleanup;
+
+ actual = virBufferCurrentContent(&cmdbuf);
+
+ if (STRNEQ_NULLABLE(expected, actual)) {
+ fprintf(stderr, "Unexected command execution\n");
+ virtTestDifference(stderr, expected, actual);
+ goto cleanup;
+ }
+
+ ret = 0;
+ cleanup:
+ virBufferFreeAndReset(&cmdbuf);
+ fwBuf = NULL;
+ virCommandSetDryRun(NULL, NULL, NULL);
+ virFirewallFree(fw);
+ return ret;
+}
+
+
+static int
+testFirewallNoRollback(const void *opaque ATTRIBUTE_UNUSED)
+{
+ virBuffer cmdbuf = VIR_BUFFER_INITIALIZER;
+ virFirewallPtr fw = NULL;
+ int ret = -1;
+ const char *actual = NULL;
+ const char *expected =
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.1 --jump ACCEPT\n"
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.255 --jump REJECT\n";
+ const struct testFirewallData *data = opaque;
+
+ fwDisabled = data->fwDisabled;
+ if (virFirewallSetBackend(data->tryBackend) < 0)
+ goto cleanup;
+
+ if (data->expectBackend == VIR_FIREWALL_BACKEND_DIRECT) {
+ virCommandSetDryRun(&cmdbuf, testFirewallRollbackHook, NULL);
+ } else {
+ fwBuf = &cmdbuf;
+ fwError = true;
+ }
+
+ fw = virFirewallNew();
+
+ virFirewallStartTransaction(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.1",
+ "--jump", "ACCEPT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.255",
+ "--jump", "REJECT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "!192.168.122.1",
+ "--jump", "REJECT", NULL);
+
+ if (virFirewallApply(fw) == 0) {
+ fprintf(stderr, "Firewall apply unexpectedly worked\n");
+ goto cleanup;
+ }
+
+ if (virtTestOOMActive())
+ goto cleanup;
+
+ if (virBufferError(&cmdbuf))
+ goto cleanup;
+
+ actual = virBufferCurrentContent(&cmdbuf);
+
+ if (STRNEQ_NULLABLE(expected, actual)) {
+ fprintf(stderr, "Unexected command execution\n");
+ virtTestDifference(stderr, expected, actual);
+ goto cleanup;
+ }
+
+ ret = 0;
+ cleanup:
+ virBufferFreeAndReset(&cmdbuf);
+ fwBuf = NULL;
+ virCommandSetDryRun(NULL, NULL, NULL);
+ virFirewallFree(fw);
+ return ret;
+}
+
+static int
+testFirewallSingleRollback(const void *opaque ATTRIBUTE_UNUSED)
+{
+ virBuffer cmdbuf = VIR_BUFFER_INITIALIZER;
+ virFirewallPtr fw = NULL;
+ int ret = -1;
+ const char *actual = NULL;
+ const char *expected =
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.1 --jump ACCEPT\n"
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.255 --jump REJECT\n"
+ IPTABLES_PATH " -D INPUT --source-host 192.168.122.1 --jump ACCEPT\n"
+ IPTABLES_PATH " -D INPUT --source-host 192.168.122.255 --jump REJECT\n"
+ IPTABLES_PATH " -D INPUT --source-host '!192.168.122.1' --jump REJECT\n";
+ const struct testFirewallData *data = opaque;
+
+ fwDisabled = data->fwDisabled;
+ if (virFirewallSetBackend(data->tryBackend) < 0)
+ goto cleanup;
+
+ if (data->expectBackend == VIR_FIREWALL_BACKEND_DIRECT) {
+ virCommandSetDryRun(&cmdbuf, testFirewallRollbackHook, NULL);
+ } else {
+ fwError = true;
+ fwBuf = &cmdbuf;
+ }
+
+ fw = virFirewallNew();
+
+ virFirewallStartTransaction(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.1",
+ "--jump", "ACCEPT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.255",
+ "--jump", "REJECT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "!192.168.122.1",
+ "--jump", "REJECT", NULL);
+
+ virFirewallStartRollback(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-D", "INPUT",
+ "--source-host", "192.168.122.1",
+ "--jump", "ACCEPT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-D", "INPUT",
+ "--source-host", "192.168.122.255",
+ "--jump", "REJECT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-D", "INPUT",
+ "--source-host", "!192.168.122.1",
+ "--jump", "REJECT", NULL);
+
+ if (virFirewallApply(fw) == 0) {
+ fprintf(stderr, "Firewall apply unexpectedly worked\n");
+ goto cleanup;
+ }
+
+ if (virtTestOOMActive())
+ goto cleanup;
+
+ if (virBufferError(&cmdbuf))
+ goto cleanup;
+
+ actual = virBufferCurrentContent(&cmdbuf);
+
+ if (STRNEQ_NULLABLE(expected, actual)) {
+ fprintf(stderr, "Unexected command execution\n");
+ virtTestDifference(stderr, expected, actual);
+ goto cleanup;
+ }
+
+ ret = 0;
+ cleanup:
+ virBufferFreeAndReset(&cmdbuf);
+ fwBuf = NULL;
+ virCommandSetDryRun(NULL, NULL, NULL);
+ virFirewallFree(fw);
+ return ret;
+}
+
+static int
+testFirewallManyRollback(const void *opaque ATTRIBUTE_UNUSED)
+{
+ virBuffer cmdbuf = VIR_BUFFER_INITIALIZER;
+ virFirewallPtr fw = NULL;
+ int ret = -1;
+ const char *actual = NULL;
+ const char *expected =
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.1 --jump ACCEPT\n"
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.255 --jump REJECT\n"
+ IPTABLES_PATH " -D INPUT --source-host 192.168.122.255 --jump REJECT\n"
+ IPTABLES_PATH " -D INPUT --source-host '!192.168.122.1' --jump REJECT\n";
+ const struct testFirewallData *data = opaque;
+
+ fwDisabled = data->fwDisabled;
+ if (virFirewallSetBackend(data->tryBackend) < 0)
+ goto cleanup;
+
+ if (data->expectBackend == VIR_FIREWALL_BACKEND_DIRECT) {
+ virCommandSetDryRun(&cmdbuf, testFirewallRollbackHook, NULL);
+ } else {
+ fwBuf = &cmdbuf;
+ fwError = true;
+ }
+
+ fw = virFirewallNew();
+
+ virFirewallStartTransaction(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.1",
+ "--jump", "ACCEPT", NULL);
+
+ virFirewallStartRollback(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-D", "INPUT",
+ "--source-host", "192.168.122.1",
+ "--jump", "ACCEPT", NULL);
+
+ virFirewallStartTransaction(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.255",
+ "--jump", "REJECT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "!192.168.122.1",
+ "--jump", "REJECT", NULL);
+
+ virFirewallStartRollback(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-D", "INPUT",
+ "--source-host", "192.168.122.255",
+ "--jump", "REJECT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-D", "INPUT",
+ "--source-host", "!192.168.122.1",
+ "--jump", "REJECT", NULL);
+
+ if (virFirewallApply(fw) == 0) {
+ fprintf(stderr, "Firewall apply unexpectedly worked\n");
+ goto cleanup;
+ }
+
+ if (virtTestOOMActive())
+ goto cleanup;
+
+ if (virBufferError(&cmdbuf))
+ goto cleanup;
+
+ actual = virBufferCurrentContent(&cmdbuf);
+
+ if (STRNEQ_NULLABLE(expected, actual)) {
+ fprintf(stderr, "Unexected command execution\n");
+ virtTestDifference(stderr, expected, actual);
+ goto cleanup;
+ }
+
+ ret = 0;
+ cleanup:
+ virBufferFreeAndReset(&cmdbuf);
+ fwBuf = NULL;
+ virCommandSetDryRun(NULL, NULL, NULL);
+ virFirewallFree(fw);
+ return ret;
+}
+
+static int
+testFirewallChainedRollback(const void *opaque ATTRIBUTE_UNUSED)
+{
+ virBuffer cmdbuf = VIR_BUFFER_INITIALIZER;
+ virFirewallPtr fw = NULL;
+ int ret = -1;
+ const char *actual = NULL;
+ const char *expected =
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.1 --jump ACCEPT\n"
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.127 --jump REJECT\n"
+ IPTABLES_PATH " -A INPUT --source-host '!192.168.122.1' --jump REJECT\n"
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.255 --jump REJECT\n"
+ IPTABLES_PATH " -D INPUT --source-host 192.168.122.127 --jump REJECT\n"
+ IPTABLES_PATH " -D INPUT --source-host '!192.168.122.1' --jump REJECT\n"
+ IPTABLES_PATH " -D INPUT --source-host 192.168.122.255 --jump REJECT\n"
+ IPTABLES_PATH " -D INPUT --source-host '!192.168.122.1' --jump REJECT\n";
+ const struct testFirewallData *data = opaque;
+
+ fwDisabled = data->fwDisabled;
+ if (virFirewallSetBackend(data->tryBackend) < 0)
+ goto cleanup;
+
+ if (data->expectBackend == VIR_FIREWALL_BACKEND_DIRECT) {
+ virCommandSetDryRun(&cmdbuf, testFirewallRollbackHook, NULL);
+ } else {
+ fwBuf = &cmdbuf;
+ fwError = true;
+ }
+
+ fw = virFirewallNew();
+
+ virFirewallStartTransaction(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.1",
+ "--jump", "ACCEPT", NULL);
+
+ virFirewallStartRollback(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-D", "INPUT",
+ "--source-host", "192.168.122.1",
+ "--jump", "ACCEPT", NULL);
+
+
+ virFirewallStartTransaction(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.127",
+ "--jump", "REJECT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "!192.168.122.1",
+ "--jump", "REJECT", NULL);
+
+ virFirewallStartRollback(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-D", "INPUT",
+ "--source-host", "192.168.122.127",
+ "--jump", "REJECT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-D", "INPUT",
+ "--source-host", "!192.168.122.1",
+ "--jump", "REJECT", NULL);
+
+
+ virFirewallStartTransaction(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.255",
+ "--jump", "REJECT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "!192.168.122.1",
+ "--jump", "REJECT", NULL);
+
+ virFirewallStartRollback(fw, VIR_FIREWALL_ROLLBACK_INHERIT_PREVIOUS);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-D", "INPUT",
+ "--source-host", "192.168.122.255",
+ "--jump", "REJECT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-D", "INPUT",
+ "--source-host", "!192.168.122.1",
+ "--jump", "REJECT", NULL);
+
+ if (virFirewallApply(fw) == 0) {
+ fprintf(stderr, "Firewall apply unexpectedly worked\n");
+ goto cleanup;
+ }
+
+ if (virtTestOOMActive())
+ goto cleanup;
+
+ if (virBufferError(&cmdbuf))
+ goto cleanup;
+
+ actual = virBufferCurrentContent(&cmdbuf);
+
+ if (STRNEQ_NULLABLE(expected, actual)) {
+ fprintf(stderr, "Unexected command execution\n");
+ virtTestDifference(stderr, expected, actual);
+ goto cleanup;
+ }
+
+ ret = 0;
+ cleanup:
+ virBufferFreeAndReset(&cmdbuf);
+ fwBuf = NULL;
+ virCommandSetDryRun(NULL, NULL, NULL);
+ virFirewallFree(fw);
+ return ret;
+}
+
+
+static const char *expectedLines[] = {
+ "Chain INPUT (policy ACCEPT)",
+ "target prot opt source destination",
+ "",
+ "Chain FORWARD (policy ACCEPT)",
+ "target prot opt source destination",
+ "",
+ "Chain OUTPUT (policy ACCEPT)",
+ "target prot opt source destination",
+ "",
+ "Chain PREROUTING (policy ACCEPT)",
+ "target prot opt source destination",
+ "",
+ "Chain INPUT (policy ACCEPT)",
+ "target prot opt source destination",
+ "",
+ "Chain OUTPUT (policy ACCEPT)",
+ "target prot opt source destination",
+ "",
+ "Chain POSTROUTING (policy ACCEPT)",
+ "target prot opt source destination",
+ "",
+};
+static size_t expectedLineNum;
+static bool expectedLineError;
+
+static void
+testFirewallQueryHook(const char *const*args,
+ const char *const*env ATTRIBUTE_UNUSED,
+ const char *input ATTRIBUTE_UNUSED,
+ char **output,
+ char **error ATTRIBUTE_UNUSED,
+ int *status,
+ void *opaque ATTRIBUTE_UNUSED)
+{
+ if (STREQ(args[0], IPTABLES_PATH) &&
+ STREQ(args[1], "-L")) {
+ if (VIR_STRDUP(*output, TEST_FILTER_TABLE_LIST) < 0)
+ *status = 127;
+ } else if (STREQ(args[0], IPTABLES_PATH) &&
+ STREQ(args[1], "-t") &&
+ STREQ(args[2], "nat") &&
+ STREQ(args[3], "-L")) {
+ if (VIR_STRDUP(*output, TEST_NAT_TABLE_LIST) < 0)
+ *status = 127;
+ }
+}
+
+
+static int
+testFirewallQueryCallback(virFirewallPtr fw,
+ const char *const *lines,
+ void *opaque ATTRIBUTE_UNUSED)
+{
+ size_t i;
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "!192.168.122.129",
+ "--jump", "REJECT", NULL);
+
+ for (i = 0; lines[i] != NULL; i++) {
+ if (expectedLineNum >= ARRAY_CARDINALITY(expectedLines)) {
+ expectedLineError = true;
+ break;
+ }
+ if (STRNEQ(expectedLines[expectedLineNum], lines[i])) {
+ fprintf(stderr, "Mismatch '%s' vs '%s' at %zu, %zu\n",
+ expectedLines[expectedLineNum], lines[i],
+ expectedLineNum, i);
+ expectedLineError = true;
+ break;
+ }
+ expectedLineNum++;
+ }
+ return 0;
+}
+
+static int
+testFirewallQuery(const void *opaque ATTRIBUTE_UNUSED)
+{
+ virBuffer cmdbuf = VIR_BUFFER_INITIALIZER;
+ virFirewallPtr fw = NULL;
+ int ret = -1;
+ const char *actual = NULL;
+ const char *expected =
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.1 --jump ACCEPT\n"
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.127 --jump REJECT\n"
+ IPTABLES_PATH " -L\n"
+ IPTABLES_PATH " -t nat -L\n"
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.130 --jump REJECT\n"
+ IPTABLES_PATH " -A INPUT --source-host '!192.168.122.129' --jump REJECT\n"
+ IPTABLES_PATH " -A INPUT --source-host '!192.168.122.129' --jump REJECT\n"
+ IPTABLES_PATH " -A INPUT --source-host 192.168.122.128 --jump REJECT\n"
+ IPTABLES_PATH " -A INPUT --source-host '!192.168.122.1' --jump REJECT\n";
+ const struct testFirewallData *data = opaque;
+
+ expectedLineNum = 0;
+ expectedLineError = false;
+ fwDisabled = data->fwDisabled;
+ if (virFirewallSetBackend(data->tryBackend) < 0)
+ goto cleanup;
+
+ if (data->expectBackend == VIR_FIREWALL_BACKEND_DIRECT) {
+ virCommandSetDryRun(&cmdbuf, testFirewallQueryHook, NULL);
+ } else {
+ fwBuf = &cmdbuf;
+ fwError = true;
+ }
+
+ fw = virFirewallNew();
+
+ virFirewallStartTransaction(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.1",
+ "--jump", "ACCEPT", NULL);
+
+ virFirewallStartTransaction(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.127",
+ "--jump", "REJECT", NULL);
+
+ virFirewallAddRuleFull(fw, VIR_FIREWALL_LAYER_IPV4,
+ false,
+ testFirewallQueryCallback,
+ NULL,
+ "-L", NULL);
+ virFirewallAddRuleFull(fw, VIR_FIREWALL_LAYER_IPV4,
+ false,
+ testFirewallQueryCallback,
+ NULL,
+ "-t", "nat", "-L", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.130",
+ "--jump", "REJECT", NULL);
+
+
+ virFirewallStartTransaction(fw, 0);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "192.168.122.128",
+ "--jump", "REJECT", NULL);
+
+ virFirewallAddRule(fw, VIR_FIREWALL_LAYER_IPV4,
+ "-A", "INPUT",
+ "--source-host", "!192.168.122.1",
+ "--jump", "REJECT", NULL);
+
+ if (virFirewallApply(fw) < 0)
+ goto cleanup;
+
+ if (virBufferError(&cmdbuf))
+ goto cleanup;
+
+ actual = virBufferCurrentContent(&cmdbuf);
+
+ if (expectedLineError) {
+ fprintf(stderr, "Got some unexpected query data\n");
+ goto cleanup;
+ }
+
+ if (STRNEQ_NULLABLE(expected, actual)) {
+ fprintf(stderr, "Unexected command execution\n");
+ virtTestDifference(stderr, expected, actual);
+ goto cleanup;
+ }
+
+ ret = 0;
+ cleanup:
+ virBufferFreeAndReset(&cmdbuf);
+ fwBuf = NULL;
+ virCommandSetDryRun(NULL, NULL, NULL);
+ virFirewallFree(fw);
+ return ret;
+}
+
+static int
+mymain(void)
+{
+ int ret = 0;
+
+#define RUN_TEST_DIRECT(name, method) \
+ do { \
+ struct testFirewallData data; \
+ data.tryBackend = VIR_FIREWALL_BACKEND_AUTOMATIC; \
+ data.expectBackend = VIR_FIREWALL_BACKEND_DIRECT; \
+ data.fwDisabled = true; \
+ if (virtTestRun(name " auto direct", method, &data) < 0) \
+ ret = -1; \
+ data.tryBackend = VIR_FIREWALL_BACKEND_DIRECT; \
+ data.expectBackend = VIR_FIREWALL_BACKEND_DIRECT; \
+ data.fwDisabled = true; \
+ if (virtTestRun(name " manual direct", method, &data) < 0) \
+ ret = -1; \
+ } while (0)
+
+#if WITH_DBUS
+# define RUN_TEST_FIREWALLD(name, method) \
+ do { \
+ struct testFirewallData data; \
+ data.tryBackend = VIR_FIREWALL_BACKEND_AUTOMATIC; \
+ data.expectBackend = VIR_FIREWALL_BACKEND_FIREWALLD; \
+ data.fwDisabled = false; \
+ if (virtTestRun(name " auto firewalld", method, &data) < 0) \
+ ret = -1; \
+ data.tryBackend = VIR_FIREWALL_BACKEND_FIREWALLD; \
+ data.expectBackend = VIR_FIREWALL_BACKEND_FIREWALLD; \
+ data.fwDisabled = false; \
+ if (virtTestRun(name " manual firewalld", method, &data) < 0) \
+ ret = -1; \
+ } while (0)
+
+# define RUN_TEST(name, method) \
+ RUN_TEST_DIRECT(name, method); \
+ RUN_TEST_FIREWALLD(name, method)
+#else /* ! WITH_DBUS */
+# define RUN_TEST(name, method) \
+ RUN_TEST_DIRECT(name, method)
+#endif /* ! WITH_DBUS */
+
+ RUN_TEST("single group", testFirewallSingleGroup);
+ RUN_TEST("remove rule", testFirewallRemoveRule);
+ RUN_TEST("many groups", testFirewallManyGroups);
+ RUN_TEST("ignore fail group", testFirewallIgnoreFailGroup);
+ RUN_TEST("ignore fail rule", testFirewallIgnoreFailRule);
+ RUN_TEST("no rollback", testFirewallNoRollback);
+ RUN_TEST("single rollback", testFirewallSingleRollback);
+ RUN_TEST("many rollback", testFirewallManyRollback);
+ RUN_TEST("chained rollback", testFirewallChainedRollback);
+ RUN_TEST("query transaction", testFirewallQuery);
+
+ return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+#if WITH_DBUS
+VIRT_TEST_MAIN_PRELOAD(mymain, abs_builddir "/.libs/virmockdbus.so")
+#else
+VIRT_TEST_MAIN(mymain)
+#endif