The network and nwfilter drivers both have a need to update
firewall rules. The currently share no code for interacting
with iptables / firewalld. The nwfilter driver is fairly
tied to the concept of creating shell scripts to execute
which makes it very hard to port to talk to firewalld via
DBus APIs.

This patch introduces a virFirewallPtr object which is able
to represent a complete sequence of rule changes, with the
ability to have multiple transactional checkpoints with
rollbacks. By formally separating the definition of the rules
to be applied from the mechanism used to apply them, it is
also possible to write a firewall engine that uses firewalld
DBus APIs natively instead of via the slow firewalld-cmd.
---
 include/libvirt/virterror.h |   1 +
 po/POTFILES.in              |   1 +
 src/Makefile.am             |   2 +
 src/libvirt_private.syms    |  13 +
 src/util/virerror.c         |   1 +
 src/util/virfirewall.c      | 653 ++++++++++++++++++++++++++++++++++++++++++++
 src/util/virfirewall.h      |  93 +++++++
 src/util/virfirewallpriv.h  |  45 +++
 tests/Makefile.am           |   7 +
 tests/testutils.c           |  18 +-
 tests/virfirewalltest.c     | 619 +++++++++++++++++++++++++++++++++++++++++
 11 files changed, 1449 insertions(+), 4 deletions(-)
 create mode 100644 src/util/virfirewall.c
 create mode 100644 src/util/virfirewall.h
 create mode 100644 src/util/virfirewallpriv.h
 create mode 100644 tests/virfirewalltest.c

diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h
index 495c121..be90797 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 efac7b2..17c93f5 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -162,6 +162,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 a88b258..299fcdb 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -102,6 +102,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 f1607cd..0524569 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -1272,6 +1272,19 @@ virFileWriteStr;
 virFindFileInPath;
 
 
+# util/virfirewall.h
+virFirewallAddRule;
+virFirewallApply;
+virFirewallFree;
+virFirewallNew;
+virFirewallRuleAddArg;
+virFirewallRuleAddArgFormat;
+virFirewallRuleAddArgList;
+virFirewallRuleAddArgSet;
+virFirewallStartRollback;
+virFirewallStartTransaction;
+
+
 # util/virhash.h
 virHashAddEntry;
 virHashCreate;
