[libvirt] [PATCH v2 0/7] network: fix networking for firewalld+nftables

Resolves: https://bugzilla.redhat.com/1638342 Creates-and-Resolves: https://bugzilla.redhat.com/1650320 V1: https://www.redhat.com/archives/libvir-list/2019-January/msg00227.html The detailed explanation of this is in Patch 4/7 and 5/7. Basically, when firewalld enables their new nftables backend, libvirt virtual networks lose all ability to forward packets from guests out to the physical network, and can only communicate with the host itself as much as firewalld's "public" zone will allow (which isn't much, and doesn't include DHCP or DNS). I *think* I've addressed everything in Daniel and John's review comments. In particular, I've made installation of the libvirt zone file optional, and if the libvirt zone is missing, I only log an error if the firewalld backend is set to nftables. Laine Stump (7): configure: change HAVE_FIREWALLD to WITH_FIREWALLD util: move all firewalld-specific stuff into its own files util: new virFirewallD APIs + docs configure: selectively install a firewalld 'libvirt' zone network: set firewalld zone of bridges to "libvirt" zone when appropriate network: allow configuring firewalld zone for virtual network bridge device docs: update news.xml for firewalld zone changes configure.ac | 3 + docs/firewall.html.in | 38 +++ docs/formatnetwork.html.in | 17 + docs/news.xml | 40 +++ docs/schemas/basictypes.rng | 6 + docs/schemas/network.rng | 6 + include/libvirt/virterror.h | 1 + libvirt.spec.in | 31 ++ m4/virt-firewalld-zone.m4 | 45 +++ m4/virt-firewalld.m4 | 4 +- src/conf/network_conf.c | 14 +- src/conf/network_conf.h | 1 + src/libvirt_private.syms | 10 + src/network/Makefile.inc.am | 10 +- src/network/bridge_driver.c | 6 +- src/network/bridge_driver_linux.c | 67 ++++ src/network/libvirt.zone | 23 ++ src/nwfilter/nwfilter_driver.c | 6 +- src/util/Makefile.inc.am | 3 + src/util/virerror.c | 3 +- src/util/virfirewall.c | 86 +---- src/util/virfirewalld.c | 373 +++++++++++++++++++++ src/util/virfirewalld.h | 46 +++ src/util/virfirewalldpriv.h | 30 ++ src/util/virfirewallpriv.h | 2 - tests/networkxml2xmlin/routed-network.xml | 2 +- tests/networkxml2xmlout/routed-network.xml | 2 +- tests/virfirewalltest.c | 2 + 28 files changed, 779 insertions(+), 98 deletions(-) create mode 100644 m4/virt-firewalld-zone.m4 create mode 100644 src/network/libvirt.zone create mode 100644 src/util/virfirewalld.c create mode 100644 src/util/virfirewalld.h create mode 100644 src/util/virfirewalldpriv.h -- 2.20.1

