/* * nwfilter_driver.c: core driver for network filter APIs * (based on storage_driver.c) * * Copyright (C) 2006-2011, 2014 Red Hat, Inc. * Copyright (C) 2006-2008 Daniel P. Berrange * Copyright (C) 2010 IBM Corporation * Copyright (C) 2010 Stefan Berger * * 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 * . */ #include #include "virgdbus.h" #include "virlog.h" #include "internal.h" #include "virerror.h" #include "datatypes.h" #include "nwfilter_driver.h" #include "nwfilter_gentech_driver.h" #include "configmake.h" #include "virpidfile.h" #include "viraccessapicheck.h" #include "nwfilter_ipaddrmap.h" #include "nwfilter_dhcpsnoop.h" #include "nwfilter_learnipaddr.h" #define VIR_FROM_THIS VIR_FROM_NWFILTER VIR_LOG_INIT("nwfilter.nwfilter_driver"); static virNWFilterDriverState *driver; static int nwfilterStateReload(void); static virMutex driverMutex = VIR_MUTEX_INITIALIZER; #ifdef WITH_FIREWALLD static void nwfilterStateReloadThread(void *opaque G_GNUC_UNUSED) { VIR_INFO("Reloading configuration on firewalld reload/restart"); nwfilterStateReload(); } static void nwfilterFirewalldDBusSignalCallback(GDBusConnection *connection G_GNUC_UNUSED, const char *senderName G_GNUC_UNUSED, const char *objectPath G_GNUC_UNUSED, const char *interfaceName G_GNUC_UNUSED, const char *signalName G_GNUC_UNUSED, GVariant *parameters G_GNUC_UNUSED, gpointer user_data G_GNUC_UNUSED) { virThread thr; if (virThreadCreateFull(&thr, false, nwfilterStateReloadThread, "firewall-reload", false, NULL) < 0) { /* * Not much we can do on error here except log it. */ VIR_ERROR(_("Failed to create thread to handle firewall reload/restart")); } } static unsigned int restartID; static unsigned int reloadID; static void nwfilterDriverRemoveDBusMatches(void) { GDBusConnection *sysbus = virGDBusGetSystemBus(); if (!sysbus) return; if (restartID != 0) { g_dbus_connection_signal_unsubscribe(sysbus, restartID); restartID = 0; } if (reloadID != 0) { g_dbus_connection_signal_unsubscribe(sysbus, reloadID); reloadID = 0; } } /** * virNWFilterDriverInstallDBusMatches * * Startup DBus matches for monitoring the state of firewalld */ static void nwfilterDriverInstallDBusMatches(GDBusConnection *sysbus) { restartID = g_dbus_connection_signal_subscribe(sysbus, NULL, "org.freedesktop.DBus", "NameOwnerChanged", NULL, "org.fedoraproject.FirewallD1", G_DBUS_SIGNAL_FLAGS_NONE, nwfilterFirewalldDBusSignalCallback, NULL, NULL); reloadID = g_dbus_connection_signal_subscribe(sysbus, NULL, "org.fedoraproject.FirewallD1", "Reloaded", NULL, NULL, G_DBUS_SIGNAL_FLAGS_NONE, nwfilterFirewalldDBusSignalCallback, NULL, NULL); } #else /* WITH_FIREWALLD */ static void nwfilterDriverRemoveDBusMatches(void) { } static void nwfilterDriverInstallDBusMatches(GDBusConnection *sysbus G_GNUC_UNUSED) { } #endif /* WITH_FIREWALLD */ static int virNWFilterTriggerRebuildImpl(void *opaque) { virNWFilterDriverState *nwdriver = opaque; return virNWFilterBuildAll(nwdriver, true); } static int nwfilterStateCleanupLocked(void) { if (!driver) return -1; if (driver->privileged) { virNWFilterConfLayerShutdown(); virNWFilterDHCPSnoopShutdown(); virNWFilterLearnShutdown(); virNWFilterIPAddrMapShutdown(); virNWFilterTechDriversShutdown(); nwfilterDriverRemoveDBusMatches(); if (driver->lockFD != -1) virPidFileRelease(driver->stateDir, "driver", driver->lockFD); g_free(driver->stateDir); g_free(driver->configDir); g_free(driver->bindingDir); } virObjectUnref(driver->bindings); /* free inactive nwfilters */ virNWFilterObjListFree(driver->nwfilters); if (driver->updateLockInitialized) virMutexDestroy(&driver->updateLock); g_clear_pointer(&driver, g_free); return 0; } /** * nwfilterStateCleanup: * * Shutdown the nwfilter driver, it will stop all active nwfilters */ static int nwfilterStateCleanup(void) { VIR_LOCK_GUARD lock = virLockGuardLock(&driverMutex); return nwfilterStateCleanupLocked(); } /** * nwfilterStateInitialize: * * Initialization function for the QEMU daemon */ static int nwfilterStateInitialize(bool privileged, const char *root, bool monolithic G_GNUC_UNUSED, virStateInhibitCallback callback G_GNUC_UNUSED, void *opaque G_GNUC_UNUSED) { VIR_LOCK_GUARD lock = virLockGuardLock(&driverMutex); GDBusConnection *sysbus = NULL; if (root != NULL) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("Driver does not support embedded mode")); return VIR_DRV_STATE_INIT_ERROR; } if (virGDBusHasSystemBus() && !(sysbus = virGDBusGetSystemBus())) return VIR_DRV_STATE_INIT_ERROR; driver = g_new0(virNWFilterDriverState, 1); driver->lockFD = -1; if (virMutexInitRecursive(&driver->updateLock) < 0) goto error; driver->updateLockInitialized = true; driver->privileged = privileged; if (!(driver->nwfilters = virNWFilterObjListNew())) goto error; if (!(driver->bindings = virNWFilterBindingObjListNew())) goto error; if (!privileged) return VIR_DRV_STATE_INIT_SKIPPED; driver->stateDir = g_strdup(RUNSTATEDIR "/libvirt/nwfilter"); if (g_mkdir_with_parents(driver->stateDir, S_IRWXU) < 0) { virReportSystemError(errno, _("cannot create state directory '%1$s'"), driver->stateDir); goto error; } if ((driver->lockFD = virPidFileAcquire(driver->stateDir, "driver", getpid())) < 0) goto error; if (virNWFilterIPAddrMapInit() < 0) goto error; if (virNWFilterLearnInit() < 0) goto error; if (virNWFilterDHCPSnoopInit() < 0) goto error; if (virNWFilterTechDriversInit(privileged) < 0) goto error; if (virNWFilterConfLayerInit(virNWFilterTriggerRebuildImpl, driver) < 0) goto error; /* * startup the DBus late so we don't get a reload signal while * initializing */ if (sysbus) nwfilterDriverInstallDBusMatches(sysbus); driver->configDir = g_strdup(SYSCONFDIR "/libvirt/nwfilter"); if (g_mkdir_with_parents(driver->configDir, S_IRWXU) < 0) { virReportSystemError(errno, _("cannot create config directory '%1$s'"), driver->configDir); goto error; } driver->bindingDir = g_strdup(RUNSTATEDIR "/libvirt/nwfilter-binding"); if (g_mkdir_with_parents(driver->bindingDir, S_IRWXU) < 0) { virReportSystemError(errno, _("cannot create config directory '%1$s'"), driver->bindingDir); goto error; } if (virNWFilterObjListLoadAllConfigs(driver->nwfilters, driver->configDir) < 0) goto error; if (virNWFilterBindingObjListLoadAllConfigs(driver->bindings, driver->bindingDir) < 0) goto error; if (virNWFilterBuildAll(driver, false) < 0) goto error; return VIR_DRV_STATE_INIT_COMPLETE; error: nwfilterStateCleanupLocked(); return VIR_DRV_STATE_INIT_ERROR; } /** * nwfilterStateReload: * * Function to restart the nwfilter driver, it will recheck the configuration * files and update its state */ static int nwfilterStateReload(void) { if (!driver) return -1; if (!driver->privileged) return 0; virNWFilterDHCPSnoopEnd(NULL); /* shut down all threads -- they will be restarted if necessary */ virNWFilterLearnThreadsTerminate(true); VIR_WITH_MUTEX_LOCK_GUARD(&driverMutex) { VIR_WITH_MUTEX_LOCK_GUARD(&driver->updateLock) { virNWFilterObjListLoadAllConfigs(driver->nwfilters, driver->configDir); } virNWFilterBuildAll(driver, false); } return 0; } static virDrvOpenStatus nwfilterConnectOpen(virConnectPtr conn, virConnectAuthPtr auth G_GNUC_UNUSED, virConf *conf G_GNUC_UNUSED, unsigned int flags) { virCheckFlags(VIR_CONNECT_RO, VIR_DRV_OPEN_ERROR); if (driver == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("nwfilter state driver is not active")); return VIR_DRV_OPEN_ERROR; } if (STRNEQ(conn->uri->path, "/system")) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unexpected nwfilter URI path '%1$s', try nwfilter:///system"), conn->uri->path); return VIR_DRV_OPEN_ERROR; } if (virConnectOpenEnsureACL(conn) < 0) return VIR_DRV_OPEN_ERROR; return VIR_DRV_OPEN_SUCCESS; } static int nwfilterConnectClose(virConnectPtr conn G_GNUC_UNUSED) { return 0; } static int nwfilterConnectIsSecure(virConnectPtr conn G_GNUC_UNUSED) { /* Trivially secure, since always inside the daemon */ return 1; } static int nwfilterConnectIsEncrypted(virConnectPtr conn G_GNUC_UNUSED) { /* Not encrypted, but remote driver takes care of that */ return 0; } static int nwfilterConnectIsAlive(virConnectPtr conn G_GNUC_UNUSED) { return 1; } static virNWFilterObj * nwfilterObjFromNWFilter(const unsigned char *uuid) { virNWFilterObj *obj; char uuidstr[VIR_UUID_STRING_BUFLEN]; if (!(obj = virNWFilterObjListFindByUUID(driver->nwfilters, uuid))) { virUUIDFormat(uuid, uuidstr); virReportError(VIR_ERR_NO_NWFILTER, _("no nwfilter with matching uuid '%1$s'"), uuidstr); } return obj; } static virNWFilterPtr nwfilterLookupByUUID(virConnectPtr conn, const unsigned char *uuid) { virNWFilterObj *obj = NULL; virNWFilterDef *def; virNWFilterPtr nwfilter = NULL; VIR_WITH_MUTEX_LOCK_GUARD(&driverMutex) { obj = nwfilterObjFromNWFilter(uuid); } if (!obj) return NULL; def = virNWFilterObjGetDef(obj); if (virNWFilterLookupByUUIDEnsureACL(conn, def) < 0) goto cleanup; nwfilter = virGetNWFilter(conn, def->name, def->uuid); cleanup: virNWFilterObjUnlock(obj); return nwfilter; } static virNWFilterPtr nwfilterLookupByName(virConnectPtr conn, const char *name) { virNWFilterObj *obj = NULL; virNWFilterDef *def; virNWFilterPtr nwfilter = NULL; VIR_WITH_MUTEX_LOCK_GUARD(&driverMutex) { obj = virNWFilterObjListFindByName(driver->nwfilters, name); } if (!obj) { virReportError(VIR_ERR_NO_NWFILTER, _("no nwfilter with matching name '%1$s'"), name); return NULL; } def = virNWFilterObjGetDef(obj); if (virNWFilterLookupByNameEnsureACL(conn, def) < 0) goto cleanup; nwfilter = virGetNWFilter(conn, def->name, def->uuid); cleanup: virNWFilterObjUnlock(obj); return nwfilter; } static int nwfilterConnectNumOfNWFilters(virConnectPtr conn) { int ret = -1; if (virConnectNumOfNWFiltersEnsureACL(conn) < 0) return -1; VIR_WITH_MUTEX_LOCK_GUARD(&driverMutex) { ret = virNWFilterObjListNumOfNWFilters(driver->nwfilters, conn, virConnectNumOfNWFiltersCheckACL); } return ret; } static int nwfilterConnectListNWFilters(virConnectPtr conn, char **const names, int maxnames) { int nnames = -1; if (virConnectListNWFiltersEnsureACL(conn) < 0) return -1; VIR_WITH_MUTEX_LOCK_GUARD(&driverMutex) { nnames = virNWFilterObjListGetNames(driver->nwfilters, conn, virConnectListNWFiltersCheckACL, names, maxnames); } return nnames; } static int nwfilterConnectListAllNWFilters(virConnectPtr conn, virNWFilterPtr **nwfilters, unsigned int flags) { int ret = -1; virCheckFlags(0, -1); if (virConnectListAllNWFiltersEnsureACL(conn) < 0) return -1; VIR_WITH_MUTEX_LOCK_GUARD(&driverMutex) { ret = virNWFilterObjListExport(conn, driver->nwfilters, nwfilters, virConnectListAllNWFiltersCheckACL); } return ret; } static virNWFilterPtr nwfilterDefineXMLFlags(virConnectPtr conn, const char *xml, unsigned int flags) { VIR_LOCK_GUARD lock = virLockGuardLock(&driverMutex); virNWFilterDef *def; virNWFilterObj *obj = NULL; virNWFilterDef *objdef; virNWFilterPtr nwfilter = NULL; virCheckFlags(VIR_NWFILTER_DEFINE_VALIDATE, NULL); if (!driver->privileged) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("Can't define NWFilters in session mode")); return NULL; } if (!(def = virNWFilterDefParse(xml, NULL, flags))) goto cleanup; if (virNWFilterDefineXMLFlagsEnsureACL(conn, def) < 0) goto cleanup; VIR_WITH_MUTEX_LOCK_GUARD(&driver->updateLock) { if (!(obj = virNWFilterObjListAssignDef(driver->nwfilters, def))) goto cleanup; } def = NULL; objdef = virNWFilterObjGetDef(obj); if (virNWFilterSaveConfig(driver->configDir, objdef) < 0) { virNWFilterObjListRemove(driver->nwfilters, obj); goto cleanup; } nwfilter = virGetNWFilter(conn, objdef->name, objdef->uuid); cleanup: virNWFilterDefFree(def); if (obj) virNWFilterObjUnlock(obj); return nwfilter; } static virNWFilterPtr nwfilterDefineXML(virConnectPtr conn, const char *xml) { return nwfilterDefineXMLFlags(conn, xml, 0); } static int nwfilterUndefine(virNWFilterPtr nwfilter) { VIR_LOCK_GUARD lock = virLockGuardLock(&driverMutex); virNWFilterObj *obj; virNWFilterDef *def; int ret = -1; VIR_WITH_MUTEX_LOCK_GUARD(&driver->updateLock) { if (!(obj = nwfilterObjFromNWFilter(nwfilter->uuid))) goto cleanup; def = virNWFilterObjGetDef(obj); if (virNWFilterUndefineEnsureACL(nwfilter->conn, def) < 0) goto cleanup; if (virNWFilterObjTestUnassignDef(obj) < 0) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("nwfilter is in use")); goto cleanup; } if (virNWFilterDeleteDef(driver->configDir, def) < 0) goto cleanup; virNWFilterObjListRemove(driver->nwfilters, obj); obj = NULL; } ret = 0; cleanup: if (obj) virNWFilterObjUnlock(obj); return ret; } static char * nwfilterGetXMLDesc(virNWFilterPtr nwfilter, unsigned int flags) { virNWFilterObj *obj = NULL; virNWFilterDef *def; char *ret = NULL; virCheckFlags(0, NULL); VIR_WITH_MUTEX_LOCK_GUARD(&driverMutex) { obj = nwfilterObjFromNWFilter(nwfilter->uuid); } if (!obj) return NULL; def = virNWFilterObjGetDef(obj); if (virNWFilterGetXMLDescEnsureACL(nwfilter->conn, def) < 0) goto cleanup; ret = virNWFilterDefFormat(def); cleanup: virNWFilterObjUnlock(obj); return ret; } static virNWFilterBindingPtr nwfilterBindingLookupByPortDev(virConnectPtr conn, const char *portdev) { virNWFilterBindingPtr ret = NULL; virNWFilterBindingObj *obj; virNWFilterBindingDef *def; obj = virNWFilterBindingObjListFindByPortDev(driver->bindings, portdev); if (!obj) { virReportError(VIR_ERR_NO_NWFILTER_BINDING, _("no nwfilter binding for port dev '%1$s'"), portdev); goto cleanup; } def = virNWFilterBindingObjGetDef(obj); if (virNWFilterBindingLookupByPortDevEnsureACL(conn, def) < 0) goto cleanup; ret = virGetNWFilterBinding(conn, def->portdevname, def->filter); cleanup: virNWFilterBindingObjEndAPI(&obj); return ret; } static int nwfilterConnectListAllNWFilterBindings(virConnectPtr conn, virNWFilterBindingPtr **bindings, unsigned int flags) { virCheckFlags(0, -1); if (virConnectListAllNWFilterBindingsEnsureACL(conn) < 0) return -1; return virNWFilterBindingObjListExport(driver->bindings, conn, bindings, virConnectListAllNWFilterBindingsCheckACL); } static char * nwfilterBindingGetXMLDesc(virNWFilterBindingPtr binding, unsigned int flags) { virNWFilterBindingObj *obj; virNWFilterBindingDef *def; char *ret = NULL; virCheckFlags(0, NULL); obj = virNWFilterBindingObjListFindByPortDev(driver->bindings, binding->portdev); if (!obj) { virReportError(VIR_ERR_NO_NWFILTER_BINDING, _("no nwfilter binding for port dev '%1$s'"), binding->portdev); goto cleanup; } def = virNWFilterBindingObjGetDef(obj); if (virNWFilterBindingGetXMLDescEnsureACL(binding->conn, def) < 0) goto cleanup; ret = virNWFilterBindingDefFormat(def); cleanup: virNWFilterBindingObjEndAPI(&obj); return ret; } static virNWFilterBindingPtr nwfilterBindingCreateXML(virConnectPtr conn, const char *xml, unsigned int flags) { virNWFilterBindingDef *def; virNWFilterBindingObj *obj = NULL; virNWFilterBindingPtr ret = NULL; virCheckFlags(VIR_NWFILTER_BINDING_CREATE_VALIDATE, NULL); if (!driver->privileged) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("Can't define NWFilter bindings in session mode")); return NULL; } def = virNWFilterBindingDefParse(xml, NULL, flags); if (!def) return NULL; if (virNWFilterBindingCreateXMLEnsureACL(conn, def) < 0) goto cleanup; obj = virNWFilterBindingObjListAdd(driver->bindings, def); if (!obj) goto cleanup; if (!(ret = virGetNWFilterBinding(conn, def->portdevname, def->filter))) goto cleanup; VIR_WITH_MUTEX_LOCK_GUARD(&driver->updateLock) { if (virNWFilterInstantiateFilter(driver, def) < 0) { virNWFilterBindingObjListRemove(driver->bindings, obj); g_clear_pointer(&ret, virObjectUnref); goto cleanup; } } virNWFilterBindingObjSave(obj, driver->bindingDir); cleanup: if (!obj) virNWFilterBindingDefFree(def); virNWFilterBindingObjEndAPI(&obj); return ret; } /* * Note that this is primarily intended for usage by the hypervisor * drivers. it is exposed to the admin, however, and nothing stops * an admin from deleting filter bindings created by the hypervisor * drivers. IOW, it is the admin's responsibility not to shoot * themself in the foot */ static int nwfilterBindingDelete(virNWFilterBindingPtr binding) { virNWFilterBindingObj *obj; virNWFilterBindingDef *def; int ret = -1; obj = virNWFilterBindingObjListFindByPortDev(driver->bindings, binding->portdev); if (!obj) { virReportError(VIR_ERR_NO_NWFILTER_BINDING, _("no nwfilter binding for port dev '%1$s'"), binding->portdev); return -1; } def = virNWFilterBindingObjGetDef(obj); if (virNWFilterBindingDeleteEnsureACL(binding->conn, def) < 0) goto cleanup; VIR_WITH_MUTEX_LOCK_GUARD(&driver->updateLock) { virNWFilterTeardownFilter(def); } virNWFilterBindingObjDelete(obj, driver->bindingDir); virNWFilterBindingObjListRemove(driver->bindings, obj); ret = 0; cleanup: virNWFilterBindingObjEndAPI(&obj); return ret; } static virNWFilterDriver nwfilterDriver = { .name = "nwfilter", .connectNumOfNWFilters = nwfilterConnectNumOfNWFilters, /* 0.8.0 */ .connectListNWFilters = nwfilterConnectListNWFilters, /* 0.8.0 */ .connectListAllNWFilters = nwfilterConnectListAllNWFilters, /* 0.10.2 */ .nwfilterLookupByName = nwfilterLookupByName, /* 0.8.0 */ .nwfilterLookupByUUID = nwfilterLookupByUUID, /* 0.8.0 */ .nwfilterDefineXML = nwfilterDefineXML, /* 0.8.0 */ .nwfilterDefineXMLFlags = nwfilterDefineXMLFlags, /* 7.7.0 */ .nwfilterUndefine = nwfilterUndefine, /* 0.8.0 */ .nwfilterGetXMLDesc = nwfilterGetXMLDesc, /* 0.8.0 */ .nwfilterBindingLookupByPortDev = nwfilterBindingLookupByPortDev, /* 4.5.0 */ .connectListAllNWFilterBindings = nwfilterConnectListAllNWFilterBindings, /* 4.5.0 */ .nwfilterBindingGetXMLDesc = nwfilterBindingGetXMLDesc, /* 4.5.0 */ .nwfilterBindingCreateXML = nwfilterBindingCreateXML, /* 4.5.0 */ .nwfilterBindingDelete = nwfilterBindingDelete, /* 4.5.0 */ }; static virHypervisorDriver nwfilterHypervisorDriver = { .name = "nwfilter", .connectOpen = nwfilterConnectOpen, /* 4.1.0 */ .connectClose = nwfilterConnectClose, /* 4.1.0 */ .connectIsEncrypted = nwfilterConnectIsEncrypted, /* 4.1.0 */ .connectIsSecure = nwfilterConnectIsSecure, /* 4.1.0 */ .connectIsAlive = nwfilterConnectIsAlive, /* 4.1.0 */ }; static virConnectDriver nwfilterConnectDriver = { .localOnly = true, .uriSchemes = (const char *[]){ "nwfilter", NULL }, .hypervisorDriver = &nwfilterHypervisorDriver, .nwfilterDriver = &nwfilterDriver, }; static virStateDriver stateDriver = { .name = "NWFilter", .stateInitialize = nwfilterStateInitialize, .stateCleanup = nwfilterStateCleanup, .stateReload = nwfilterStateReload, }; int nwfilterRegister(void) { if (virRegisterConnectDriver(&nwfilterConnectDriver, false) < 0) return -1; if (virSetSharedNWFilterDriver(&nwfilterDriver) < 0) return -1; if (virRegisterStateDriver(&stateDriver) < 0) return -1; return 0; }