diff --git a/src/util/virerror.c b/src/util/virerror.c
index 3eb4f5d..f64c4b2 100644
--- a/src/util/virerror.c
+++ b/src/util/virerror.c
@@ -126,6 +126,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 0000000..f1cf678
--- /dev/null
+++ b/src/util/virfirewall.c
@@ -0,0 +1,653 @@
+/*
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *    Daniel P. Berrange <berra...@redhat.com>
+ */
+
+#include <config.h>
+
+#define __VIR_FIREWALL_PRIV_H_ALLOW__
+
+#include <stdarg.h>
+
+#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
+
+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;
+
+    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;
+};
+
+static virFirewallBackend currentBackend;
+
+static int
+virFirewallValidateBackend(virFirewallBackend backend);
+
+static int
+virFirewallOnceInit(void)
+{
+    return virFirewallValidateBackend(VIR_FIREWALL_BACKEND_AUTOMATIC);
+}
+
+VIR_ONCE_GLOBAL_INIT(virFirewall)
+
+static int
+virFirewallValidateBackend(virFirewallBackend backend)
+{
+    VIR_DEBUG("Validating backend %d", backend);
+    if (backend == VIR_FIREWALL_BACKEND_AUTOMATIC ||
+        backend == VIR_FIREWALL_BACKEND_FIREWALLD) {
+        int rv = virDBusIsServiceRegistered(VIR_FIREWALL_FIREWALLD_SERVICE);
+        VIR_DEBUG("Firewalled is registered ? %d", rv);
+        if (rv < 0) {
+            if (rv == -2) {
+                if (backend == VIR_FIREWALL_BACKEND_FIREWALLD) {
+                    virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                                   _("firewalld backend requested, but 
firewalld service is not running"));
+                    return -1;
+                } else {
+                    backend = VIR_FIREWALL_BACKEND_DIRECT;
+                }
+            } else {
+                return -1;
+            }
+        }
+    }
+
+    if (backend == VIR_FIREWALL_BACKEND_DIRECT) {
+        if (!virFileIsExecutable(IPTABLES_PATH)) {
+            virReportSystemError(errno,
+                                 _("direct backend requested, but %s is not 
available"),
+                                 IPTABLES_PATH);
+            return -1;
+        }
+        if (!virFileIsExecutable(IP6TABLES_PATH)) {
+            virReportSystemError(errno,
+                                 _("direct backend requested, but %s is not 
available"),
+                                 IP6TABLES_PATH);
+            return -1;
+        }
+        if (!virFileIsExecutable(EBTABLES_PATH)) {
+            virReportSystemError(errno,
+                                 _("direct backend requested, but %s is not 
available"),
+                                 EBTABLES_PATH);
+            return -1;
+        }
+    }
+
+    VIR_DEBUG("Set backend to %d", backend);
+    currentBackend = backend;
+    return 0;
+}
+
+int
+virFirewallSetBackend(virFirewallBackend 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)          \
+    if (!firewall || firewall->err)                     \
+        return;
+
+#define VIR_FIREWALL_RULE_RETURN_IF_ERROR(firewall, ruel)\
+    if (!firewall || firewall->err || !rule)            \
+        return;
+
+#define VIR_FIREWALL_RETURN_NULL_IF_ERROR(firewall)     \
+    if (!firewall || firewall->err)                     \
+        return NULL;
+
+#define ADD_ARG(rule, str)                              \
+    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;
+
+/**
+ * virFirewallRuleAny:
+ * @firewall: add any type of rule to the firewall ruleset
+ * @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,
+                   ...)
+{
+    virFirewallGroupPtr group;
+    virFirewallRulePtr rule;
+    va_list list;
+    char *str;
+
+    VIR_FIREWALL_RETURN_NULL_IF_ERROR(firewall);
+
+    if (firewall->ngroups == 0) {
+        firewall->err = ENODATA;
+        return NULL;
+    }
+    group = firewall->groups[firewall->ngroups - 1];
+
+    va_start(list, layer);
+
+    if (VIR_ALLOC(rule) < 0)
+        goto no_memory;
+
+    rule->layer = layer;
+
+    while ((str = va_arg(list, char *)) != NULL) {
+        ADD_ARG(rule, str);
+    }
+
+    if (group->addingRollback) {
+        if (VIR_EXPAND_N(group->rollback,
+                         group->nrollback, 1) < 0)
+            goto no_memory;
+
+        group->rollback[group->nrollback - 1] = rule;
+    } else {
+        if (VIR_EXPAND_N(group->action,
+                         group->naction, 1) < 0)
+            goto no_memory;
+
+        group->action[group->naction - 1] = rule;
+    }
+
+    va_end(list);
+
+    return rule;
+
+no_memory:
+    firewall->err = ENOMEM;
+    virFirewallRuleFree(rule);
+    va_end(list);
+    return NULL;
+}
+
+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);
+
+    return;
+
+ no_memory:
+    firewall->err = ENOMEM;
+    va_end(list);
+}
+
+
+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);
+}
+
+
+/**
+ * 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;
+}
+
+/**
+ * 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 int
+virFirewallApplyRuleDirect(virFirewallRulePtr rule,
+                           bool ignoreErrors)
+{
+    size_t i;
+    const char *bin = virFirewallLayerCommandTypeToString(rule->layer);
+    virCommandPtr cmd = NULL;
+    int status;
+    int ret = -1;
+
+    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]);
+
+    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"),
+                           NULLSTR(args));
+            VIR_FREE(args);
+            goto cleanup;
+        }
+    }
+
+    ret = 0;
+ cleanup:
+    virCommandFree(cmd);
+    return ret;
+}
+
+static int
+virFirewallApplyRuleFirewallD(virFirewallRulePtr rule,
+                              bool ignoreErrors)
+{
+    const char *ipv = virFirewallLayerFirewallDTypeToString(rule->layer);
+    DBusConnection *sysbus = virDBusGetSystemBus();
+    int ret = -1;
+
+    if (!sysbus)
+        return -1;
+
+    if (!ipv) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("Unknown firewall layer %d"),
+                       rule->layer);
+        goto cleanup;
+    }
+
+    if (virDBusCallMethod(sysbus,
+                          NULL,
+                          VIR_FIREWALL_FIREWALLD_SERVICE,
+                          "/org/fedoraproject/FirewallD1",
+                          "org.fedoraproject.FirewallD1.direct",
+                          "passthrough",
+                          "sas",
+                          ipv,
+                          (int)rule->argsLen,
+                          rule->args) < 0) {
+        if (ignoreErrors) {
+            virResetLastError();
+            VIR_DEBUG("Ignoring error running command");
+        } else {
+            goto cleanup;
+        }
+    }
+
+    ret = 0;
+
+ cleanup:
+    return ret;
+}
+
+
+static int
+virFirewallApplyRule(virFirewallRulePtr rule,
+                     bool ignoreErrors)
+{
+    switch (currentBackend) {
+    case VIR_FIREWALL_BACKEND_DIRECT:
+        return virFirewallApplyRuleDirect(rule, ignoreErrors);
+    case VIR_FIREWALL_BACKEND_FIREWALLD:
+        return virFirewallApplyRuleFirewallD(rule, ignoreErrors);
+    default:
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("Unexpected firewall engine backend"));
+        return -1;
+    }
+}
+
+static int
+virFirewallApplyGroup(virFirewallGroupPtr group)
+{
+    size_t i;
+    bool ignoreErrors = (group->actionFlags & 
VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS);
+
+    VIR_DEBUG("Applying rules for group %p", group);
+    for (i = 0; i < group->naction; i++) {
+        if (virFirewallApplyRule(group->action[i],
+                                 ignoreErrors) < 0)
+            return -1;
+    }
+    return 0;
+}
+
+
+static void
+virFirewallRollbackGroup(virFirewallGroupPtr group)
+{
+    size_t i;
+
+    VIR_DEBUG("Rolling back rules for group %p", group);
+    for (i = 0; i < group->nrollback; i++) {
+        ignore_value(virFirewallApplyRule(group->rollback[i],
+                                          true));
+    }
+}
+
+
+int
+virFirewallApply(virFirewallPtr firewall)
+{
+    size_t i, j;
+
+    if (virFirewallInitialize() < 0)
+        return -1;
+
+    VIR_DEBUG("Applying groups for %p", firewall);
+    for (i = 0; i < firewall->ngroups; i++) {
+        if (virFirewallApplyGroup(firewall->groups[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->groups[j]);
+            }
+
+            virSetError(saved_error);
+            virFreeError(saved_error);
+            VIR_DEBUG("Done rolling back groups for %p", firewall);
+            return -1;
+        }
+    }
+    VIR_DEBUG("Done applying groups for %p", firewall);
+    return 0;
+}
diff --git a/src/util/virfirewall.h b/src/util/virfirewall.h
new file mode 100644
index 0000000..6a1cd92
--- /dev/null
+++ b/src/util/virfirewall.h
@@ -0,0 +1,93 @@
+/*
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *    Daniel P. Berrange <berra...@redhat.com>
+ */
+
+#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;
+
+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;
+
+
+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 0000000..359d0e4
--- /dev/null
+++ b/src/util/virfirewallpriv.h
@@ -0,0 +1,45 @@
+/*
+ * virfirewall.h: 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *    Daniel P. Berrange <berra...@redhat.com>
+ */
+
+#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 baf0483..63e869e 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -141,6 +141,7 @@ test_programs = virshtest sockettest \
        virpcitest \
        virendiantest \
        virfiletest \
+       virfirewalltest \
        viridentitytest \
        virkeycodetest \
        virlockspacetest \
@@ -943,6 +944,12 @@ 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)
+virfirewalltest_LDFLAGS = $(DRIVER_MODULE_LDFLAGS)
+
 jsontest_SOURCES = \
        jsontest.c testutils.h testutils.c
 jsontest_LDADD = $(LDADDS)
diff --git a/tests/testutils.c b/tests/testutils.c
index ede6239..a46823d 100644
--- a/tests/testutils.c
+++ b/tests/testutils.c
@@ -456,10 +456,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 0000000..144614c
--- /dev/null
+++ b/tests/virfirewalltest.c
@@ -0,0 +1,619 @@
+/*
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daniel P. Berrange <berra...@redhat.com>
+ */
+
+#include <config.h>
+
+#define __VIR_FIREWALL_PRIV_H_ALLOW__
+
+#include "testutils.h"
+#include "virbuffer.h"
+#include "vircommand.h"
+#include "virfirewallpriv.h"
+#include "virmock.h"
+
+#define VIR_FROM_THIS VIR_FROM_FIREWALL
+
+#include <dbus/dbus.h>
+
+static bool noFirewalld = true;
+
+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);
+
+    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 (!noFirewalld &&
+            !dbus_message_iter_append_basic(&sub,
+                                            DBUS_TYPE_STRING,
+                                            &svc2))
+            goto error;
+        dbus_message_iter_close_container(&iter, &sub);
+    } else {
+        reply = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_RETURN);
+    }
+
+    return reply;
+
+ error:
+    dbus_message_unref(reply);
+    return NULL;
+})
+
+
+static int
+testFirewallSingleGroup(const void *opaque ATTRIBUTE_UNUSED)
+{
+    virBuffer buf = VIR_BUFFER_INITIALIZER;
+    virFirewallPtr fw = NULL;
+    int ret = -1;
+    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";
+
+    virCommandSetDryRun(&buf, NULL, NULL);
+
+    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(&buf))
+        goto cleanup;
+
+    actual = virBufferContentAndReset(&buf);
+
+    if (STRNEQ_NULLABLE(expected, actual)) {
+        fprintf(stderr, "Unexected command execution");
+        virtTestDifference(stderr, expected, actual);
+    }
+    
+    ret = 0;
+ cleanup:
+    VIR_FREE(actual);
+    virCommandSetDryRun(NULL, NULL, NULL);
+    virFirewallFree(fw);
+    return ret;
+}
+
+
+static int
+testFirewallManyGroups(const void *opaque ATTRIBUTE_UNUSED)
+{
+    virBuffer buf = VIR_BUFFER_INITIALIZER;
+    virFirewallPtr fw = NULL;
+    int ret = -1;
+    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";
+
+    virCommandSetDryRun(&buf, NULL, NULL);
+
+    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(&buf))
+        goto cleanup;
+
+    actual = virBufferContentAndReset(&buf);
+
+    if (STRNEQ_NULLABLE(expected, actual)) {
+        fprintf(stderr, "Unexected command execution");
+        virtTestDifference(stderr, expected, actual);
+    }
+    
+    ret = 0;
+ cleanup:
+    VIR_FREE(actual);
+    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
+testFirewallIgnoreFail(const void *opaque ATTRIBUTE_UNUSED)
+{
+    virBuffer buf = VIR_BUFFER_INITIALIZER;
+    virFirewallPtr fw = NULL;
+    int ret = -1;
+    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";
+
+    virCommandSetDryRun(&buf, testFirewallRollbackHook, NULL);
+
+    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(&buf))
+        goto cleanup;
+
+    actual = virBufferContentAndReset(&buf);
+
+    if (STRNEQ_NULLABLE(expected, actual)) {
+        fprintf(stderr, "Unexected command execution");
+        virtTestDifference(stderr, expected, actual);
+    }
+    
+    ret = 0;
+ cleanup:
+    VIR_FREE(actual);
+    virCommandSetDryRun(NULL, NULL, NULL);
+    virFirewallFree(fw);
+    return ret;
+}
+
+
+static int
+testFirewallNoRollback(const void *opaque ATTRIBUTE_UNUSED)
+{
+    virBuffer buf = VIR_BUFFER_INITIALIZER;
+    virFirewallPtr fw = NULL;
+    int ret = -1;
+    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";
+
+    virCommandSetDryRun(&buf, testFirewallRollbackHook, NULL);
+
+    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 (virBufferError(&buf))
+        goto cleanup;
+
+    actual = virBufferContentAndReset(&buf);
+
+    if (STRNEQ_NULLABLE(expected, actual)) {
+        fprintf(stderr, "Unexected command execution");
+        virtTestDifference(stderr, expected, actual);
+    }
+    
+    ret = 0;
+ cleanup:
+    VIR_FREE(actual);
+    virCommandSetDryRun(NULL, NULL, NULL);
+    virFirewallFree(fw);
+    return ret;
+}
+
+static int
+testFirewallSingleRollback(const void *opaque ATTRIBUTE_UNUSED)
+{
+    virBuffer buf = VIR_BUFFER_INITIALIZER;
+    virFirewallPtr fw = NULL;
+    int ret = -1;
+    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";
+
+    virCommandSetDryRun(&buf, testFirewallRollbackHook, NULL);
+
+    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 (virBufferError(&buf))
+        goto cleanup;
+
+    actual = virBufferContentAndReset(&buf);
+
+    if (STRNEQ_NULLABLE(expected, actual)) {
+        fprintf(stderr, "Unexected command execution");
+        virtTestDifference(stderr, expected, actual);
+    }
+    
+    ret = 0;
+ cleanup:
+    VIR_FREE(actual);
+    virCommandSetDryRun(NULL, NULL, NULL);
+    virFirewallFree(fw);
+    return ret;
+}
+
+static int
+testFirewallManyRollback(const void *opaque ATTRIBUTE_UNUSED)
+{
+    virBuffer buf = VIR_BUFFER_INITIALIZER;
+    virFirewallPtr fw = NULL;
+    int ret = -1;
+    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";
+
+    virCommandSetDryRun(&buf, testFirewallRollbackHook, NULL);
+
+    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 (virBufferError(&buf))
+        goto cleanup;
+
+    actual = virBufferContentAndReset(&buf);
+
+    if (STRNEQ_NULLABLE(expected, actual)) {
+        fprintf(stderr, "Unexected command execution");
+        virtTestDifference(stderr, expected, actual);
+    }
+    
+    ret = 0;
+ cleanup:
+    VIR_FREE(actual);
+    virCommandSetDryRun(NULL, NULL, NULL);
+    virFirewallFree(fw);
+    return ret;
+}
+
+static int
+testFirewallChainedRollback(const void *opaque ATTRIBUTE_UNUSED)
+{
+    virBuffer buf = VIR_BUFFER_INITIALIZER;
+    virFirewallPtr fw = NULL;
+    int ret = -1;
+    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";
+
+    virCommandSetDryRun(&buf, testFirewallRollbackHook, NULL);
+
+    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 (virBufferError(&buf))
+        goto cleanup;
+
+    actual = virBufferContentAndReset(&buf);
+
+    if (STRNEQ_NULLABLE(expected, actual)) {
+        fprintf(stderr, "Unexected command execution");
+        virtTestDifference(stderr, expected, actual);
+    }
+    
+    ret = 0;
+ cleanup:
+    VIR_FREE(actual);
+    virCommandSetDryRun(NULL, NULL, NULL);
+    virFirewallFree(fw);
+    return ret;
+}
+
+static int
+mymain(void)
+{
+    int ret = 0;
+
+    if (virtTestRun("single group", testFirewallSingleGroup, NULL) < 0)
+        ret = -1;
+    if (virtTestRun("many groups", testFirewallManyGroups, NULL) < 0)
+        ret = -1;
+    if (virtTestRun("ignore fail", testFirewallIgnoreFail, NULL) < 0)
+        ret = -1;
+    if (virtTestRun("no rollback", testFirewallNoRollback, NULL) < 0)
+        ret = -1;
+    if (virtTestRun("single rollback", testFirewallSingleRollback, NULL) < 0)
+        ret = -1;
+    if (virtTestRun("many rollback", testFirewallManyRollback, NULL) < 0)
+        ret = -1;
+    if (virtTestRun("chained rollback", testFirewallChainedRollback, NULL) < 0)
+        ret = -1;
+
+    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
-- 
1.8.5.3

--
libvir-list mailing list
libvir-list@redhat.com
https://www.redhat.com/mailman/listinfo/libvir-list

Reply via email to