Support for firewalld is a feature that can be selectively enabled or disabled (using --with-firewalld/--without-firewalld), not merely something that must be accounted for in the code if it is present with no exceptions. It is more consistent with other usage in libvirt to use WITH_FIREWALLD rather than HAVE_FIREWALLD. Signed-off-by: Laine Stump <laine@laine.org> --- New patch in V2 (NB, I already pushed patch 1 from V1, as it was ACKed, and not directly related to the rest of the series) m4/virt-firewalld.m4 | 4 ++-- src/network/bridge_driver.c | 6 +++--- src/nwfilter/nwfilter_driver.c | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/m4/virt-firewalld.m4 b/m4/virt-firewalld.m4 index 08d2ff83d6..89efa47589 100644 --- a/m4/virt-firewalld.m4 +++ b/m4/virt-firewalld.m4 @@ -32,10 +32,10 @@ AC_DEFUN([LIBVIRT_CHECK_FIREWALLD], [ if test "x$with_dbus" != "xyes" ; then AC_MSG_ERROR([You must have dbus enabled for firewalld support]) fi - AC_DEFINE_UNQUOTED([HAVE_FIREWALLD], [1], [whether firewalld support is enabled]) + AC_DEFINE_UNQUOTED([WITH_FIREWALLD], [1], [whether firewalld support is enabled]) fi - AM_CONDITIONAL([HAVE_FIREWALLD], [test "x$with_firewalld" != "xno"]) + AM_CONDITIONAL([WITH_FIREWALLD], [test "x$with_firewalld" != "xno"]) ]) AC_DEFUN([LIBVIRT_RESULT_FIREWALLD], [ diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index 20a0f65e65..238ea2e68e 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -536,7 +536,7 @@ networkAutostartConfig(virNetworkObjPtr obj, } -#if HAVE_FIREWALLD +#ifdef WITH_FIREWALLD static DBusHandlerResult firewalld_dbus_filter_bridge(DBusConnection *connection ATTRIBUTE_UNUSED, DBusMessage *message, @@ -571,7 +571,7 @@ networkStateInitialize(bool privileged, int ret = -1; char *configdir = NULL; char *rundir = NULL; -#ifdef HAVE_FIREWALLD +#ifdef WITH_FIREWALLD DBusConnection *sysbus = NULL; #endif @@ -662,7 +662,7 @@ networkStateInitialize(bool privileged, network_driver->networkEventState = virObjectEventStateNew(); -#ifdef HAVE_FIREWALLD +#ifdef WITH_FIREWALLD if (!(sysbus = virDBusGetSystemBus())) { VIR_WARN("DBus not available, disabling firewalld support " "in bridge_network_driver: %s", virGetLastErrorMessage()); diff --git a/src/nwfilter/nwfilter_driver.c b/src/nwfilter/nwfilter_driver.c index 0e29d3e19e..fdfc6f48fa 100644 --- a/src/nwfilter/nwfilter_driver.c +++ b/src/nwfilter/nwfilter_driver.c @@ -76,7 +76,7 @@ static void nwfilterDriverUnlock(void) virMutexUnlock(&driver->lock); } -#if HAVE_FIREWALLD +#ifdef WITH_FIREWALLD static DBusHandlerResult nwfilterFirewalldDBusFilter(DBusConnection *connection ATTRIBUTE_UNUSED, @@ -145,7 +145,7 @@ nwfilterDriverInstallDBusMatches(DBusConnection *sysbus) return ret; } -#else /* HAVE_FIREWALLD */ +#else /* WITH_FIREWALLD */ static void nwfilterDriverRemoveDBusMatches(void) @@ -158,7 +158,7 @@ nwfilterDriverInstallDBusMatches(DBusConnection *sysbus ATTRIBUTE_UNUSED) return 0; } -#endif /* HAVE_FIREWALLD */ +#endif /* WITH_FIREWALLD */ static int virNWFilterTriggerRebuildImpl(void *opaque) -- 2.20.1

On Thu, Jan 31, 2019 at 08:24:52PM -0500, Laine Stump wrote:
Support for firewalld is a feature that can be selectively enabled or disabled (using --with-firewalld/--without-firewalld), not merely something that must be accounted for in the code if it is present with no exceptions. It is more consistent with other usage in libvirt to use WITH_FIREWALLD rather than HAVE_FIREWALLD.
Signed-off-by: Laine Stump <laine@laine.org> ---
New patch in V2 (NB, I already pushed patch 1 from V1, as it was ACKed, and not directly related to the rest of the series)
m4/virt-firewalld.m4 | 4 ++-- src/network/bridge_driver.c | 6 +++--- src/nwfilter/nwfilter_driver.c | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-)
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

In preparation for adding several other firewalld-specific functions, separate the code that's unique to firewalld from the more-generic "firewall" file. Signed-off-by: Laine Stump <laine@laine.org> --- Change from V1: define VIR_FIREWALL_FIREWALLD_SERVICE in virfirewalldpriv.h, since it should only be used by virfirewalld.c and unit test programs. (no, I haven't done anything about the awkward error checking that was already existing in the code I moved - not only is that a separate issue, but there no existing way to fix it anyway - that would take changes to firewalld) include/libvirt/virterror.h | 1 + src/libvirt_private.syms | 5 ++ src/util/Makefile.inc.am | 3 + src/util/virerror.c | 3 +- src/util/virfirewall.c | 86 +------------------- src/util/virfirewalld.c | 151 ++++++++++++++++++++++++++++++++++++ src/util/virfirewalld.h | 33 ++++++++ src/util/virfirewalldpriv.h | 30 +++++++ src/util/virfirewallpriv.h | 2 - tests/virfirewalltest.c | 2 + 10 files changed, 231 insertions(+), 85 deletions(-) create mode 100644 src/util/virfirewalld.c create mode 100644 src/util/virfirewalld.h create mode 100644 src/util/virfirewalldpriv.h diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h index fbbe2d5624..3c19ff5e2e 100644 --- a/include/libvirt/virterror.h +++ b/include/libvirt/virterror.h @@ -131,6 +131,7 @@ typedef enum { VIR_FROM_PERF = 65, /* Error from perf */ VIR_FROM_LIBSSH = 66, /* Error from libssh connection transport */ VIR_FROM_RESCTRL = 67, /* Error from resource control */ + VIR_FROM_FIREWALLD = 68, /* Error from firewalld */ # ifdef VIR_ENUM_SENTINELS VIR_ERR_DOMAIN_LAST diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index e53ae0dbeb..d52e2412fa 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1921,6 +1921,11 @@ virFirewallStartRollback; virFirewallStartTransaction; +# util/virfirewalld.h +virFirewallDApplyRule; +virFirewallDIsRegistered; + + # util/virfirmware.h virFirmwareFreeList; virFirmwareParse; diff --git a/src/util/Makefile.inc.am b/src/util/Makefile.inc.am index 4295babac3..aa5c6cbe03 100644 --- a/src/util/Makefile.inc.am +++ b/src/util/Makefile.inc.am @@ -64,6 +64,9 @@ UTIL_SOURCES = \ util/virfirewall.c \ util/virfirewall.h \ util/virfirewallpriv.h \ + util/virfirewalld.c \ + util/virfirewalld.h \ + util/virfirewalldpriv.h \ util/virfirmware.c \ util/virfirmware.h \ util/virgettext.c \ diff --git a/src/util/virerror.c b/src/util/virerror.c index 61b47d2be0..740f3b84b3 100644 --- a/src/util/virerror.c +++ b/src/util/virerror.c @@ -138,7 +138,8 @@ VIR_ENUM_IMPL(virErrorDomain, VIR_ERR_DOMAIN_LAST, "Perf", /* 65 */ "Libssh transport layer", "Resource control", - ) + "FirewallD", + ); /* diff --git a/src/util/virfirewall.c b/src/util/virfirewall.c index 0ed54d6228..7582ce5b5d 100644 --- a/src/util/virfirewall.c +++ b/src/util/virfirewall.c @@ -24,12 +24,12 @@ #define LIBVIRT_VIRFIREWALLPRIV_H_ALLOW #include "virfirewallpriv.h" +#include "virfirewalld.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" @@ -46,11 +46,6 @@ VIR_ENUM_IMPL(virFirewallLayerCommand, VIR_FIREWALL_LAYER_LAST, IPTABLES_PATH, IP6TABLES_PATH); -VIR_ENUM_DECL(virFirewallLayerFirewallD) -VIR_ENUM_IMPL(virFirewallLayerFirewallD, VIR_FIREWALL_LAYER_LAST, - "eb", "ipv4", "ipv6") - - struct _virFirewallRule { virFirewallLayer layer; @@ -152,7 +147,7 @@ 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); + int rv = virFirewallDIsRegistered(); VIR_DEBUG("Firewalld is registered ? %d", rv); if (rv < 0) { @@ -712,81 +707,8 @@ virFirewallApplyRuleFirewallD(virFirewallRulePtr rule, bool ignoreErrors, char **output) { - const char *ipv = virFirewallLayerFirewallDTypeToString(rule->layer); - DBusConnection *sysbus = virDBusGetSystemBus(); - DBusMessage *reply = NULL; - virError error; - int ret = -1; - - if (!sysbus) - return -1; - - memset(&error, 0, sizeof(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 (error.level == VIR_ERR_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.str1, error.message); - } else { - virReportErrorObject(&error); - goto cleanup; - } - } else { - if (virDBusMessageRead(reply, "s", output) < 0) - goto cleanup; - } - - ret = 0; - - cleanup: - virResetError(&error); - virDBusMessageUnref(reply); - return ret; + /* wrapper necessary because virFirewallRule is a private struct */ + return virFirewallDApplyRule(rule->layer, rule->args, rule->argsLen, ignoreErrors, output); } static int diff --git a/src/util/virfirewalld.c b/src/util/virfirewalld.c new file mode 100644 index 0000000000..f27ec9c124 --- /dev/null +++ b/src/util/virfirewalld.c @@ -0,0 +1,151 @@ +/* + * virfirewalld.c: support for firewalld (https://firewalld.org) + * + * Copyright (C) 2019 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <stdarg.h> + +#include "virfirewall.h" +#include "virfirewalld.h" +#define LIBVIRT_VIRFIREWALLDPRIV_H_ALLOW +#include "virfirewalldpriv.h" +#include "virerror.h" +#include "virutil.h" +#include "virlog.h" +#include "virdbus.h" + +#define VIR_FROM_THIS VIR_FROM_FIREWALLD + +VIR_LOG_INIT("util.firewalld"); + +/* used to convert virFirewallLayer enum values to strings + * understood by the firewalld.direct "passthrough" method + */ +VIR_ENUM_DECL(virFirewallLayerFirewallD); +VIR_ENUM_IMPL(virFirewallLayerFirewallD, VIR_FIREWALL_LAYER_LAST, + "eb", + "ipv4", + "ipv6", + ); + + +/** + * virFirewallDIsRegistered: + * + * Returns 0 if service is registered, -1 on fatal error, or -2 if service is not registered + */ +int +virFirewallDIsRegistered(void) +{ + return virDBusIsServiceRegistered(VIR_FIREWALL_FIREWALLD_SERVICE); +} + + +/** + * virFirewallDApplyRule: + * @layer: which layer to apply the rule to + * @args: list of args to send to this layer's passthrough command. + * @argsLen: number of items in @args + * @ignoreErrors: true to suppress logging of errors and return success + * false to log errors and return actual status + * @output: output of the direct passthrough command, if it was successful + */ +int +virFirewallDApplyRule(virFirewallLayer layer, + char **args, size_t argsLen, + bool ignoreErrors, + char **output) +{ + const char *ipv = virFirewallLayerFirewallDTypeToString(layer); + DBusConnection *sysbus = virDBusGetSystemBus(); + DBusMessage *reply = NULL; + virError error; + int ret = -1; + + if (!sysbus) + return -1; + + memset(&error, 0, sizeof(error)); + + if (!ipv) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unknown firewall layer %d"), + layer); + goto cleanup; + } + + if (virDBusCallMethod(sysbus, + &reply, + &error, + VIR_FIREWALL_FIREWALLD_SERVICE, + "/org/fedoraproject/FirewallD1", + "org.fedoraproject.FirewallD1.direct", + "passthrough", + "sa&s", + ipv, + (int)argsLen, + args) < 0) + goto cleanup; + + if (error.level == VIR_ERR_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.str1, error.message); + } else { + virReportErrorObject(&error); + goto cleanup; + } + } else { + if (virDBusMessageRead(reply, "s", output) < 0) + goto cleanup; + } + + ret = 0; + + cleanup: + virResetError(&error); + virDBusMessageUnref(reply); + return ret; +} diff --git a/src/util/virfirewalld.h b/src/util/virfirewalld.h new file mode 100644 index 0000000000..83fe1149cc --- /dev/null +++ b/src/util/virfirewalld.h @@ -0,0 +1,33 @@ +/* + * virfirewalld.h: support for firewalld (https://firewalld.org) + * + * Copyright (C) 2019 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/>. + */ + +#ifndef LIBVIRT_VIRFIREWALLD_H +# define LIBVIRT_VIRFIREWALLD_H + +# define VIR_FIREWALL_FIREWALLD_SERVICE "org.fedoraproject.FirewallD1" + +int virFirewallDIsRegistered(void); + +int virFirewallDApplyRule(virFirewallLayer layer, + char **args, size_t argsLen, + bool ignoreErrors, + char **output); + +#endif /* LIBVIRT_VIRFIREWALLD_H */ diff --git a/src/util/virfirewalldpriv.h b/src/util/virfirewalldpriv.h new file mode 100644 index 0000000000..6c03b467c9 --- /dev/null +++ b/src/util/virfirewalldpriv.h @@ -0,0 +1,30 @@ +/* + * virfirewalldpriv.h: private APIs for firewalld + * + * Copyright (C) 2019 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/>. + */ + +#ifndef LIBVIRT_VIRFIREWALLDPRIV_H_ALLOW +# error "virfirewalldpriv.h may only be included by virfirewalld.c or test suites" +#endif /* LIBVIRT_VIRFIREWALLDPRIV_H_ALLOW */ + +#ifndef LIBVIRT_VIRFIREWALLDPRIV_H +# define LIBVIRT_VIRFIREWALLDPRIV_H + +# define VIR_FIREWALL_FIREWALLD_SERVICE "org.fedoraproject.FirewallD1" + +#endif /* LIBVIRT_VIRFIREWALLDPRIV_H */ diff --git a/src/util/virfirewallpriv.h b/src/util/virfirewallpriv.h index efa94a7da4..7c31d0680d 100644 --- a/src/util/virfirewallpriv.h +++ b/src/util/virfirewallpriv.h @@ -27,8 +27,6 @@ # include "virfirewall.h" -# define VIR_FIREWALL_FIREWALLD_SERVICE "org.fedoraproject.FirewallD1" - typedef enum { VIR_FIREWALL_BACKEND_AUTOMATIC, VIR_FIREWALL_BACKEND_DIRECT, diff --git a/tests/virfirewalltest.c b/tests/virfirewalltest.c index 5fde25d8f6..7c586877d3 100644 --- a/tests/virfirewalltest.c +++ b/tests/virfirewalltest.c @@ -27,6 +27,8 @@ # include "vircommandpriv.h" # define LIBVIRT_VIRFIREWALLPRIV_H_ALLOW # include "virfirewallpriv.h" +# define LIBVIRT_VIRFIREWALLDPRIV_H_ALLOW +# include "virfirewalldpriv.h" # include "virmock.h" # define LIBVIRT_VIRDBUSPRIV_H_ALLOW # include "virdbuspriv.h" -- 2.20.1

On Thu, Jan 31, 2019 at 08:24:53PM -0500, Laine Stump wrote:
In preparation for adding several other firewalld-specific functions, separate the code that's unique to firewalld from the more-generic "firewall" file.
Signed-off-by: Laine Stump <laine@laine.org> ---
Change from V1: define VIR_FIREWALL_FIREWALLD_SERVICE in virfirewalldpriv.h, since it should only be used by virfirewalld.c and unit test programs.
(no, I haven't done anything about the awkward error checking that was already existing in the code I moved - not only is that a separate issue, but there no existing way to fix it anyway - that would take changes to firewalld)
include/libvirt/virterror.h | 1 + src/libvirt_private.syms | 5 ++ src/util/Makefile.inc.am | 3 + src/util/virerror.c | 3 +- src/util/virfirewall.c | 86 +------------------- src/util/virfirewalld.c | 151 ++++++++++++++++++++++++++++++++++++ src/util/virfirewalld.h | 33 ++++++++ src/util/virfirewalldpriv.h | 30 +++++++ src/util/virfirewallpriv.h | 2 - tests/virfirewalltest.c | 2 + 10 files changed, 231 insertions(+), 85 deletions(-) create mode 100644 src/util/virfirewalld.c create mode 100644 src/util/virfirewalld.h create mode 100644 src/util/virfirewalldpriv.h
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

virFirewallDGetBackend() reports whether firewalld is currently using an iptables or an nftables backend. virFirewallDGetVersion() learns the version of the firewalld running on this system and returns it as 1000000*major + 1000*minor + micro. virFirewallDGetZones() gets a list of all currently active firewalld zones. virFirewallDInterfaceSetZone() sets the firewalld zone of the given interface. virFirewallDZoneExists() can be used to learn whether or not a particular zone is present and active in firewalld. Signed-off-by: Laine Stump <laine@laine.org> --- Change from V1: define several new functions (previously was just a single function, now there are 5) src/libvirt_private.syms | 5 + src/util/virfirewalld.c | 222 +++++++++++++++++++++++++++++++++++++++ src/util/virfirewalld.h | 15 ++- 3 files changed, 241 insertions(+), 1 deletion(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index d52e2412fa..bfae73878f 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1923,7 +1923,12 @@ virFirewallStartTransaction; # util/virfirewalld.h virFirewallDApplyRule; +virFirewallDGetBackend; +virFirewallDGetVersion; +virFirewallDGetZones; +virFirewallDInterfaceSetZone; virFirewallDIsRegistered; +virFirewallDZoneExists; # util/virfirmware.h diff --git a/src/util/virfirewalld.c b/src/util/virfirewalld.c index f27ec9c124..89cfaddd9a 100644 --- a/src/util/virfirewalld.c +++ b/src/util/virfirewalld.c @@ -46,6 +46,14 @@ VIR_ENUM_IMPL(virFirewallLayerFirewallD, VIR_FIREWALL_LAYER_LAST, ); +VIR_ENUM_DECL(virFirewallDBackend); +VIR_ENUM_IMPL(virFirewallDBackend, VIR_FIREWALLD_BACKEND_LAST, + "", + "iptables", + "nftables", + ); + + /** * virFirewallDIsRegistered: * @@ -57,6 +65,197 @@ virFirewallDIsRegistered(void) return virDBusIsServiceRegistered(VIR_FIREWALL_FIREWALLD_SERVICE); } +/** + * virFirewallDGetVersion: + * @version: pointer to location to save version in the form of: + * 1000000 * major + 1000 * minor + micro + * + * queries the firewalld version property from dbus, and converts it + * from a string into a number. + * + * Returns 0 if version was successfully retrieved, or -1 on error + */ +int +virFirewallDGetVersion(unsigned long *version) +{ + int ret = -1; + DBusConnection *sysbus = virDBusGetSystemBus(); + DBusMessage *reply = NULL; + VIR_AUTOFREE(char *) versionStr = NULL; + + if (!sysbus) + return -1; + + if (virDBusCallMethod(sysbus, + &reply, + NULL, + VIR_FIREWALL_FIREWALLD_SERVICE, + "/org/fedoraproject/FirewallD1", + "org.freedesktop.DBus.Properties", + "Get", + "ss", + "org.fedoraproject.FirewallD1", + "version") < 0) + goto cleanup; + + if (virDBusMessageRead(reply, "v", "s", &versionStr) < 0) + goto cleanup; + + if (virParseVersionString(versionStr, version, false) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to parse firewalld version '%s'"), + versionStr); + goto cleanup; + } + + VIR_DEBUG("FirewallD version: %s - %lu", versionStr, *version); + + ret = 0; + cleanup: + virDBusMessageUnref(reply); + return ret; +} + +/** + * virFirewallDGetBackend: + * + * Returns virVirewallDBackendType value representing which packet + * filtering backend is currently in use by firewalld, or -1 on error. + */ +int +virFirewallDGetBackend(void) +{ + DBusConnection *sysbus = virDBusGetSystemBus(); + DBusMessage *reply = NULL; + virError error; + VIR_AUTOFREE(char *) backendStr = NULL; + int backend = -1; + + if (!sysbus) + return -1; + + memset(&error, 0, sizeof(error)); + + if (virDBusCallMethod(sysbus, + &reply, + &error, + VIR_FIREWALL_FIREWALLD_SERVICE, + "/org/fedoraproject/FirewallD1/config", + "org.freedesktop.DBus.Properties", + "Get", + "ss", + "org.fedoraproject.FirewallD1.config", + "FirewallBackend") < 0) + goto cleanup; + + if (error.level == VIR_ERR_ERROR) { + /* we don't want to log any error in the case that + * FirewallBackend isn't implemented in this firewalld, since + * that just means that it is an old version, and only has an + * iptables backend. + */ + VIR_DEBUG("Failed to get FirewallBackend setting, assuming 'iptables'"); + backend = VIR_FIREWALLD_BACKEND_IPTABLES; + goto cleanup; + } + + if (virDBusMessageRead(reply, "v", "s", &backendStr) < 0) + goto cleanup; + + VIR_DEBUG("FirewallD backend: %s", backendStr); + + if ((backend = virFirewallDBackendTypeFromString(backendStr)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unrecognized firewalld backend type: %s"), + backendStr); + } + + cleanup: + virResetError(&error); + virDBusMessageUnref(reply); + return backend; +} + + +/** + * virFirewallDGetZones: + * @zones: array of char *, each entry is a null-terminated zone name + * @nzones: number of entries in @zones + * + * Get the number of currently active firewalld zones, and their names in an + * array of null-terminated strings. + * + * Returns 0 on success, -1 (and failure logged) on error + */ +int +virFirewallDGetZones(char ***zones, size_t *nzones) +{ + DBusConnection *sysbus = virDBusGetSystemBus(); + DBusMessage *reply = NULL; + int ret = -1; + + *nzones = 0; + *zones = NULL; + + if (!sysbus) + return -1; + + if (virDBusCallMethod(sysbus, + &reply, + NULL, + VIR_FIREWALL_FIREWALLD_SERVICE, + "/org/fedoraproject/FirewallD1", + "org.fedoraproject.FirewallD1.zone", + "getZones", + NULL) < 0) + goto cleanup; + + if (virDBusMessageRead(reply, "a&s", nzones, zones) < 0) + goto cleanup; + + ret = 0; + cleanup: + virDBusMessageUnref(reply); + return ret; +} + + +/** + * virFirewallDZoneExists: + * @match: name of zone to look for + * + * Returns true if the requested zone exists, or false if it doesn't exist + */ +bool +virFirewallDZoneExists(const char *match) +{ + size_t nzones = 0, i; + char **zones = NULL; + bool result = false; + + return true; + + if (virFirewallDGetZones(&zones, &nzones) < 0) + goto cleanup; + + for (i = 0; i < nzones; i++) { + VIR_DEBUG("FirewallD zone: %s", zones[i]); + if (STREQ_NULLABLE(zones[i], match)) + result = true; + } + + cleanup: + /* NB: zones points to memory inside reply, so it is cleaned + * up by virDBusMessageUnref, and doesn't need to be freed + */ + VIR_DEBUG("Requested zone '%s' %s exist", + match, result ? "does" : "doesn't"); + for (i = 0; i < nzones; i++) + VIR_FREE(zones[i]); + VIR_FREE(zones); + return result; +} + /** * virFirewallDApplyRule: @@ -149,3 +348,26 @@ virFirewallDApplyRule(virFirewallLayer layer, virDBusMessageUnref(reply); return ret; } + + +int +virFirewallDInterfaceSetZone(const char *iface, + const char *zone) +{ + DBusConnection *sysbus = virDBusGetSystemBus(); + DBusMessage *reply = NULL; + + if (!sysbus) + return -1; + + return virDBusCallMethod(sysbus, + &reply, + NULL, + VIR_FIREWALL_FIREWALLD_SERVICE, + "/org/fedoraproject/FirewallD1", + "org.fedoraproject.FirewallD1.zone", + "changeZoneOfInterface", + "ss", + zone, + iface); +} diff --git a/src/util/virfirewalld.h b/src/util/virfirewalld.h index 83fe1149cc..f05f5f2f08 100644 --- a/src/util/virfirewalld.h +++ b/src/util/virfirewalld.h @@ -23,11 +23,24 @@ # define VIR_FIREWALL_FIREWALLD_SERVICE "org.fedoraproject.FirewallD1" -int virFirewallDIsRegistered(void); +typedef enum { + VIR_FIREWALLD_BACKEND_NONE, + VIR_FIREWALLD_BACKEND_IPTABLES, + VIR_FIREWALLD_BACKEND_NFTABLES, + VIR_FIREWALLD_BACKEND_LAST, +} virFirewallDBackendType; +int virFirewallDGetVersion(unsigned long *version); +int virFirewallDGetBackend(void); +int virFirewallDIsRegistered(void); +int virFirewallDGetZones(char ***zones, size_t *nzones); +bool virFirewallDZoneExists(const char *match); int virFirewallDApplyRule(virFirewallLayer layer, char **args, size_t argsLen, bool ignoreErrors, char **output); +int virFirewallDInterfaceSetZone(const char *iface, + const char *zone); + #endif /* LIBVIRT_VIRFIREWALLD_H */ -- 2.20.1

On Thu, Jan 31, 2019 at 08:24:54PM -0500, Laine Stump wrote:
virFirewallDGetBackend() reports whether firewalld is currently using an iptables or an nftables backend.
virFirewallDGetVersion() learns the version of the firewalld running on this system and returns it as 1000000*major + 1000*minor + micro.
virFirewallDGetZones() gets a list of all currently active firewalld zones.
virFirewallDInterfaceSetZone() sets the firewalld zone of the given interface.
virFirewallDZoneExists() can be used to learn whether or not a particular zone is present and active in firewalld.
Signed-off-by: Laine Stump <laine@laine.org> ---
+int +virFirewallDGetBackend(void) +{ + DBusConnection *sysbus = virDBusGetSystemBus(); + DBusMessage *reply = NULL; + virError error; + VIR_AUTOFREE(char *) backendStr = NULL; + int backend = -1; + + if (!sysbus) + return -1; + + memset(&error, 0, sizeof(error)); + + if (virDBusCallMethod(sysbus, + &reply, + &error, + VIR_FIREWALL_FIREWALLD_SERVICE, + "/org/fedoraproject/FirewallD1/config", + "org.freedesktop.DBus.Properties", + "Get", + "ss", + "org.fedoraproject.FirewallD1.config", + "FirewallBackend") < 0) + goto cleanup; + + if (error.level == VIR_ERR_ERROR) { + /* we don't want to log any error in the case that + * FirewallBackend isn't implemented in this firewalld, since + * that just means that it is an old version, and only has an + * iptables backend. + */ + VIR_DEBUG("Failed to get FirewallBackend setting, assuming 'iptables'"); + backend = VIR_FIREWALLD_BACKEND_IPTABLES; + goto cleanup; + }
Surely we need an '} else {' case here to propagate 'error' as fatal.
+ + if (virDBusMessageRead(reply, "v", "s", &backendStr) < 0) + goto cleanup; + + VIR_DEBUG("FirewallD backend: %s", backendStr); + + if ((backend = virFirewallDBackendTypeFromString(backendStr)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unrecognized firewalld backend type: %s"), + backendStr); + }
I'd usually put an explicit 'goto cleanup' here. I've seen bugs in the past where people optimized by assuming we'll immediately hit the cleanup, but someome later introduces more code and doesn't notice the missing goto.
+ + cleanup: + virResetError(&error); + virDBusMessageUnref(reply); + return backend; +}
+/** + * virFirewallDGetZones: + * @zones: array of char *, each entry is a null-terminated zone name + * @nzones: number of entries in @zones + * + * Get the number of currently active firewalld zones, and their names in an + * array of null-terminated strings. + * + * Returns 0 on success, -1 (and failure logged) on error + */ +int +virFirewallDGetZones(char ***zones, size_t *nzones) +{ + DBusConnection *sysbus = virDBusGetSystemBus(); + DBusMessage *reply = NULL; + int ret = -1; + + *nzones = 0; + *zones = NULL; + + if (!sysbus) + return -1; + + if (virDBusCallMethod(sysbus, + &reply, + NULL, + VIR_FIREWALL_FIREWALLD_SERVICE, + "/org/fedoraproject/FirewallD1", + "org.fedoraproject.FirewallD1.zone", + "getZones", + NULL) < 0) + goto cleanup; + + if (virDBusMessageRead(reply, "a&s", nzones, zones) < 0) + goto cleanup; + + ret = 0; + cleanup: + virDBusMessageUnref(reply); + return ret; +} + + +/** + * virFirewallDZoneExists: + * @match: name of zone to look for + * + * Returns true if the requested zone exists, or false if it doesn't exist + */ +bool +virFirewallDZoneExists(const char *match) +{ + size_t nzones = 0, i; + char **zones = NULL; + bool result = false; + + return true; + + if (virFirewallDGetZones(&zones, &nzones) < 0) + goto cleanup; + + for (i = 0; i < nzones; i++) { + VIR_DEBUG("FirewallD zone: %s", zones[i]); + if (STREQ_NULLABLE(zones[i], match)) + result = true; + } + + cleanup: + /* NB: zones points to memory inside reply, so it is cleaned + * up by virDBusMessageUnref, and doesn't need to be freed + */
I'm confused by this comment. virDBusMessageUnref has already run before control even returned to this method, so it 'zones' is owned by the reply, we've been using free'd memory. The very next lines, however, contradict this comment by freein'g zones anyway, which makes me even more confused :-)
+ VIR_DEBUG("Requested zone '%s' %s exist", + match, result ? "does" : "doesn't"); + for (i = 0; i < nzones; i++) + VIR_FREE(zones[i]); + VIR_FREE(zones); + return result; +} +
Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

On 2/1/19 8:17 AM, Daniel P. Berrangé wrote:
On Thu, Jan 31, 2019 at 08:24:54PM -0500, Laine Stump wrote:
+int +virFirewallDGetBackend(void) +{ + DBusConnection *sysbus = virDBusGetSystemBus(); + DBusMessage *reply = NULL; + virError error; + VIR_AUTOFREE(char *) backendStr = NULL; + int backend = -1; + + if (!sysbus) + return -1; + + memset(&error, 0, sizeof(error)); + + if (virDBusCallMethod(sysbus, + &reply, + &error, + VIR_FIREWALL_FIREWALLD_SERVICE, + "/org/fedoraproject/FirewallD1/config", + "org.freedesktop.DBus.Properties", + "Get", + "ss", + "org.fedoraproject.FirewallD1.config", + "FirewallBackend") < 0) + goto cleanup; + + if (error.level == VIR_ERR_ERROR) { + /* we don't want to log any error in the case that + * FirewallBackend isn't implemented in this firewalld, since + * that just means that it is an old version, and only has an + * iptables backend. + */ + VIR_DEBUG("Failed to get FirewallBackend setting, assuming 'iptables'"); + backend = VIR_FIREWALLD_BACKEND_IPTABLES; + goto cleanup; + } Surely we need an '} else {' case here to propagate 'error' as fatal.
The point is to ignore all errors. If error.level != VIR_ERR_ERROR, then there is no error to propagate (and if it is then we already ignored it). Am I missing something? (Very likely, since I can count on one hand the number of times I've had to directly interact with an error object.)
+ + if (virDBusMessageRead(reply, "v", "s", &backendStr) < 0) + goto cleanup; + + VIR_DEBUG("FirewallD backend: %s", backendStr); + + if ((backend = virFirewallDBackendTypeFromString(backendStr)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unrecognized firewalld backend type: %s"), + backendStr); + } I'd usually put an explicit 'goto cleanup' here. I've seen bugs in the past where people optimized by assuming we'll immediately hit the cleanup, but someome later introduces more code and doesn't notice the missing goto.
Yeah, me too. I just missed it. I'll add it in.
+ + cleanup: + virResetError(&error); + virDBusMessageUnref(reply); + return backend; +}
+/** + * virFirewallDGetZones: + * @zones: array of char *, each entry is a null-terminated zone name + * @nzones: number of entries in @zones + * + * Get the number of currently active firewalld zones, and their names in an + * array of null-terminated strings. + * + * Returns 0 on success, -1 (and failure logged) on error + */ +int +virFirewallDGetZones(char ***zones, size_t *nzones) +{ + DBusConnection *sysbus = virDBusGetSystemBus(); + DBusMessage *reply = NULL; + int ret = -1; + + *nzones = 0; + *zones = NULL; + + if (!sysbus) + return -1; + + if (virDBusCallMethod(sysbus, + &reply, + NULL, + VIR_FIREWALL_FIREWALLD_SERVICE, + "/org/fedoraproject/FirewallD1", + "org.fedoraproject.FirewallD1.zone", + "getZones", + NULL) < 0) + goto cleanup; + + if (virDBusMessageRead(reply, "a&s", nzones, zones) < 0) + goto cleanup; + + ret = 0; + cleanup: + virDBusMessageUnref(reply); + return ret; +} + + +/** + * virFirewallDZoneExists: + * @match: name of zone to look for + * + * Returns true if the requested zone exists, or false if it doesn't exist + */ +bool +virFirewallDZoneExists(const char *match) +{ + size_t nzones = 0, i; + char **zones = NULL; + bool result = false; + + return true; + + if (virFirewallDGetZones(&zones, &nzones) < 0) + goto cleanup; + + for (i = 0; i < nzones; i++) { + VIR_DEBUG("FirewallD zone: %s", zones[i]); + if (STREQ_NULLABLE(zones[i], match)) + result = true; + } + + cleanup: + /* NB: zones points to memory inside reply, so it is cleaned + * up by virDBusMessageUnref, and doesn't need to be freed + */ I'm confused by this comment. virDBusMessageUnref has already run before control even returned to this method, so it 'zones' is owned by the reply, we've been using free'd memory.
The very next lines, however, contradict this comment by freein'g zones anyway, which makes me even more confused :-)
Yeah, I changed the code and forgot to remove the comment (even through probably a dozen or more rebases *and* moving a large chunk of this function into the preceding virFirewalldGetZones() function!! Somehow my brain just glossed right over the comment as I was changing and moving the code.) The comment arose from me misunderstanding something you said in IRC (or maybe you misunderstood what I was asking about - for a period I thought that the references returned for a&s args sent to virDBusMessageRead were pointers to memory inside the DBusMessage, and that they would be cleaned up by virDBusMessageUnref(), so I wrote that comment early on when writing the function, but then when I looked at the test programs, I saw they *were* freeing the memory, and a run under valgrind confirmed that I needed to free it. So I changed the code, by neglected to clean out the comment. Anyway, I'll remove the comment.
+ VIR_DEBUG("Requested zone '%s' %s exist", + match, result ? "does" : "doesn't"); + for (i = 0; i < nzones; i++) + VIR_FREE(zones[i]); + VIR_FREE(zones); + return result; +} +
Regards, Daniel

On Fri, Feb 01, 2019 at 10:06:44AM -0500, Laine Stump wrote:
On 2/1/19 8:17 AM, Daniel P. Berrangé wrote:
On Thu, Jan 31, 2019 at 08:24:54PM -0500, Laine Stump wrote:
+int +virFirewallDGetBackend(void) +{ + DBusConnection *sysbus = virDBusGetSystemBus(); + DBusMessage *reply = NULL; + virError error; + VIR_AUTOFREE(char *) backendStr = NULL; + int backend = -1; + + if (!sysbus) + return -1; + + memset(&error, 0, sizeof(error)); + + if (virDBusCallMethod(sysbus, + &reply, + &error, + VIR_FIREWALL_FIREWALLD_SERVICE, + "/org/fedoraproject/FirewallD1/config", + "org.freedesktop.DBus.Properties", + "Get", + "ss", + "org.fedoraproject.FirewallD1.config", + "FirewallBackend") < 0) + goto cleanup; + + if (error.level == VIR_ERR_ERROR) { + /* we don't want to log any error in the case that + * FirewallBackend isn't implemented in this firewalld, since + * that just means that it is an old version, and only has an + * iptables backend. + */ + VIR_DEBUG("Failed to get FirewallBackend setting, assuming 'iptables'"); + backend = VIR_FIREWALLD_BACKEND_IPTABLES; + goto cleanup; + } Surely we need an '} else {' case here to propagate 'error' as fatal.
The point is to ignore all errors. If error.level != VIR_ERR_ERROR, then there is no error to propagate (and if it is then we already ignored it). Am I missing something? (Very likely, since I can count on one hand the number of times I've had to directly interact with an error object.)
No, I'm just being dumb misreading this. Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

In the past (when both libvirt and firewalld used iptables), if either libvirt's rules *OR* firewalld's rules accepted a packet, it would be accepted. This was because libvirt and firewalld rules were processed during the same kernel hook, and a single ACCEPT result would terminate the rule traversal and cause the packet to be accepted. But now firewalld can use nftables for its backend, while libvirt's firewall rules are still using iptables; iptables rules are still processed, but at a different time during packet processing (i.e. during a different hook) than the firewalld nftables rules. The result is that a packet must be accepted by *BOTH* the libvirt iptables rules *AND* the firewalld nftable rules in order to be accepted. This causes pain because 1) libvirt always adds rules to permit DNS and DHCP (and sometimes TFTP) from guests to the host network's bridge interface. But libvirt's bridges are in firewalld's "default" zone (which is usually the zone called "public"). The public zone allows ssh, but doesn't allow DNS, DHCP, or TFTP. So even though libvirt's rules allow the DHCP and DNS traffic, the firewalld rules (now processed during a different hook) dont, thus guests connected to libvirt's bridges can't acquire an IP address from DHCP, nor can they make DNS queries to the DNS server libvirt has setup on the host. (This could be solved by modifying the default firewalld zone to allow DNS and DHCP, but that would open *all* interfaces in the default zone to those services, which is most likely not what the host's admin wants.) 2) Even though libvirt adds iptables rules to allow forwarded traffic to pass the iptables hook, firewalld's higher level "rich rules" don't yet have the ability to configure the acceptance of forwarded traffic (traffic that is going somewhere beyond the host), so any traffic that needs to be forwarded from guests to the network beyond the host is rejected during the nftables hook by the default zone's "default reject" policy (which rejects all traffic in the zone not specifically allowed by the rules in the zone, whether that traffic is destined to be forwarded or locally received by the host). libvirt can't send "direct" nftables rules (firewalld only supports direct/passthrough rules for iptables), so we can't solve this problem by just sending explicit nftables rules instead of explicit iptables rules (which, if it could be done, would place libvirt's rules in the same hook as firewalld's native rules, and thus eliminate the need for packets to be accepted by both libvirt's and firewalld's own rules). However, we can take advantage of a quirk in firewalld zones that have a default policy of "accept" (meaning any packet that doesn't match a specific rule in the zone will be *accepted*) - this default accept will also accept forwarded traffic (not just traffic destined for the host). Of course we don't want to modify firewalld's default zone in that way, because that would affect the filtering of traffic coming into the host from other interfaces using that zone. Instead, we will create a new zone called "libvirt". The libvirt zone will have a default policy of accept so that forwarded traffic can pass andn list specific services that will be allowed into the host from guests (DNS, DHCP, SSH, and TFTP). But the same default accept policy that fixes forwarded traffic also causes *all* traffic from guest to host to be accepted. To close this new hole, the libvirt zone can take advantage of a new feature in firewalld (currently slated for firewalld-0.7.0) - priorities for rich rules - to add a low priority rule that rejects all local traffic (but leaves alone all forwarded traffic). So, our new zone will start with a list of services that are allowed (dhcp, dns, tftp, and ssh to start, but configurable via any firewalld management application, or direct editing of the zone file in /etc/firewalld/zones/libvirt.xml), followed by a low priority <reject/> rule (to reject all other traffic from guest to host), and finally with a default policy of accept (to allow forwarded traffic). This patch only creates the zonefile for the new zone, and implements a configure.ac option to selectively enable/disable installation of the new zone. A separate patch contains the necessary code to actually place bridge interfaces in the libvirt zone. Why do we need a configure option to disable installation of the new libvirt zone? It uses a new firewalld attribute that sets the priority of a rich rule; this feature first appears in firewalld-0.7.0 (unless it has been backported to am earlier firewalld by a downstream maintainer). If the file were installed on a system with firewalld that didn't support rule priorities, firewalld would log an error every time it restarted, causing confusion and lots of extra bug reports. So we add two new configure.ac switches to avoid polluting the system logs with this error on systems that don't support rule priorities - "--with-firewalld-zone" and "--without-firewalld-zone". A package builder can use these to include/exclude the libvirt zone file in the installation. If firewalld is enabled (--with-firewalld), the default is --with-firewalld-zone, but it can be disabled during configure (using --without-firewalld-zone). Targets that are using a firewalld version too old to support the rule priority setting in the libvirt zone file can simply add --without-firewalld-zone to their configure commandline. These switches only affect whether or not the libvirt zone file is *installed* in /usr/lib/firewalld/zones, but have no effect on whether or not libvirt looks for a zone called libvirt and tries to use it. NB: firewalld zones can only be added to the permanent config of firewalld, and won't be loaded/enabled until firewalld is restarted, so at package install/upgrade time we have to restart firewalld. For rpm-based distros, this is done in the libvirt.spec file by calling the %firewalld_restart rpm macro, which is a part of the firewalld-filesystem package. (For distros that don't use rpm packages, the command "firewalld-cmd --reload" will have the same effect). Signed-off-by: Laine Stump <laine@laine.org> --- Changes from V1: * only BuildRequire: firewalld-system if we are going to need it * make installation of the zone file optional, based on configure.ac options * make the <reject/> rule's priority 32767 instead of 127. * move the code that actually sets the new zone to a separate patch configure.ac | 3 +++ libvirt.spec.in | 31 +++++++++++++++++++++++++ m4/virt-firewalld-zone.m4 | 45 +++++++++++++++++++++++++++++++++++++ src/network/Makefile.inc.am | 10 ++++++++- src/network/libvirt.zone | 23 +++++++++++++++++++ 5 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 m4/virt-firewalld-zone.m4 create mode 100644 src/network/libvirt.zone diff --git a/configure.ac b/configure.ac index 808884f2f7..4dcdd12069 100644 --- a/configure.ac +++ b/configure.ac @@ -246,6 +246,7 @@ LIBVIRT_ARG_CAPNG LIBVIRT_ARG_CURL LIBVIRT_ARG_DBUS LIBVIRT_ARG_FIREWALLD +LIBVIRT_ARG_FIREWALLD_ZONE LIBVIRT_ARG_FUSE LIBVIRT_ARG_GLUSTER LIBVIRT_ARG_HAL @@ -286,6 +287,7 @@ LIBVIRT_CHECK_DBUS LIBVIRT_CHECK_DEVMAPPER LIBVIRT_CHECK_DLOPEN LIBVIRT_CHECK_FIREWALLD +LIBVIRT_CHECK_FIREWALLD_ZONE LIBVIRT_CHECK_FUSE LIBVIRT_CHECK_GLUSTER LIBVIRT_CHECK_GNUTLS @@ -1000,6 +1002,7 @@ LIBVIRT_RESULT_CURL LIBVIRT_RESULT_DBUS LIBVIRT_RESULT_DLOPEN LIBVIRT_RESULT_FIREWALLD +LIBVIRT_RESULT_FIREWALLD_ZONE LIBVIRT_RESULT_FUSE LIBVIRT_RESULT_GLUSTER LIBVIRT_RESULT_GNUTLS diff --git a/libvirt.spec.in b/libvirt.spec.in index 879e315f49..c0e538d92d 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -82,6 +82,7 @@ %define with_sanlock 0%{!?_without_sanlock:0} %define with_numad 0%{!?_without_numad:0} %define with_firewalld 0%{!?_without_firewalld:0} +%define with_firewalld_zone 0%{!?_without_firewalld_zone:0} %define with_libssh2 0%{!?_without_libssh2:0} %define with_wireshark 0%{!?_without_wireshark:0} %define with_libssh 0%{!?_without_libssh:0} @@ -136,6 +137,11 @@ %define with_firewalld 1 +%if 0%{?fedora} >= 30 || 0%{?rhel} > 7 + %define with_firewalld_zone 0%{!?_without_firewalld_zone:1} +%endif + + # fuse is used to provide virtualized /proc for LXC %if %{with_lxc} %define with_fuse 0%{!?_without_fuse:1} @@ -395,6 +401,10 @@ BuildRequires: rpcgen BuildRequires: libtirpc-devel %endif +%if %{with_firewalld_zone} +BuildRequires: firewalld-filesystem +%endif + Provides: bundled(gnulib) %description @@ -1093,6 +1103,12 @@ exit 1 %define arg_firewalld --without-firewalld %endif +%if %{with_firewalld_zone} + %define arg_firewalld_zone --with-firewalld-zone +%else + %define arg_firewalld_zone --without-firewalld-zone +%endif + %if %{with_wireshark} %define arg_wireshark --with-wireshark-dissector %else @@ -1191,6 +1207,7 @@ rm -f po/stamp-po --with-dtrace \ --with-driver-modules \ %{?arg_firewalld} \ + %{?arg_firewalld_zone} \ %{?arg_wireshark} \ --without-pm-utils \ --with-nss-plugin \ @@ -1358,6 +1375,16 @@ if [ -f %{_localstatedir}/lib/rpm-state/libvirt/restart ]; then fi rm -rf %{_localstatedir}/lib/rpm-state/libvirt || : +%post daemon-driver-network +%if %{with_firewalld} + %firewalld_reload +%endif + +%postun daemon-driver-network +%if %{with_firewalld} + %firewalld_reload +%endif + %post daemon-config-network if test $1 -eq 1 && test ! -f %{_sysconfdir}/libvirt/qemu/networks/default.xml ; then # see if the network used by default network creates a conflict, @@ -1596,6 +1623,10 @@ exit 0 %attr(0755, root, root) %{_libexecdir}/libvirt_leaseshelper %{_libdir}/%{name}/connection-driver/libvirt_driver_network.so +%if %{with_firewalld_zone} +%{_prefix}/lib/firewalld/zones/libvirt.xml +%endif + %files daemon-driver-nodedev %{_libdir}/%{name}/connection-driver/libvirt_driver_nodedev.so diff --git a/m4/virt-firewalld-zone.m4 b/m4/virt-firewalld-zone.m4 new file mode 100644 index 0000000000..b67d1a0b2f --- /dev/null +++ b/m4/virt-firewalld-zone.m4 @@ -0,0 +1,45 @@ +dnl firewalld_zone check - whether or not to install the firewall "libvirt" zone +dnl +dnl Copyright (C) 2019 Red Hat, Inc. +dnl +dnl This library is free software; you can redistribute it and/or +dnl modify it under the terms of the GNU Lesser General Public +dnl License as published by the Free Software Foundation; either +dnl version 2.1 of the License, or (at your option) any later version. +dnl +dnl This library is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +dnl Lesser General Public License for more details. +dnl +dnl You should have received a copy of the GNU Lesser General Public +dnl License along with this library. If not, see +dnl <http://www.gnu.org/licenses/>. +dnl + +AC_DEFUN([LIBVIRT_ARG_FIREWALLD_ZONE], [ + LIBVIRT_ARG_WITH([FIREWALLD_ZONE], [Whether to install firewalld libvirt zone], [check]) +]) + +AC_DEFUN([LIBVIRT_CHECK_FIREWALLD_ZONE], [ + AC_REQUIRE([LIBVIRT_CHECK_FIREWALLD]) + AC_MSG_CHECKING([for whether to install firewalld libvirt zone]) + + if test "x$with_firewalld_zone" = "xcheck" ; then + with_firewalld_zone=$with_firewalld + fi + + if test "x$with_firewalld_zone" = "xyes" ; then + if test "x$with_firewalld" != "xyes" ; then + AC_MSG_ERROR([You must have firewalld support enabled to enable firewalld-zone]) + fi + AC_DEFINE_UNQUOTED([WITH_FIREWALLD_ZONE], [1], [whether firewalld libvirt zone is installed]) + fi + + AM_CONDITIONAL([WITH_FIREWALLD_ZONE], [test "x$with_firewalld_zone" != "xno"]) + AC_MSG_RESULT($with_firewalld_zone) +]) + +AC_DEFUN([LIBVIRT_RESULT_FIREWALLD_ZONE], [ + LIBVIRT_RESULT([firewalld-zone], [$with_firewalld_zone]) +]) diff --git a/src/network/Makefile.inc.am b/src/network/Makefile.inc.am index 508c8c0422..cbaaa7ea68 100644 --- a/src/network/Makefile.inc.am +++ b/src/network/Makefile.inc.am @@ -87,6 +87,11 @@ install-data-network: ( cd $(DESTDIR)$(confdir)/qemu/networks/autostart && \ rm -f default.xml && \ $(LN_S) ../default.xml default.xml ) +if WITH_FIREWALLD_ZONE + $(MKDIR_P) "$(DESTDIR)$(prefix)/lib/firewalld/zones" + $(INSTALL_DATA) $(srcdir)/network/libvirt.zone \ + $(DESTDIR)$(prefix)/lib/firewalld/zones/libvirt.xml +endif WITH_FIREWALLD_ZONE uninstall-data-network: rm -f $(DESTDIR)$(confdir)/qemu/networks/autostart/default.xml @@ -95,10 +100,13 @@ uninstall-data-network: rmdir "$(DESTDIR)$(confdir)/qemu/networks" || : rmdir "$(DESTDIR)$(localstatedir)/lib/libvirt/network" ||: rmdir "$(DESTDIR)$(localstatedir)/run/libvirt/network" ||: +if WITH_FIREWALLD_ZONE + rm -f $(DESTDIR)$(prefix)/lib/firewalld/zones/libvirt.xml +endif WITH_FIREWALLD_ZONE endif WITH_NETWORK -EXTRA_DIST += network/default.xml +EXTRA_DIST += network/default.xml network/libvirt.zone .PHONY: \ install-data-network \ diff --git a/src/network/libvirt.zone b/src/network/libvirt.zone new file mode 100644 index 0000000000..bf81db1b6e --- /dev/null +++ b/src/network/libvirt.zone @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<zone target="ACCEPT"> + <short>libvirt</short> + + <description> + The default policy of "ACCEPT" allows all packets to/from + interfaces in the zone to be forwarded, while the (*low priority*) + reject rule blocks any traffic destined for the host, except those + services explicitly listed (that list can be modified as required + by the local admin). This zone is intended to be used only by + libvirt virtual networks - libvirt will add the bridge devices for + all new virtual networks to this zone by default. + </description> + +<rule priority='32767'> + <reject/> +</rule> +<service name='dhcp'/> +<service name='dhcpv6'/> +<service name='dns'/> +<service name='ssh'/> +<service name='tftp'/> +</zone> -- 2.20.1

On 1/31/19 8:24 PM, Laine Stump wrote:
Changes from V1: [...]
* make the <reject/> rule's priority 32767 instead of 127. [...]
+ +<rule priority='32767'> + <reject/> +</rule>
I found out after sending this that when I make the priority of the reject rule 32767 instead of 127, it's apparently ignored (in my example, I was able to ssh to port 222 of the host even though the zone doesn't allow that). Eric, any idea why this might be happening?

On Thu, Jan 31, 2019 at 10:10:43PM -0500, Laine Stump wrote:
On 1/31/19 8:24 PM, Laine Stump wrote:
Changes from V1: [...]
* make the <reject/> rule's priority 32767 instead of 127. [...]
+ +<rule priority='32767'> + <reject/> +</rule>
I found out after sending this that when I make the priority of the reject rule 32767 instead of 127, it's apparently ignored (in my example, I was able to ssh to port 222 of the host even though the zone doesn't allow that).
Some kind of boundary condition i guess. Perhaps 32766 will work ?
Eric, any idea why this might be happening?
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list
Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

On Thu, Jan 31, 2019 at 10:10:43PM -0500, Laine Stump wrote:
On 1/31/19 8:24 PM, Laine Stump wrote:
Changes from V1: [...]
* make the <reject/> rule's priority 32767 instead of 127. [...]
+ +<rule priority='32767'> + <reject/> +</rule>
I found out after sending this that when I make the priority of the reject rule 32767 instead of 127, it's apparently ignored (in my example, I was able to ssh to port 222 of the host even though the zone doesn't allow that).
Eric, any idea why this might be happening?
What build are you testing against? At one point the limit was 127, but I increased it before pushing it upstream. You can check the firewalld logs - there may be an error reporting the above priority is out of range.

On 2/1/19 8:28 AM, Eric Garver wrote:
On Thu, Jan 31, 2019 at 10:10:43PM -0500, Laine Stump wrote:
On 1/31/19 8:24 PM, Laine Stump wrote:
Changes from V1: [...] * make the <reject/> rule's priority 32767 instead of 127. [...] + +<rule priority='32767'> + <reject/> +</rule>
I found out after sending this that when I make the priority of the reject rule 32767 instead of 127, it's apparently ignored (in my example, I was able to ssh to port 222 of the host even though the zone doesn't allow that).
Eric, any idea why this might be happening? What build are you testing against? At one point the limit was 127, but I increased it before pushing it upstream. You can check the firewalld logs - there may be an error reporting the above priority is out of range.
Ah, maybe you haven't backported that change to RHEL? I was testing on my RHEL8 beta system. If that's the case, then either we need that change backported to RHEL too, or I need to change the priority back to 127.

On 2/1/19 8:49 AM, Laine Stump wrote:
On 2/1/19 8:28 AM, Eric Garver wrote:
On Thu, Jan 31, 2019 at 10:10:43PM -0500, Laine Stump wrote:
On 1/31/19 8:24 PM, Laine Stump wrote:
Changes from V1: [...] * make the <reject/> rule's priority 32767 instead of 127. [...] + +<rule priority='32767'> + <reject/> +</rule>
I found out after sending this that when I make the priority of the reject rule 32767 instead of 127, it's apparently ignored (in my example, I was able to ssh to port 222 of the host even though the zone doesn't allow that).
Eric, any idea why this might be happening? What build are you testing against? At one point the limit was 127, but I increased it before pushing it upstream. You can check the firewalld logs - there may be an error reporting the above priority is out of range.
Ah, maybe you haven't backported that change to RHEL? I was testing on my RHEL8 beta system. If that's the case, then either we need that change backported to RHEL too, or I need to change the priority back to 127.
Okay, Eric and I figured out thie problem was that my test machine was running an early scratch build of the firewalld package that had the limit for priority at 127, but also had been given a fake version that was *higher* than the proper build in the repo, so yum update wasn't grabbing it. Now that my firewalld package is up to date, it works properly!

On Thu, Jan 31, 2019 at 08:24:55PM -0500, Laine Stump wrote:
In the past (when both libvirt and firewalld used iptables), if either libvirt's rules *OR* firewalld's rules accepted a packet, it would be accepted. This was because libvirt and firewalld rules were processed during the same kernel hook, and a single ACCEPT result would terminate the rule traversal and cause the packet to be accepted.
But now firewalld can use nftables for its backend, while libvirt's firewall rules are still using iptables; iptables rules are still processed, but at a different time during packet processing (i.e. during a different hook) than the firewalld nftables rules. The result is that a packet must be accepted by *BOTH* the libvirt iptables rules *AND* the firewalld nftable rules in order to be accepted.
This causes pain because
1) libvirt always adds rules to permit DNS and DHCP (and sometimes TFTP) from guests to the host network's bridge interface. But libvirt's bridges are in firewalld's "default" zone (which is usually the zone called "public"). The public zone allows ssh, but doesn't allow DNS, DHCP, or TFTP. So even though libvirt's rules allow the DHCP and DNS traffic, the firewalld rules (now processed during a different hook) dont, thus guests connected to libvirt's bridges can't acquire an IP address from DHCP, nor can they make DNS queries to the DNS server libvirt has setup on the host. (This could be solved by modifying the default firewalld zone to allow DNS and DHCP, but that would open *all* interfaces in the default zone to those services, which is most likely not what the host's admin wants.)
2) Even though libvirt adds iptables rules to allow forwarded traffic to pass the iptables hook, firewalld's higher level "rich rules" don't yet have the ability to configure the acceptance of forwarded traffic (traffic that is going somewhere beyond the host), so any traffic that needs to be forwarded from guests to the network beyond the host is rejected during the nftables hook by the default zone's "default reject" policy (which rejects all traffic in the zone not specifically allowed by the rules in the zone, whether that traffic is destined to be forwarded or locally received by the host).
libvirt can't send "direct" nftables rules (firewalld only supports direct/passthrough rules for iptables), so we can't solve this problem by just sending explicit nftables rules instead of explicit iptables rules (which, if it could be done, would place libvirt's rules in the same hook as firewalld's native rules, and thus eliminate the need for packets to be accepted by both libvirt's and firewalld's own rules).
However, we can take advantage of a quirk in firewalld zones that have a default policy of "accept" (meaning any packet that doesn't match a specific rule in the zone will be *accepted*) - this default accept will also accept forwarded traffic (not just traffic destined for the host).
Of course we don't want to modify firewalld's default zone in that way, because that would affect the filtering of traffic coming into the host from other interfaces using that zone. Instead, we will create a new zone called "libvirt". The libvirt zone will have a default policy of accept so that forwarded traffic can pass andn list
s/andn/and/
specific services that will be allowed into the host from guests (DNS, DHCP, SSH, and TFTP).
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

From: Laine Stump <laine@redhat.com> This patch restores broken guest network connectivity after a host firewalld is switched to using an nftables backend. It does this by adding libvirt networks' bridge interfaces to the new "libvirt" zone in firewalld. After this patch, the bridge interface of any network created by libvirt (when firewalld is active) will be added to the firewalld zone called "libvirt" if it exists (regardless of the firewalld backend setting). This behavior does *not* depend on whether or not libvirt has installed the libvirt zone file (set with "--with[out]-firewalld-zone" during the configure phase of the package build). If the libvirt zone doesn't exist (either because the package was configured to not install it, or possibly it was installed, but firewalld doesn't support rule priorities, resulting in a parse error), the bridge will remain in firewalld's default zone, which could be innocuous (in the case that the firewalld backend is iptables, guest networking will still function properly with the bridge in the default zone), or it could be disastrous (if the firewalld backend is nftables, we can be assured that guest networking will fail). In order to be unobtrusive in the former case, and informative in the latter, when the libvirt zone doesn't exist we then check the firewalld version to see if it's new enough to support the nftables backend, and then if the backend is actually set to nftables, before logging an error (and failing the net-start operation, since the network couldn't possibly work anyway). When the libvirt zone is used, network behavior is *slightly* different from behavior of previous libvirt. In the past, libvirt network behavior would be affected by the configuration of firewalld's default zone (usually "public"), but now it is affected only by the "libvirt" zone), and thus almost surely warrants a release note for any distro upgrading to libvirt 5.1 or above. Although it's unfortunate that we have to deal with a mandatory behavior change, the architecture of multiple hooks makes it impossible to *not* change behavior in some way, and the new behavior is arguably better (since it will now be possible to manage access to the host from virtual machines vs from public interfaces separately). Resolves: https://bugzilla.redhat.com/1638342 Creates-and-Resolves: https://bugzilla.redhat.com/1650320 Signed-off-by: Laine Stump <laine@laine.org> --- NB: I had considered that it might be useful to cache the results of checking the list of active zones, the firewalld version, or the firewalld backend, but in my tests of restarting libvirtd with 100 active networks, the full startup time (from the beginning of "systemctl restart libvirtd.service" until successful return from a subsequent "virsh list") showed 42 seconds and a bit regardless of whether or not I made those checks. This tells me that the amount of time to be saved by caching the results of a single call vs calling once for each network are insignificant relative to everything else that is being done. Because any cached values would need to be stored in the network driver state object, and thus require acquiring the driver-wide lock to update at potentially very different times (e.g. in the response to a dbus message informing us that firewalld was restarted, as well as while starting a new network from an API call) I consider the chance of a bug in my code causing an obscure deadlock sometime in the future to be a much greater concern than maybe saving 1/10th of a second out of 42 (and lock contention might eliminate the gain anyway(), so I have left the code to retrieve the list of zones once for each network start. Changes in V2: * split off from Patch 5. This patch only sets the libvirt zone if it exists (and attempts to somewhat document the behavior in firewall.html), it doesn't install the libvirt zone. * check for existence of libvirt zone before attempting to set it. * if libvirt zone doesn't exist, only log an error in the case that the firewalld version is new enough to have an nftables backend, and the backend is actually set to nftables. docs/firewall.html.in | 33 +++++++++++++++++++++ src/network/bridge_driver_linux.c | 48 +++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/docs/firewall.html.in b/docs/firewall.html.in index 0a50687c26..5d584e582e 100644 --- a/docs/firewall.html.in +++ b/docs/firewall.html.in @@ -129,6 +129,39 @@ MASQUERADE all -- * * 192.168.122.0/24 !192.168.122.0/24</pre> </li> </ul> + <h3><a id="fw-firewalld-and-virtual-network-driver">firewalld and the virtual network driver</a> + </h3> + <p> + If <a href="https://firewalld.org">firewalld</a> is active on + the host, libvirt will attempt to place the bridge interface of + a libvirt virtual network into the firewalld zone named + "libvirt" (thus making all guest->host traffic on that network + subject to the rules of the "libvirt" zone). This is done + because, if firewalld is using its nftables backend (available + since firewalld 0.6.0) the default firewalld zone (which would + be used if libvirt didn't explicitly set the zone) prevents + forwarding traffic from guests through the bridge, as well as + preventing DHCP, DNS, and most other traffic from guests to + host. The zone named "libvirt" is installed into the firewalld + configuration by libvirt (not by firewalld), and allows + forwarded traffic through the bridge as well as DHCP, DNS, TFTP, + and SSH traffic to the host - depending on firewalld's backend + this will be implemented via either iptables or nftables + rules. libvirt's own rules outlined above will *always* be + iptables rules regardless of which backend is in use by + firewalld. + </p> + <p> + NB: Prior to libvirt 5.1.0, the firewalld "libvirt" zone did not + exist, and prior to firewalld 0.7.0 a feature crucial to making + the "libvirt" zone operate properly (rich rule priority + settings) was not implemented in firewalld. In cases where one + or the other of the two packages is missing the necessary + functionality, it's still possible to have functional guest + networking by setting the firewalld backend to "iptables" (in + firewalld prior to 0.6.0, this was the only backend available). + </p> + <h3><a id="fw-network-filter-driver">The network filter driver</a> </h3> <p>This driver provides a fully configurable network filtering capability diff --git a/src/network/bridge_driver_linux.c b/src/network/bridge_driver_linux.c index e5e48c90f1..9d2e6877ae 100644 --- a/src/network/bridge_driver_linux.c +++ b/src/network/bridge_driver_linux.c @@ -27,6 +27,7 @@ #include "virstring.h" #include "virlog.h" #include "virfirewall.h" +#include "virfirewalld.h" #define VIR_FROM_THIS VIR_FROM_NONE @@ -670,6 +671,53 @@ int networkAddFirewallRules(virNetworkDefPtr def) virFirewallPtr fw = NULL; int ret = -1; + /* if firewalld is active, try to set the "libvirt" zone. This is + * desirable (for consistency) if firewalld is using the iptables + * backend, but is necessary (for basic network connectivity) if + * firewalld is using the nftables backend + */ + if (virFirewallDIsRegistered() == 0) { + + /* if the "libvirt" zone exists, then set it. If not, and + * if firewalld is using the nftables backend, then we + * need to log an error because the combination of + * nftables + default zone means that traffic cannot be + * forwarded (and even DHCP and DNS from guest to host + * will probably no be permitted by the default zone + */ + if (virFirewallDZoneExists("libvirt")) { + if (virFirewallDInterfaceSetZone(def->bridge, "libvirt") < 0) + goto cleanup; + } else { + unsigned long version; + int vresult = virFirewallDGetVersion(&version); + + if (vresult < 0) + goto cleanup; + + /* Support for nftables backend was added in firewalld + * 0.6.0. Support for rule priorities (required by the + * 'libvirt' zone, which should be installed by a + * libvirt package, *not* by firewalld) was not added + * until firewalld 0.7.0 (unless it was backported). + */ + if (version >= 6000 && + virFirewallDGetBackend() == VIR_FIREWALLD_BACKEND_NFTABLES) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("firewalld is set to use the nftables " + "backend, but the required firewalld " + "'libvirt' zone is missing. Either set " + "the firewalld backend to 'iptables', or " + "ensure that firewalld has a 'libvirt' " + "zone by upgrading firewalld to a " + "version supporting rule priorities " + "(0.7.0+) and/or rebuilding " + "libvirt with --with-firewalld-zone")); + goto cleanup; + } + } + } + fw = virFirewallNew(); virFirewallStartTransaction(fw, 0); -- 2.20.1

On Thu, Jan 31, 2019 at 08:24:56PM -0500, Laine Stump wrote:
From: Laine Stump <laine@redhat.com>
This patch restores broken guest network connectivity after a host firewalld is switched to using an nftables backend. It does this by adding libvirt networks' bridge interfaces to the new "libvirt" zone in firewalld.
After this patch, the bridge interface of any network created by libvirt (when firewalld is active) will be added to the firewalld zone called "libvirt" if it exists (regardless of the firewalld backend setting). This behavior does *not* depend on whether or not libvirt has installed the libvirt zone file (set with "--with[out]-firewalld-zone" during the configure phase of the package build).
If the libvirt zone doesn't exist (either because the package was configured to not install it, or possibly it was installed, but firewalld doesn't support rule priorities, resulting in a parse error), the bridge will remain in firewalld's default zone, which could be innocuous (in the case that the firewalld backend is iptables, guest networking will still function properly with the bridge in the default zone), or it could be disastrous (if the firewalld backend is nftables, we can be assured that guest networking will fail). In order to be unobtrusive in the former case, and informative in the latter, when the libvirt zone doesn't exist we then check the firewalld version to see if it's new enough to support the nftables backend, and then if the backend is actually set to nftables, before logging an error (and failing the net-start operation, since the network couldn't possibly work anyway).
When the libvirt zone is used, network behavior is *slightly* different from behavior of previous libvirt. In the past, libvirt network behavior would be affected by the configuration of firewalld's default zone (usually "public"), but now it is affected only by the "libvirt" zone), and thus almost surely warrants a release note for any distro upgrading to libvirt 5.1 or above. Although it's unfortunate that we have to deal with a mandatory behavior change, the architecture of multiple hooks makes it impossible to *not* change behavior in some way, and the new behavior is arguably better (since it will now be possible to manage access to the host from virtual machines vs from public interfaces separately).
Resolves: https://bugzilla.redhat.com/1638342 Creates-and-Resolves: https://bugzilla.redhat.com/1650320 Signed-off-by: Laine Stump <laine@laine.org> ---
NB: I had considered that it might be useful to cache the results of checking the list of active zones, the firewalld version, or the firewalld backend, but in my tests of restarting libvirtd with 100 active networks, the full startup time (from the beginning of "systemctl restart libvirtd.service" until successful return from a subsequent "virsh list") showed 42 seconds and a bit regardless of whether or not I made those checks. This tells me that the amount of time to be saved by caching the results of a single call vs calling once for each network are insignificant relative to everything else that is being done. Because any cached values would need to be stored in the network driver state object, and thus require acquiring the driver-wide lock to update at potentially very different times (e.g. in the response to a dbus message informing us that firewalld was restarted, as well as while starting a new network from an API call) I consider the chance of a bug in my code causing an obscure deadlock sometime in the future to be a much greater concern than maybe saving 1/10th of a second out of 42 (and lock contention might eliminate the gain anyway(), so I have left the code to retrieve the list of zones once for each network start.
Changes in V2:
* split off from Patch 5. This patch only sets the libvirt zone if it exists (and attempts to somewhat document the behavior in firewall.html), it doesn't install the libvirt zone.
* check for existence of libvirt zone before attempting to set it.
* if libvirt zone doesn't exist, only log an error in the case that the firewalld version is new enough to have an nftables backend, and the backend is actually set to nftables.
docs/firewall.html.in | 33 +++++++++++++++++++++ src/network/bridge_driver_linux.c | 48 +++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+)
diff --git a/docs/firewall.html.in b/docs/firewall.html.in index 0a50687c26..5d584e582e 100644 --- a/docs/firewall.html.in +++ b/docs/firewall.html.in @@ -129,6 +129,39 @@ MASQUERADE all -- * * 192.168.122.0/24 !192.168.122.0/24</pre> </li> </ul>
+ <h3><a id="fw-firewalld-and-virtual-network-driver">firewalld and the virtual network driver</a> + </h3> + <p> + If <a href="https://firewalld.org">firewalld</a> is active on + the host, libvirt will attempt to place the bridge interface of + a libvirt virtual network into the firewalld zone named + "libvirt" (thus making all guest->host traffic on that network + subject to the rules of the "libvirt" zone). This is done + because, if firewalld is using its nftables backend (available + since firewalld 0.6.0) the default firewalld zone (which would + be used if libvirt didn't explicitly set the zone) prevents + forwarding traffic from guests through the bridge, as well as + preventing DHCP, DNS, and most other traffic from guests to + host. The zone named "libvirt" is installed into the firewalld + configuration by libvirt (not by firewalld), and allows + forwarded traffic through the bridge as well as DHCP, DNS, TFTP, + and SSH traffic to the host - depending on firewalld's backend + this will be implemented via either iptables or nftables + rules. libvirt's own rules outlined above will *always* be + iptables rules regardless of which backend is in use by + firewalld. + </p> + <p> + NB: Prior to libvirt 5.1.0, the firewalld "libvirt" zone did not + exist, and prior to firewalld 0.7.0 a feature crucial to making + the "libvirt" zone operate properly (rich rule priority + settings) was not implemented in firewalld. In cases where one + or the other of the two packages is missing the necessary + functionality, it's still possible to have functional guest + networking by setting the firewalld backend to "iptables" (in + firewalld prior to 0.6.0, this was the only backend available). + </p> + <h3><a id="fw-network-filter-driver">The network filter driver</a> </h3> <p>This driver provides a fully configurable network filtering capability diff --git a/src/network/bridge_driver_linux.c b/src/network/bridge_driver_linux.c index e5e48c90f1..9d2e6877ae 100644 --- a/src/network/bridge_driver_linux.c +++ b/src/network/bridge_driver_linux.c @@ -27,6 +27,7 @@ #include "virstring.h" #include "virlog.h" #include "virfirewall.h" +#include "virfirewalld.h"
#define VIR_FROM_THIS VIR_FROM_NONE
@@ -670,6 +671,53 @@ int networkAddFirewallRules(virNetworkDefPtr def) virFirewallPtr fw = NULL; int ret = -1;
+ /* if firewalld is active, try to set the "libvirt" zone. This is + * desirable (for consistency) if firewalld is using the iptables + * backend, but is necessary (for basic network connectivity) if + * firewalld is using the nftables backend + */ + if (virFirewallDIsRegistered() == 0) {
The indentation here is odd - its 4 spaces too much for the surrounding context. With that fixed Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

On 2/1/19 8:24 AM, Daniel P. Berrangé wrote:
On Thu, Jan 31, 2019 at 08:24:56PM -0500, Laine Stump wrote:
From: Laine Stump <laine@redhat.com>
This patch restores broken guest network connectivity after a host firewalld is switched to using an nftables backend. It does this by adding libvirt networks' bridge interfaces to the new "libvirt" zone in firewalld.
After this patch, the bridge interface of any network created by libvirt (when firewalld is active) will be added to the firewalld zone called "libvirt" if it exists (regardless of the firewalld backend setting). This behavior does *not* depend on whether or not libvirt has installed the libvirt zone file (set with "--with[out]-firewalld-zone" during the configure phase of the package build).
If the libvirt zone doesn't exist (either because the package was configured to not install it, or possibly it was installed, but firewalld doesn't support rule priorities, resulting in a parse error), the bridge will remain in firewalld's default zone, which could be innocuous (in the case that the firewalld backend is iptables, guest networking will still function properly with the bridge in the default zone), or it could be disastrous (if the firewalld backend is nftables, we can be assured that guest networking will fail). In order to be unobtrusive in the former case, and informative in the latter, when the libvirt zone doesn't exist we then check the firewalld version to see if it's new enough to support the nftables backend, and then if the backend is actually set to nftables, before logging an error (and failing the net-start operation, since the network couldn't possibly work anyway).
When the libvirt zone is used, network behavior is *slightly* different from behavior of previous libvirt. In the past, libvirt network behavior would be affected by the configuration of firewalld's default zone (usually "public"), but now it is affected only by the "libvirt" zone), and thus almost surely warrants a release note for any distro upgrading to libvirt 5.1 or above. Although it's unfortunate that we have to deal with a mandatory behavior change, the architecture of multiple hooks makes it impossible to *not* change behavior in some way, and the new behavior is arguably better (since it will now be possible to manage access to the host from virtual machines vs from public interfaces separately).
Resolves: https://bugzilla.redhat.com/1638342 Creates-and-Resolves: https://bugzilla.redhat.com/1650320 Signed-off-by: Laine Stump <laine@laine.org> ---
NB: I had considered that it might be useful to cache the results of checking the list of active zones, the firewalld version, or the firewalld backend, but in my tests of restarting libvirtd with 100 active networks, the full startup time (from the beginning of "systemctl restart libvirtd.service" until successful return from a subsequent "virsh list") showed 42 seconds and a bit regardless of whether or not I made those checks. This tells me that the amount of time to be saved by caching the results of a single call vs calling once for each network are insignificant relative to everything else that is being done. Because any cached values would need to be stored in the network driver state object, and thus require acquiring the driver-wide lock to update at potentially very different times (e.g. in the response to a dbus message informing us that firewalld was restarted, as well as while starting a new network from an API call) I consider the chance of a bug in my code causing an obscure deadlock sometime in the future to be a much greater concern than maybe saving 1/10th of a second out of 42 (and lock contention might eliminate the gain anyway(), so I have left the code to retrieve the list of zones once for each network start.
Changes in V2:
* split off from Patch 5. This patch only sets the libvirt zone if it exists (and attempts to somewhat document the behavior in firewall.html), it doesn't install the libvirt zone.
* check for existence of libvirt zone before attempting to set it.
* if libvirt zone doesn't exist, only log an error in the case that the firewalld version is new enough to have an nftables backend, and the backend is actually set to nftables.
docs/firewall.html.in | 33 +++++++++++++++++++++ src/network/bridge_driver_linux.c | 48 +++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+)
diff --git a/docs/firewall.html.in b/docs/firewall.html.in index 0a50687c26..5d584e582e 100644 --- a/docs/firewall.html.in +++ b/docs/firewall.html.in @@ -129,6 +129,39 @@ MASQUERADE all -- * * 192.168.122.0/24 !192.168.122.0/24</pre> </li> </ul>
+ <h3><a id="fw-firewalld-and-virtual-network-driver">firewalld and the virtual network driver</a> + </h3> + <p> + If <a href="https://firewalld.org">firewalld</a> is active on + the host, libvirt will attempt to place the bridge interface of + a libvirt virtual network into the firewalld zone named + "libvirt" (thus making all guest->host traffic on that network + subject to the rules of the "libvirt" zone). This is done + because, if firewalld is using its nftables backend (available + since firewalld 0.6.0) the default firewalld zone (which would + be used if libvirt didn't explicitly set the zone) prevents + forwarding traffic from guests through the bridge, as well as + preventing DHCP, DNS, and most other traffic from guests to + host. The zone named "libvirt" is installed into the firewalld + configuration by libvirt (not by firewalld), and allows + forwarded traffic through the bridge as well as DHCP, DNS, TFTP, + and SSH traffic to the host - depending on firewalld's backend + this will be implemented via either iptables or nftables + rules. libvirt's own rules outlined above will *always* be + iptables rules regardless of which backend is in use by + firewalld. + </p> + <p> + NB: Prior to libvirt 5.1.0, the firewalld "libvirt" zone did not + exist, and prior to firewalld 0.7.0 a feature crucial to making + the "libvirt" zone operate properly (rich rule priority + settings) was not implemented in firewalld. In cases where one + or the other of the two packages is missing the necessary + functionality, it's still possible to have functional guest + networking by setting the firewalld backend to "iptables" (in + firewalld prior to 0.6.0, this was the only backend available). + </p> + <h3><a id="fw-network-filter-driver">The network filter driver</a> </h3> <p>This driver provides a fully configurable network filtering capability diff --git a/src/network/bridge_driver_linux.c b/src/network/bridge_driver_linux.c index e5e48c90f1..9d2e6877ae 100644 --- a/src/network/bridge_driver_linux.c +++ b/src/network/bridge_driver_linux.c @@ -27,6 +27,7 @@ #include "virstring.h" #include "virlog.h" #include "virfirewall.h" +#include "virfirewalld.h"
#define VIR_FROM_THIS VIR_FROM_NONE
@@ -670,6 +671,53 @@ int networkAddFirewallRules(virNetworkDefPtr def) virFirewallPtr fw = NULL; int ret = -1;
+ /* if firewalld is active, try to set the "libvirt" zone. This is + * desirable (for consistency) if firewalld is using the iptables + * backend, but is necessary (for basic network connectivity) if + * firewalld is using the nftables backend + */ + if (virFirewallDIsRegistered() == 0) {
The indentation here is odd - its 4 spaces too much for the surrounding context.
I did that to eliminate the pointless churn in the next patch, which would have just indented all this code by 4 spaces, thus making it more difficult to see what exactly was changed (you would have to either look through the diff line by line, or apply it and use diff -w). So should I still reindent? (I guess now that you've seen the trimmed down patches, this version has already done its job, so there's not much downside unless someone comes along 5 years from now and is interested in exactly the next commit, which seems unlikely...)
With that fixed
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Regards, Daniel

On Fri, Feb 01, 2019 at 10:10:21AM -0500, Laine Stump wrote:
On 2/1/19 8:24 AM, Daniel P. Berrangé wrote:
On Thu, Jan 31, 2019 at 08:24:56PM -0500, Laine Stump wrote:
From: Laine Stump <laine@redhat.com>
This patch restores broken guest network connectivity after a host firewalld is switched to using an nftables backend. It does this by adding libvirt networks' bridge interfaces to the new "libvirt" zone in firewalld.
After this patch, the bridge interface of any network created by libvirt (when firewalld is active) will be added to the firewalld zone called "libvirt" if it exists (regardless of the firewalld backend setting). This behavior does *not* depend on whether or not libvirt has installed the libvirt zone file (set with "--with[out]-firewalld-zone" during the configure phase of the package build).
If the libvirt zone doesn't exist (either because the package was configured to not install it, or possibly it was installed, but firewalld doesn't support rule priorities, resulting in a parse error), the bridge will remain in firewalld's default zone, which could be innocuous (in the case that the firewalld backend is iptables, guest networking will still function properly with the bridge in the default zone), or it could be disastrous (if the firewalld backend is nftables, we can be assured that guest networking will fail). In order to be unobtrusive in the former case, and informative in the latter, when the libvirt zone doesn't exist we then check the firewalld version to see if it's new enough to support the nftables backend, and then if the backend is actually set to nftables, before logging an error (and failing the net-start operation, since the network couldn't possibly work anyway).
When the libvirt zone is used, network behavior is *slightly* different from behavior of previous libvirt. In the past, libvirt network behavior would be affected by the configuration of firewalld's default zone (usually "public"), but now it is affected only by the "libvirt" zone), and thus almost surely warrants a release note for any distro upgrading to libvirt 5.1 or above. Although it's unfortunate that we have to deal with a mandatory behavior change, the architecture of multiple hooks makes it impossible to *not* change behavior in some way, and the new behavior is arguably better (since it will now be possible to manage access to the host from virtual machines vs from public interfaces separately).
Resolves: https://bugzilla.redhat.com/1638342 Creates-and-Resolves: https://bugzilla.redhat.com/1650320 Signed-off-by: Laine Stump <laine@laine.org> ---
NB: I had considered that it might be useful to cache the results of checking the list of active zones, the firewalld version, or the firewalld backend, but in my tests of restarting libvirtd with 100 active networks, the full startup time (from the beginning of "systemctl restart libvirtd.service" until successful return from a subsequent "virsh list") showed 42 seconds and a bit regardless of whether or not I made those checks. This tells me that the amount of time to be saved by caching the results of a single call vs calling once for each network are insignificant relative to everything else that is being done. Because any cached values would need to be stored in the network driver state object, and thus require acquiring the driver-wide lock to update at potentially very different times (e.g. in the response to a dbus message informing us that firewalld was restarted, as well as while starting a new network from an API call) I consider the chance of a bug in my code causing an obscure deadlock sometime in the future to be a much greater concern than maybe saving 1/10th of a second out of 42 (and lock contention might eliminate the gain anyway(), so I have left the code to retrieve the list of zones once for each network start.
Changes in V2:
* split off from Patch 5. This patch only sets the libvirt zone if it exists (and attempts to somewhat document the behavior in firewall.html), it doesn't install the libvirt zone.
* check for existence of libvirt zone before attempting to set it.
* if libvirt zone doesn't exist, only log an error in the case that the firewalld version is new enough to have an nftables backend, and the backend is actually set to nftables.
docs/firewall.html.in | 33 +++++++++++++++++++++ src/network/bridge_driver_linux.c | 48 +++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+)
diff --git a/docs/firewall.html.in b/docs/firewall.html.in index 0a50687c26..5d584e582e 100644 --- a/docs/firewall.html.in +++ b/docs/firewall.html.in @@ -129,6 +129,39 @@ MASQUERADE all -- * * 192.168.122.0/24 !192.168.122.0/24</pre> </li> </ul> + <h3><a id="fw-firewalld-and-virtual-network-driver">firewalld and the virtual network driver</a> + </h3> + <p> + If <a href="https://firewalld.org">firewalld</a> is active on + the host, libvirt will attempt to place the bridge interface of + a libvirt virtual network into the firewalld zone named + "libvirt" (thus making all guest->host traffic on that network + subject to the rules of the "libvirt" zone). This is done + because, if firewalld is using its nftables backend (available + since firewalld 0.6.0) the default firewalld zone (which would + be used if libvirt didn't explicitly set the zone) prevents + forwarding traffic from guests through the bridge, as well as + preventing DHCP, DNS, and most other traffic from guests to + host. The zone named "libvirt" is installed into the firewalld + configuration by libvirt (not by firewalld), and allows + forwarded traffic through the bridge as well as DHCP, DNS, TFTP, + and SSH traffic to the host - depending on firewalld's backend + this will be implemented via either iptables or nftables + rules. libvirt's own rules outlined above will *always* be + iptables rules regardless of which backend is in use by + firewalld. + </p> + <p> + NB: Prior to libvirt 5.1.0, the firewalld "libvirt" zone did not + exist, and prior to firewalld 0.7.0 a feature crucial to making + the "libvirt" zone operate properly (rich rule priority + settings) was not implemented in firewalld. In cases where one + or the other of the two packages is missing the necessary + functionality, it's still possible to have functional guest + networking by setting the firewalld backend to "iptables" (in + firewalld prior to 0.6.0, this was the only backend available). + </p> + <h3><a id="fw-network-filter-driver">The network filter driver</a> </h3> <p>This driver provides a fully configurable network filtering capability diff --git a/src/network/bridge_driver_linux.c b/src/network/bridge_driver_linux.c index e5e48c90f1..9d2e6877ae 100644 --- a/src/network/bridge_driver_linux.c +++ b/src/network/bridge_driver_linux.c @@ -27,6 +27,7 @@ #include "virstring.h" #include "virlog.h" #include "virfirewall.h" +#include "virfirewalld.h" #define VIR_FROM_THIS VIR_FROM_NONE @@ -670,6 +671,53 @@ int networkAddFirewallRules(virNetworkDefPtr def) virFirewallPtr fw = NULL; int ret = -1; + /* if firewalld is active, try to set the "libvirt" zone. This is + * desirable (for consistency) if firewalld is using the iptables + * backend, but is necessary (for basic network connectivity) if + * firewalld is using the nftables backend + */ + if (virFirewallDIsRegistered() == 0) {
The indentation here is odd - its 4 spaces too much for the surrounding context.
I did that to eliminate the pointless churn in the next patch, which would have just indented all this code by 4 spaces, thus making it more difficult to see what exactly was changed (you would have to either look through the diff line by line, or apply it and use diff -w).
So should I still reindent? (I guess now that you've seen the trimmed down patches, this version has already done its job, so there's not much downside unless someone comes along 5 years from now and is interested in exactly the next commit, which seems unlikely...)
I'd suggest fixing this before pushing. If someone is looking back at history they can tell git to show the next patch without whitespace changes. Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

Since we're setting the zone anyway, it will be useful to allow setting a different (custom) zone for each network. This will be done by adding a "zone" attribute to the "bridge" element, e.g.: ... <bridge name='virbr0' zone='myzone'/> ... If a zone is specified in the config and it can't be honored, this will be an error. Signed-off-by: Laine Stump <laine@laine.org> --- Change from V1: move news.xml additions to a separate patch, as requested. docs/firewall.html.in | 5 +++++ docs/formatnetwork.html.in | 17 +++++++++++++++++ docs/schemas/basictypes.rng | 6 ++++++ docs/schemas/network.rng | 6 ++++++ src/conf/network_conf.c | 14 ++++++++++++-- src/conf/network_conf.h | 1 + src/network/bridge_driver_linux.c | 19 +++++++++++++++++++ tests/networkxml2xmlin/routed-network.xml | 2 +- tests/networkxml2xmlout/routed-network.xml | 2 +- 9 files changed, 68 insertions(+), 4 deletions(-) diff --git a/docs/firewall.html.in b/docs/firewall.html.in index 5d584e582e..e86ab0d974 100644 --- a/docs/firewall.html.in +++ b/docs/firewall.html.in @@ -151,6 +151,11 @@ MASQUERADE all -- * * 192.168.122.0/24 !192.168.122.0/24</pre> iptables rules regardless of which backend is in use by firewalld. </p> + <p> + NB: It is possible to manually set the firewalld zone for a + network's interface with the "zone" attribute of the network's + "bridge" element. + </p> <p> NB: Prior to libvirt 5.1.0, the firewalld "libvirt" zone did not exist, and prior to firewalld 0.7.0 a feature crucial to making diff --git a/docs/formatnetwork.html.in b/docs/formatnetwork.html.in index 156cfae4ec..509cca9e8b 100644 --- a/docs/formatnetwork.html.in +++ b/docs/formatnetwork.html.in @@ -152,6 +152,23 @@ <span class="since">Since 1.2.11, requires kernel 3.17 or newer</span> </p> + + <p> + The optional <code>zone</code> attribute of + the <code>bridge</code> element is used to specify + the <a href="https://firewalld.org">firewalld</a> + zone for the bridge of a network with <code>forward</code> + mode of "nat", "route", "open", or one with + no <code>forward</code> specified. By default, the bridges + of all virtual networks with these forward modes are placed + in the firewalld zone named "libvirt", which permits + incoming DNS, DHCP, TFTP, and SSH to the host from guests on + the network. This behavior can be changed either by + modifying the libvirt zone (using firewalld management + tools), or by placing the network in a different zone (which + will also be managed using firewalld tools). + <span class="since">Since 5.1.0</span> + </p> </dd> <dt><code>mtu</code></dt> diff --git a/docs/schemas/basictypes.rng b/docs/schemas/basictypes.rng index 9a63720ff7..9b3dcad4a5 100644 --- a/docs/schemas/basictypes.rng +++ b/docs/schemas/basictypes.rng @@ -279,6 +279,12 @@ </data> </define> + <define name="zoneName"> + <data type="string"> + <param name="pattern">[a-zA-Z0-9_\-]+</param> + </data> + </define> + <define name="filePath"> <data type="string"> <param name="pattern">.+</param> diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index f37c422bf3..2a6e3358fd 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -58,6 +58,12 @@ </attribute> </optional> + <optional> + <attribute name="zone"> + <ref name="zoneName"/> + </attribute> + </optional> + <optional> <attribute name="stp"> <ref name="virOnOff"/> diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index e035d8aba7..b09cb1dae2 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -203,6 +203,7 @@ virNetworkDefFree(virNetworkDefPtr def) VIR_FREE(def->name); VIR_FREE(def->bridge); + VIR_FREE(def->bridgeZone); VIR_FREE(def->domain); virNetworkForwardDefClear(&def->forward); @@ -1684,6 +1685,7 @@ virNetworkDefParseXML(xmlXPathContextPtr ctxt) /* Parse bridge information */ def->bridge = virXPathString("string(./bridge[1]/@name)", ctxt); + def->bridgeZone = virXPathString("string(./bridge[1]/@zone)", ctxt); stp = virXPathString("string(./bridge[1]/@stp)", ctxt); def->stp = (stp && STREQ(stp, "off")) ? false : true; @@ -1920,6 +1922,13 @@ virNetworkDefParseXML(xmlXPathContextPtr ctxt) def->name); goto error; } + if (def->bridgeZone) { + virReportError(VIR_ERR_XML_ERROR, + _("bridge zone not allowed in %s mode (network '%s')"), + virNetworkForwardTypeToString(def->forward.type), + def->name); + goto error; + } if (def->macTableManager) { virReportError(VIR_ERR_XML_ERROR, _("bridge macTableManager setting not allowed " @@ -1931,9 +1940,9 @@ virNetworkDefParseXML(xmlXPathContextPtr ctxt) ATTRIBUTE_FALLTHROUGH; case VIR_NETWORK_FORWARD_BRIDGE: - if (def->delay || stp) { + if (def->delay || stp || def->bridgeZone) { virReportError(VIR_ERR_XML_ERROR, - _("bridge delay/stp options only allowed in " + _("bridge delay/stp/zone options only allowed in " "route, nat, and isolated mode, not in %s " "(network '%s')"), virNetworkForwardTypeToString(def->forward.type), @@ -2508,6 +2517,7 @@ virNetworkDefFormatBuf(virBufferPtr buf, if (hasbridge || def->bridge || def->macTableManager) { virBufferAddLit(buf, "<bridge"); virBufferEscapeString(buf, " name='%s'", def->bridge); + virBufferEscapeString(buf, " zone='%s'", def->bridgeZone); if (hasbridge) virBufferAsprintf(buf, " stp='%s' delay='%ld'", def->stp ? "on" : "off", def->delay); diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index c630674300..673f70cc68 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -235,6 +235,7 @@ struct _virNetworkDef { int connections; /* # of guest interfaces connected to this network */ char *bridge; /* Name of bridge device */ + char *bridgeZone; /* name of firewalld zone for bridge */ int macTableManager; /* enum virNetworkBridgeMACTableManager */ char *domain; int domainLocalOnly; /* enum virTristateBool: yes disables dns forwarding */ diff --git a/src/network/bridge_driver_linux.c b/src/network/bridge_driver_linux.c index 9d2e6877ae..b10d0a6c4d 100644 --- a/src/network/bridge_driver_linux.c +++ b/src/network/bridge_driver_linux.c @@ -671,6 +671,24 @@ int networkAddFirewallRules(virNetworkDefPtr def) virFirewallPtr fw = NULL; int ret = -1; + if (def->bridgeZone) { + + /* if a firewalld zone has been specified, fail/log an error + * if we can't honor it + */ + if (virFirewallDIsRegistered() < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("zone %s requested for network %s " + "but firewalld is not active"), + def->bridgeZone, def->name); + goto cleanup; + } + + if (virFirewallDInterfaceSetZone(def->bridge, def->bridgeZone) < 0) + goto cleanup; + + } else { + /* if firewalld is active, try to set the "libvirt" zone. This is * desirable (for consistency) if firewalld is using the iptables * backend, but is necessary (for basic network connectivity) if @@ -717,6 +735,7 @@ int networkAddFirewallRules(virNetworkDefPtr def) } } } + } fw = virFirewallNew(); diff --git a/tests/networkxml2xmlin/routed-network.xml b/tests/networkxml2xmlin/routed-network.xml index ab5e15b1f6..fce01df132 100644 --- a/tests/networkxml2xmlin/routed-network.xml +++ b/tests/networkxml2xmlin/routed-network.xml @@ -1,7 +1,7 @@ <network> <name>local</name> <uuid>81ff0d90-c91e-6742-64da-4a736edb9a9b</uuid> - <bridge name="virbr1"/> + <bridge name="virbr1" zone="myzone"/> <mac address='12:34:56:78:9A:BC'/> <forward mode="route" dev="eth1"/> <ip address="192.168.122.1" netmask="255.255.255.0"> diff --git a/tests/networkxml2xmlout/routed-network.xml b/tests/networkxml2xmlout/routed-network.xml index 81abf06e9f..2e13cf4ffa 100644 --- a/tests/networkxml2xmlout/routed-network.xml +++ b/tests/networkxml2xmlout/routed-network.xml @@ -4,7 +4,7 @@ <forward dev='eth1' mode='route'> <interface dev='eth1'/> </forward> - <bridge name='virbr1' stp='on' delay='0'/> + <bridge name='virbr1' zone='myzone' stp='on' delay='0'/> <mac address='12:34:56:78:9a:bc'/> <ip address='192.168.122.1' netmask='255.255.255.0'> </ip> -- 2.20.1

On Thu, Jan 31, 2019 at 08:24:57PM -0500, Laine Stump wrote:
Since we're setting the zone anyway, it will be useful to allow setting a different (custom) zone for each network. This will be done by adding a "zone" attribute to the "bridge" element, e.g.:
... <bridge name='virbr0' zone='myzone'/> ...
If a zone is specified in the config and it can't be honored, this will be an error.
Signed-off-by: Laine Stump <laine@laine.org> ---
Change from V1: move news.xml additions to a separate patch, as requested.
docs/firewall.html.in | 5 +++++ docs/formatnetwork.html.in | 17 +++++++++++++++++ docs/schemas/basictypes.rng | 6 ++++++ docs/schemas/network.rng | 6 ++++++ src/conf/network_conf.c | 14 ++++++++++++-- src/conf/network_conf.h | 1 + src/network/bridge_driver_linux.c | 19 +++++++++++++++++++ tests/networkxml2xmlin/routed-network.xml | 2 +- tests/networkxml2xmlout/routed-network.xml | 2 +- 9 files changed, 68 insertions(+), 4 deletions(-)
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

Signed-off-by: Laine Stump <laine@laine.org> --- New in V2. Split off from previous patch. docs/news.xml | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/docs/news.xml b/docs/news.xml index 5759a9e178..f47fec90b3 100644 --- a/docs/news.xml +++ b/docs/news.xml @@ -46,10 +46,50 @@ configuration. </description> </change> + <change> + <summary> + network: support setting a firewalld "zone" for virtual network bridges + </summary> + <description> + All libvirt virtual networks with bridges managed by libvirt + (i.e. those with forward mode of "nat", "route", "open", or + no forward mode) will now be placed in a special firewalld + zone called "libvirt" by default. The zone of any network + bridge can be changed using the <code>zone</code> attribute + of the network's <code>bridge</code> element. + </description> + </change> </section> <section title="Improvements"> </section> <section title="Bug fixes"> + <change> + <summary> + network: fix virtual networks on systems using firewalld+nftables + </summary> + <description> + Because of the transitional state of firewalld's new support + for nftables, not all iptables features required by libvirt + are yet available, so libvirt must continue to use iptables + for its own packet filtering rules even when the firewalld + backend is set to use nftables. However, due to the way + iptables support is implemented in kernels using nftables + (iptables rules are converted to nftables rules and + processed in a separate hook from the native nftables + rules), guest networking was broken on hosts with firewalld + configured to use nftables as the backend. This has been + fixed by putting libvirt-managed bridges in their own + firewalld zone, so that guest traffic can be forwarded + beyond the host and host services can be exposed to guests + on the virtual network without opening up those same + services to the rest of the physical network. This means + that host access from virtual machines is no longer + controlled by the firewalld default zone (usually "public"), + but rather by the new firewalld zone called "libvirt" + (unless configured otherwise using the new zone + attribute of the network bridge element). + </description> + </change> </section> </release> <release version="v5.0.0" date="2019-01-15"> -- 2.20.1

On Thu, Jan 31, 2019 at 08:24:58PM -0500, Laine Stump wrote:
Signed-off-by: Laine Stump <laine@laine.org> ---
New in V2. Split off from previous patch.
docs/news.xml | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+)
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
participants (3)
-
Daniel P. Berrangé
-
Eric Garver
-
Laine Stump