mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-08-28 03:21:19 +00:00
fca9afa084
Use the virNWFilterBindingDefPtr struct in the DHCP address snooping code directly. Reviewed-by: John Ferlan <jferlan@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
1082 lines
32 KiB
C
1082 lines
32 KiB
C
/*
|
|
* nwfilter_gentech_driver.c: generic technology driver
|
|
*
|
|
* Copyright (C) 2011-2014 Red Hat, Inc.
|
|
* Copyright (C) 2010 IBM Corp.
|
|
* 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
|
|
* <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Author: Stefan Berger <stefanb@us.ibm.com>
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "internal.h"
|
|
|
|
#include "viralloc.h"
|
|
#include "virlog.h"
|
|
#include "domain_conf.h"
|
|
#include "virerror.h"
|
|
#include "nwfilter_gentech_driver.h"
|
|
#include "nwfilter_ebiptables_driver.h"
|
|
#include "nwfilter_dhcpsnoop.h"
|
|
#include "nwfilter_ipaddrmap.h"
|
|
#include "nwfilter_learnipaddr.h"
|
|
#include "virnetdev.h"
|
|
#include "datatypes.h"
|
|
#include "virsocketaddr.h"
|
|
#include "virstring.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_NWFILTER
|
|
|
|
VIR_LOG_INIT("nwfilter.nwfilter_gentech_driver");
|
|
|
|
#define NWFILTER_STD_VAR_MAC NWFILTER_VARNAME_MAC
|
|
#define NWFILTER_STD_VAR_IP NWFILTER_VARNAME_IP
|
|
|
|
#define NWFILTER_DFLT_LEARN "any"
|
|
|
|
static int _virNWFilterTeardownFilter(const char *ifname);
|
|
|
|
|
|
static virNWFilterTechDriverPtr filter_tech_drivers[] = {
|
|
&ebiptables_driver,
|
|
NULL
|
|
};
|
|
|
|
/* Serializes instantiation of filters. This is necessary
|
|
* to avoid lock ordering deadlocks. eg virNWFilterInstantiateFilterUpdate
|
|
* will hold a lock on a virNWFilterObjPtr. This in turn invokes
|
|
* virNWFilterDoInstantiate which invokes virNWFilterDetermineMissingVarsRec
|
|
* which invokes virNWFilterObjListFindInstantiateFilter. This iterates over
|
|
* every single virNWFilterObjPtr in the list. So if 2 threads try to
|
|
* instantiate a filter in parallel, they'll both hold 1 lock at the top level
|
|
* in virNWFilterInstantiateFilterUpdate which will cause the other thread
|
|
* to deadlock in virNWFilterObjListFindInstantiateFilter.
|
|
*
|
|
* XXX better long term solution is to make virNWFilterObjList use a
|
|
* hash table as is done for virDomainObjList. You can then get
|
|
* lockless lookup of objects by name.
|
|
*/
|
|
static virMutex updateMutex;
|
|
|
|
int virNWFilterTechDriversInit(bool privileged)
|
|
{
|
|
size_t i = 0;
|
|
VIR_DEBUG("Initializing NWFilter technology drivers");
|
|
if (virMutexInitRecursive(&updateMutex) < 0)
|
|
return -1;
|
|
|
|
while (filter_tech_drivers[i]) {
|
|
if (!(filter_tech_drivers[i]->flags & TECHDRV_FLAG_INITIALIZED))
|
|
filter_tech_drivers[i]->init(privileged);
|
|
i++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
void virNWFilterTechDriversShutdown(void)
|
|
{
|
|
size_t i = 0;
|
|
while (filter_tech_drivers[i]) {
|
|
if ((filter_tech_drivers[i]->flags & TECHDRV_FLAG_INITIALIZED))
|
|
filter_tech_drivers[i]->shutdown();
|
|
i++;
|
|
}
|
|
virMutexDestroy(&updateMutex);
|
|
}
|
|
|
|
|
|
virNWFilterTechDriverPtr
|
|
virNWFilterTechDriverForName(const char *name)
|
|
{
|
|
size_t i = 0;
|
|
while (filter_tech_drivers[i]) {
|
|
if (STREQ(filter_tech_drivers[i]->name, name)) {
|
|
if ((filter_tech_drivers[i]->flags & TECHDRV_FLAG_INITIALIZED) == 0)
|
|
break;
|
|
return filter_tech_drivers[i];
|
|
}
|
|
i++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void
|
|
virNWFilterRuleInstFree(virNWFilterRuleInstPtr inst)
|
|
{
|
|
if (!inst)
|
|
return;
|
|
|
|
virHashFree(inst->vars);
|
|
VIR_FREE(inst);
|
|
}
|
|
|
|
|
|
/**
|
|
* virNWFilterVarHashmapAddStdValues:
|
|
* @tables: pointer to hash tabel to add values to
|
|
* @macaddr: The string of the MAC address to add to the hash table,
|
|
* may be NULL
|
|
* @ipaddr: The string of the IP address to add to the hash table;
|
|
* may be NULL
|
|
*
|
|
* Returns 0 in case of success, -1 in case an error happened with
|
|
* error having been reported.
|
|
*
|
|
* Adds a couple of standard keys (MAC, IP) to the hash table.
|
|
*/
|
|
static int
|
|
virNWFilterVarHashmapAddStdValues(virHashTablePtr table,
|
|
const char *macaddr,
|
|
const virNWFilterVarValue *ipaddr)
|
|
{
|
|
virNWFilterVarValue *val;
|
|
|
|
if (macaddr) {
|
|
val = virNWFilterVarValueCreateSimpleCopyValue(macaddr);
|
|
if (!val)
|
|
return -1;
|
|
|
|
if (virHashAddEntry(table,
|
|
NWFILTER_STD_VAR_MAC,
|
|
val) < 0) {
|
|
virNWFilterVarValueFree(val);
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("Could not add variable 'MAC' to hashmap"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (ipaddr) {
|
|
val = virNWFilterVarValueCopy(ipaddr);
|
|
if (!val)
|
|
return -1;
|
|
|
|
if (virHashAddEntry(table,
|
|
NWFILTER_STD_VAR_IP,
|
|
val) < 0) {
|
|
virNWFilterVarValueFree(val);
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("Could not add variable 'IP' to hashmap"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Convert a virHashTable into a string of comma-separated
|
|
* variable names.
|
|
*/
|
|
struct printString
|
|
{
|
|
virBuffer buf;
|
|
const char *separator;
|
|
bool reportMAC;
|
|
bool reportIP;
|
|
};
|
|
|
|
|
|
static int
|
|
printString(void *payload ATTRIBUTE_UNUSED, const void *name, void *data)
|
|
{
|
|
struct printString *ps = data;
|
|
|
|
if ((STREQ((char *)name, NWFILTER_STD_VAR_IP) && !ps->reportIP) ||
|
|
(STREQ((char *)name, NWFILTER_STD_VAR_MAC) && !ps->reportMAC))
|
|
return 0;
|
|
|
|
if (virBufferUse(&ps->buf) && ps->separator)
|
|
virBufferAdd(&ps->buf, ps->separator, -1);
|
|
|
|
virBufferAdd(&ps->buf, name, -1);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* virNWFilterPrintVars
|
|
*
|
|
* @var: hash table containing variables
|
|
* @separator: separator to use between variable names, i.e., ", "
|
|
* @reportMAC: whether to report the 'MAC' variable
|
|
* @reportIP : whether to report the IP variable
|
|
*
|
|
* Returns a string of comma separated variable names
|
|
*/
|
|
static char *
|
|
virNWFilterPrintVars(virHashTablePtr vars,
|
|
const char *separator,
|
|
bool reportMAC,
|
|
bool reportIP)
|
|
{
|
|
struct printString ps = {
|
|
.buf = VIR_BUFFER_INITIALIZER,
|
|
.separator = separator,
|
|
.reportMAC = reportMAC,
|
|
.reportIP = reportIP,
|
|
};
|
|
|
|
virHashForEach(vars, printString, &ps);
|
|
|
|
if (virBufferCheckError(&ps.buf) < 0)
|
|
return NULL;
|
|
return virBufferContentAndReset(&ps.buf);
|
|
}
|
|
|
|
|
|
/**
|
|
* virNWFilterCreateVarsFrom:
|
|
* @vars1: pointer to hash table
|
|
* @vars2: pointer to hash table
|
|
*
|
|
* Returns pointer to new hashtable or NULL in case of error with
|
|
* error already reported.
|
|
*
|
|
* Creates a new hash table with contents of var1 and var2 added where
|
|
* contents of var2 will overwrite those of var1.
|
|
*/
|
|
static virHashTablePtr
|
|
virNWFilterCreateVarsFrom(virHashTablePtr vars1,
|
|
virHashTablePtr vars2)
|
|
{
|
|
virHashTablePtr res = virNWFilterHashTableCreate(0);
|
|
if (!res)
|
|
return NULL;
|
|
|
|
if (virNWFilterHashTablePutAll(vars1, res) < 0)
|
|
goto err_exit;
|
|
|
|
if (virNWFilterHashTablePutAll(vars2, res) < 0)
|
|
goto err_exit;
|
|
|
|
return res;
|
|
|
|
err_exit:
|
|
virHashFree(res);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
typedef struct _virNWFilterInst virNWFilterInst;
|
|
typedef virNWFilterInst *virNWFilterInstPtr;
|
|
struct _virNWFilterInst {
|
|
virNWFilterObjPtr *filters;
|
|
size_t nfilters;
|
|
virNWFilterRuleInstPtr *rules;
|
|
size_t nrules;
|
|
};
|
|
|
|
|
|
static void
|
|
virNWFilterInstReset(virNWFilterInstPtr inst)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < inst->nfilters; i++)
|
|
virNWFilterObjUnlock(inst->filters[i]);
|
|
VIR_FREE(inst->filters);
|
|
inst->nfilters = 0;
|
|
|
|
for (i = 0; i < inst->nrules; i++)
|
|
virNWFilterRuleInstFree(inst->rules[i]);
|
|
VIR_FREE(inst->rules);
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
virNWFilterDefToInst(virNWFilterDriverStatePtr driver,
|
|
virNWFilterDefPtr def,
|
|
virHashTablePtr vars,
|
|
enum instCase useNewFilter,
|
|
bool *foundNewFilter,
|
|
virNWFilterInstPtr inst);
|
|
|
|
static int
|
|
virNWFilterRuleDefToRuleInst(virNWFilterDefPtr def,
|
|
virNWFilterRuleDefPtr rule,
|
|
virHashTablePtr vars,
|
|
virNWFilterInstPtr inst)
|
|
{
|
|
virNWFilterRuleInstPtr ruleinst;
|
|
int ret = -1;
|
|
|
|
if (VIR_ALLOC(ruleinst) < 0)
|
|
goto cleanup;
|
|
|
|
ruleinst->chainSuffix = def->chainsuffix;
|
|
ruleinst->chainPriority = def->chainPriority;
|
|
ruleinst->def = rule;
|
|
ruleinst->priority = rule->priority;
|
|
if (!(ruleinst->vars = virNWFilterHashTableCreate(0)))
|
|
goto cleanup;
|
|
if (virNWFilterHashTablePutAll(vars, ruleinst->vars) < 0)
|
|
goto cleanup;
|
|
|
|
if (VIR_APPEND_ELEMENT(inst->rules,
|
|
inst->nrules,
|
|
ruleinst) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virNWFilterRuleInstFree(ruleinst);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virNWFilterIncludeDefToRuleInst(virNWFilterDriverStatePtr driver,
|
|
virNWFilterIncludeDefPtr inc,
|
|
virHashTablePtr vars,
|
|
enum instCase useNewFilter,
|
|
bool *foundNewFilter,
|
|
virNWFilterInstPtr inst)
|
|
{
|
|
virNWFilterObjPtr obj;
|
|
virHashTablePtr tmpvars = NULL;
|
|
virNWFilterDefPtr childdef;
|
|
virNWFilterDefPtr newChilddef;
|
|
int ret = -1;
|
|
|
|
VIR_DEBUG("Instantiating filter %s", inc->filterref);
|
|
if (!(obj = virNWFilterObjListFindInstantiateFilter(driver->nwfilters,
|
|
inc->filterref)))
|
|
goto cleanup;
|
|
|
|
/* create a temporary hashmap for depth-first tree traversal */
|
|
if (!(tmpvars = virNWFilterCreateVarsFrom(inc->params,
|
|
vars)))
|
|
goto cleanup;
|
|
|
|
childdef = virNWFilterObjGetDef(obj);
|
|
|
|
switch (useNewFilter) {
|
|
case INSTANTIATE_FOLLOW_NEWFILTER:
|
|
newChilddef = virNWFilterObjGetNewDef(obj);
|
|
if (newChilddef) {
|
|
childdef = newChilddef;
|
|
*foundNewFilter = true;
|
|
}
|
|
break;
|
|
case INSTANTIATE_ALWAYS:
|
|
break;
|
|
}
|
|
|
|
if (VIR_APPEND_ELEMENT(inst->filters,
|
|
inst->nfilters,
|
|
obj) < 0)
|
|
goto cleanup;
|
|
obj = NULL;
|
|
|
|
if (virNWFilterDefToInst(driver,
|
|
childdef,
|
|
tmpvars,
|
|
useNewFilter,
|
|
foundNewFilter,
|
|
inst) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
if (ret < 0)
|
|
virNWFilterInstReset(inst);
|
|
virHashFree(tmpvars);
|
|
if (obj)
|
|
virNWFilterObjUnlock(obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* virNWFilterDefToInst:
|
|
* @driver: the driver state pointer
|
|
* @def: The filter to instantiate
|
|
* @vars: A map holding variable names and values used for instantiating
|
|
* the filter and its subfilters.
|
|
* @useNewFilter: instruct whether to use a newDef pointer rather than a
|
|
* def ptr which is useful during a filter update
|
|
* @foundNewFilter: pointer to int indivating whether a newDef pointer was
|
|
* ever used; variable expected to be initialized to 0 by caller
|
|
* @rulesout: array to be filled with rule instance
|
|
* @nrulesout: counter to be filled with number of rule instances
|
|
*
|
|
* Recursively expand a nested filter into a flat list of rule instances,
|
|
* in a depth-first traversal of the tree.
|
|
*
|
|
* Returns 0 on success, -1 on error
|
|
*/
|
|
static int
|
|
virNWFilterDefToInst(virNWFilterDriverStatePtr driver,
|
|
virNWFilterDefPtr def,
|
|
virHashTablePtr vars,
|
|
enum instCase useNewFilter,
|
|
bool *foundNewFilter,
|
|
virNWFilterInstPtr inst)
|
|
{
|
|
size_t i;
|
|
int ret = -1;
|
|
|
|
for (i = 0; i < def->nentries; i++) {
|
|
if (def->filterEntries[i]->rule) {
|
|
if (virNWFilterRuleDefToRuleInst(def,
|
|
def->filterEntries[i]->rule,
|
|
vars,
|
|
inst) < 0)
|
|
goto cleanup;
|
|
} else if (def->filterEntries[i]->include) {
|
|
if (virNWFilterIncludeDefToRuleInst(driver,
|
|
def->filterEntries[i]->include,
|
|
vars,
|
|
useNewFilter, foundNewFilter,
|
|
inst) < 0)
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
if (ret < 0)
|
|
virNWFilterInstReset(inst);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virNWFilterDetermineMissingVarsRec(virNWFilterDefPtr filter,
|
|
virHashTablePtr vars,
|
|
virHashTablePtr missing_vars,
|
|
int useNewFilter,
|
|
virNWFilterDriverStatePtr driver)
|
|
{
|
|
virNWFilterObjPtr obj;
|
|
int rc = 0;
|
|
size_t i, j;
|
|
virNWFilterDefPtr next_filter;
|
|
virNWFilterDefPtr newNext_filter;
|
|
virNWFilterVarValuePtr val;
|
|
virHashTablePtr tmpvars;
|
|
|
|
for (i = 0; i < filter->nentries; i++) {
|
|
virNWFilterRuleDefPtr rule = filter->filterEntries[i]->rule;
|
|
virNWFilterIncludeDefPtr inc = filter->filterEntries[i]->include;
|
|
if (rule) {
|
|
/* check all variables of this rule */
|
|
for (j = 0; j < rule->nVarAccess; j++) {
|
|
if (!virNWFilterVarAccessIsAvailable(rule->varAccess[j],
|
|
vars)) {
|
|
char *varAccess;
|
|
virBuffer buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virNWFilterVarAccessPrint(rule->varAccess[j], &buf);
|
|
if (virBufferError(&buf)) {
|
|
virReportOOMError();
|
|
return -1;
|
|
}
|
|
|
|
val = virNWFilterVarValueCreateSimpleCopyValue("1");
|
|
if (!val) {
|
|
virBufferFreeAndReset(&buf);
|
|
return -1;
|
|
}
|
|
|
|
varAccess = virBufferContentAndReset(&buf);
|
|
rc = virHashUpdateEntry(missing_vars, varAccess, val);
|
|
VIR_FREE(varAccess);
|
|
if (rc < 0) {
|
|
virNWFilterVarValueFree(val);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
} else if (inc) {
|
|
VIR_DEBUG("Following filter %s", inc->filterref);
|
|
if (!(obj = virNWFilterObjListFindInstantiateFilter(driver->nwfilters,
|
|
inc->filterref)))
|
|
return -1;
|
|
|
|
/* create a temporary hashmap for depth-first tree traversal */
|
|
if (!(tmpvars = virNWFilterCreateVarsFrom(inc->params, vars))) {
|
|
virNWFilterObjUnlock(obj);
|
|
return -1;
|
|
}
|
|
|
|
next_filter = virNWFilterObjGetDef(obj);
|
|
|
|
switch (useNewFilter) {
|
|
case INSTANTIATE_FOLLOW_NEWFILTER:
|
|
newNext_filter = virNWFilterObjGetNewDef(obj);
|
|
if (newNext_filter)
|
|
next_filter = newNext_filter;
|
|
break;
|
|
case INSTANTIATE_ALWAYS:
|
|
break;
|
|
}
|
|
|
|
rc = virNWFilterDetermineMissingVarsRec(next_filter,
|
|
tmpvars,
|
|
missing_vars,
|
|
useNewFilter,
|
|
driver);
|
|
|
|
virHashFree(tmpvars);
|
|
|
|
virNWFilterObjUnlock(obj);
|
|
if (rc < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virNWFilterDoInstantiate:
|
|
* @techdriver: The driver to use for instantiation
|
|
* @binding: description of port to bind the filter to
|
|
* @filter: The filter to instantiate
|
|
* @forceWithPendingReq: Ignore the check whether a pending learn request
|
|
* is active; 'true' only when the rules are applied late
|
|
*
|
|
* Returns 0 on success, a value otherwise.
|
|
*
|
|
* Instantiate a filter by instantiating the filter itself along with
|
|
* all its subfilters in a depth-first traversal of the tree of referenced
|
|
* filters. The name of the interface to which the rules belong must be
|
|
* provided. Apply the values of variables as needed.
|
|
*
|
|
* Call this function while holding the NWFilter filter update lock
|
|
*/
|
|
static int
|
|
virNWFilterDoInstantiate(virNWFilterTechDriverPtr techdriver,
|
|
virNWFilterBindingDefPtr binding,
|
|
virNWFilterDefPtr filter,
|
|
int ifindex,
|
|
enum instCase useNewFilter,
|
|
bool *foundNewFilter,
|
|
bool teardownOld,
|
|
virNWFilterDriverStatePtr driver,
|
|
bool forceWithPendingReq)
|
|
{
|
|
int rc;
|
|
virNWFilterInst inst;
|
|
bool instantiate = true;
|
|
char *buf;
|
|
virNWFilterVarValuePtr lv;
|
|
const char *learning;
|
|
bool reportIP = false;
|
|
|
|
virHashTablePtr missing_vars = virNWFilterHashTableCreate(0);
|
|
|
|
memset(&inst, 0, sizeof(inst));
|
|
|
|
if (!missing_vars) {
|
|
rc = -1;
|
|
goto err_exit;
|
|
}
|
|
|
|
rc = virNWFilterDetermineMissingVarsRec(filter,
|
|
binding->filterparams,
|
|
missing_vars,
|
|
useNewFilter,
|
|
driver);
|
|
if (rc < 0)
|
|
goto err_exit;
|
|
|
|
lv = virHashLookup(binding->filterparams, NWFILTER_VARNAME_CTRL_IP_LEARNING);
|
|
if (lv)
|
|
learning = virNWFilterVarValueGetNthValue(lv, 0);
|
|
else
|
|
learning = NULL;
|
|
|
|
if (learning == NULL)
|
|
learning = NWFILTER_DFLT_LEARN;
|
|
|
|
if (virHashSize(missing_vars) == 1) {
|
|
if (virHashLookup(missing_vars,
|
|
NWFILTER_STD_VAR_IP) != NULL) {
|
|
if (STRCASEEQ(learning, "none")) { /* no learning */
|
|
reportIP = true;
|
|
goto err_unresolvable_vars;
|
|
}
|
|
if (STRCASEEQ(learning, "dhcp")) {
|
|
rc = virNWFilterDHCPSnoopReq(techdriver,
|
|
binding,
|
|
driver);
|
|
goto err_exit;
|
|
} else if (STRCASEEQ(learning, "any")) {
|
|
if (!virNWFilterHasLearnReq(ifindex)) {
|
|
rc = virNWFilterLearnIPAddress(techdriver,
|
|
binding,
|
|
ifindex,
|
|
driver,
|
|
DETECT_DHCP|DETECT_STATIC);
|
|
}
|
|
goto err_exit;
|
|
} else {
|
|
rc = -1;
|
|
virReportError(VIR_ERR_PARSE_FAILED,
|
|
_("filter '%s' "
|
|
"learning value '%s' invalid."),
|
|
filter->name, learning);
|
|
goto err_exit;
|
|
}
|
|
} else {
|
|
goto err_unresolvable_vars;
|
|
}
|
|
} else if (virHashSize(missing_vars) > 1) {
|
|
goto err_unresolvable_vars;
|
|
} else if (!forceWithPendingReq &&
|
|
virNWFilterHasLearnReq(ifindex)) {
|
|
goto err_exit;
|
|
}
|
|
|
|
rc = virNWFilterDefToInst(driver,
|
|
filter,
|
|
binding->filterparams,
|
|
useNewFilter, foundNewFilter,
|
|
&inst);
|
|
|
|
if (rc < 0)
|
|
goto err_exit;
|
|
|
|
switch (useNewFilter) {
|
|
case INSTANTIATE_FOLLOW_NEWFILTER:
|
|
instantiate = *foundNewFilter;
|
|
break;
|
|
case INSTANTIATE_ALWAYS:
|
|
instantiate = true;
|
|
break;
|
|
}
|
|
|
|
if (instantiate) {
|
|
if (virNWFilterLockIface(binding->portdevname) < 0)
|
|
goto err_exit;
|
|
|
|
rc = techdriver->applyNewRules(binding->portdevname, inst.rules, inst.nrules);
|
|
|
|
if (teardownOld && rc == 0)
|
|
techdriver->tearOldRules(binding->portdevname);
|
|
|
|
if (rc == 0 && (virNetDevValidateConfig(binding->portdevname, NULL, ifindex) <= 0)) {
|
|
virResetLastError();
|
|
/* interface changed/disppeared */
|
|
techdriver->allTeardown(binding->portdevname);
|
|
rc = -1;
|
|
}
|
|
|
|
virNWFilterUnlockIface(binding->portdevname);
|
|
}
|
|
|
|
err_exit:
|
|
virNWFilterInstReset(&inst);
|
|
virHashFree(missing_vars);
|
|
|
|
return rc;
|
|
|
|
err_unresolvable_vars:
|
|
|
|
buf = virNWFilterPrintVars(missing_vars, ", ", false, reportIP);
|
|
if (buf) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Cannot instantiate filter due to unresolvable "
|
|
"variables or unavailable list elements: %s"), buf);
|
|
VIR_FREE(buf);
|
|
}
|
|
|
|
rc = -1;
|
|
goto err_exit;
|
|
}
|
|
|
|
|
|
/*
|
|
* Call this function while holding the NWFilter filter update lock
|
|
*/
|
|
static int
|
|
virNWFilterInstantiateFilterUpdate(virNWFilterDriverStatePtr driver,
|
|
bool teardownOld,
|
|
virNWFilterBindingDefPtr binding,
|
|
int ifindex,
|
|
enum instCase useNewFilter,
|
|
bool forceWithPendingReq,
|
|
bool *foundNewFilter)
|
|
{
|
|
int rc;
|
|
const char *drvname = EBIPTABLES_DRIVER_ID;
|
|
virNWFilterTechDriverPtr techdriver;
|
|
virNWFilterObjPtr obj;
|
|
virNWFilterDefPtr filter;
|
|
virNWFilterDefPtr newFilter;
|
|
char vmmacaddr[VIR_MAC_STRING_BUFLEN] = {0};
|
|
virNWFilterVarValuePtr ipaddr;
|
|
|
|
techdriver = virNWFilterTechDriverForName(drvname);
|
|
|
|
if (!techdriver) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Could not get access to ACL tech "
|
|
"driver '%s'"),
|
|
drvname);
|
|
return -1;
|
|
}
|
|
|
|
VIR_DEBUG("filter name: %s", binding->filter);
|
|
|
|
if (!(obj = virNWFilterObjListFindInstantiateFilter(driver->nwfilters,
|
|
binding->filter)))
|
|
return -1;
|
|
|
|
virMacAddrFormat(&binding->mac, vmmacaddr);
|
|
|
|
ipaddr = virNWFilterIPAddrMapGetIPAddr(binding->portdevname);
|
|
|
|
if (virNWFilterVarHashmapAddStdValues(binding->filterparams,
|
|
vmmacaddr, ipaddr) < 0) {
|
|
rc = -1;
|
|
goto err_exit;
|
|
}
|
|
|
|
filter = virNWFilterObjGetDef(obj);
|
|
|
|
switch (useNewFilter) {
|
|
case INSTANTIATE_FOLLOW_NEWFILTER:
|
|
newFilter = virNWFilterObjGetNewDef(obj);
|
|
if (newFilter) {
|
|
filter = newFilter;
|
|
*foundNewFilter = true;
|
|
}
|
|
break;
|
|
|
|
case INSTANTIATE_ALWAYS:
|
|
break;
|
|
}
|
|
|
|
rc = virNWFilterDoInstantiate(techdriver, binding, filter,
|
|
ifindex, useNewFilter, foundNewFilter,
|
|
teardownOld, driver,
|
|
forceWithPendingReq);
|
|
|
|
err_exit:
|
|
virNWFilterObjUnlock(obj);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
static int
|
|
virNWFilterInstantiateFilterInternal(virNWFilterDriverStatePtr driver,
|
|
virNWFilterBindingDefPtr binding,
|
|
bool teardownOld,
|
|
enum instCase useNewFilter,
|
|
bool *foundNewFilter)
|
|
{
|
|
int ifindex;
|
|
int rc;
|
|
|
|
virMutexLock(&updateMutex);
|
|
|
|
/* after grabbing the filter update lock check for the interface; if
|
|
it's not there anymore its filters will be or are being removed
|
|
(while holding the lock) and we don't want to build new ones */
|
|
if (virNetDevExists(binding->portdevname) != 1 ||
|
|
virNetDevGetIndex(binding->portdevname, &ifindex) < 0) {
|
|
/* interfaces / VMs can disappear during filter instantiation;
|
|
don't mark it as an error */
|
|
virResetLastError();
|
|
rc = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
rc = virNWFilterInstantiateFilterUpdate(driver, teardownOld,
|
|
binding,
|
|
ifindex,
|
|
useNewFilter,
|
|
false, foundNewFilter);
|
|
|
|
cleanup:
|
|
virMutexUnlock(&updateMutex);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
int
|
|
virNWFilterInstantiateFilterLate(virNWFilterDriverStatePtr driver,
|
|
virNWFilterBindingDefPtr binding,
|
|
int ifindex)
|
|
{
|
|
int rc;
|
|
bool foundNewFilter = false;
|
|
|
|
virNWFilterReadLockFilterUpdates();
|
|
virMutexLock(&updateMutex);
|
|
|
|
rc = virNWFilterInstantiateFilterUpdate(driver, true,
|
|
binding, ifindex,
|
|
INSTANTIATE_ALWAYS, true,
|
|
&foundNewFilter);
|
|
if (rc < 0) {
|
|
/* something went wrong... 'DOWN' the interface */
|
|
if ((virNetDevValidateConfig(binding->portdevname, NULL, ifindex) <= 0) ||
|
|
(virNetDevSetOnline(binding->portdevname, false) < 0)) {
|
|
virResetLastError();
|
|
/* assuming interface disappeared... */
|
|
_virNWFilterTeardownFilter(binding->portdevname);
|
|
}
|
|
}
|
|
|
|
virNWFilterUnlockFilterUpdates();
|
|
virMutexUnlock(&updateMutex);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
int
|
|
virNWFilterInstantiateFilter(virNWFilterDriverStatePtr driver,
|
|
virNWFilterBindingDefPtr binding)
|
|
{
|
|
bool foundNewFilter = false;
|
|
|
|
return virNWFilterInstantiateFilterInternal(driver, binding,
|
|
1,
|
|
INSTANTIATE_ALWAYS,
|
|
&foundNewFilter);
|
|
}
|
|
|
|
|
|
int
|
|
virNWFilterUpdateInstantiateFilter(virNWFilterDriverStatePtr driver,
|
|
virNWFilterBindingDefPtr binding,
|
|
bool *skipIface)
|
|
{
|
|
bool foundNewFilter = false;
|
|
|
|
int rc = virNWFilterInstantiateFilterInternal(driver, binding,
|
|
0,
|
|
INSTANTIATE_FOLLOW_NEWFILTER,
|
|
&foundNewFilter);
|
|
|
|
*skipIface = !foundNewFilter;
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
virNWFilterRollbackUpdateFilter(virNWFilterBindingDefPtr binding)
|
|
{
|
|
const char *drvname = EBIPTABLES_DRIVER_ID;
|
|
int ifindex;
|
|
virNWFilterTechDriverPtr techdriver;
|
|
|
|
techdriver = virNWFilterTechDriverForName(drvname);
|
|
if (!techdriver) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Could not get access to ACL tech "
|
|
"driver '%s'"),
|
|
drvname);
|
|
return -1;
|
|
}
|
|
|
|
/* don't tear anything while the address is being learned */
|
|
if (virNetDevGetIndex(binding->portdevname, &ifindex) < 0)
|
|
virResetLastError();
|
|
else if (virNWFilterHasLearnReq(ifindex))
|
|
return 0;
|
|
|
|
return techdriver->tearNewRules(binding->portdevname);
|
|
}
|
|
|
|
|
|
static int
|
|
virNWFilterTearOldFilter(virNWFilterBindingDefPtr binding)
|
|
{
|
|
const char *drvname = EBIPTABLES_DRIVER_ID;
|
|
int ifindex;
|
|
virNWFilterTechDriverPtr techdriver;
|
|
|
|
techdriver = virNWFilterTechDriverForName(drvname);
|
|
if (!techdriver) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Could not get access to ACL tech "
|
|
"driver '%s'"),
|
|
drvname);
|
|
return -1;
|
|
}
|
|
|
|
/* don't tear anything while the address is being learned */
|
|
if (virNetDevGetIndex(binding->portdevname, &ifindex) < 0)
|
|
virResetLastError();
|
|
else if (virNWFilterHasLearnReq(ifindex))
|
|
return 0;
|
|
|
|
return techdriver->tearOldRules(binding->portdevname);
|
|
}
|
|
|
|
|
|
static int
|
|
_virNWFilterTeardownFilter(const char *ifname)
|
|
{
|
|
const char *drvname = EBIPTABLES_DRIVER_ID;
|
|
virNWFilterTechDriverPtr techdriver;
|
|
techdriver = virNWFilterTechDriverForName(drvname);
|
|
|
|
if (!techdriver) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Could not get access to ACL tech "
|
|
"driver '%s'"),
|
|
drvname);
|
|
return -1;
|
|
}
|
|
|
|
virNWFilterDHCPSnoopEnd(ifname);
|
|
|
|
virNWFilterTerminateLearnReq(ifname);
|
|
|
|
if (virNWFilterLockIface(ifname) < 0)
|
|
return -1;
|
|
|
|
techdriver->allTeardown(ifname);
|
|
|
|
virNWFilterIPAddrMapDelIPAddr(ifname, NULL);
|
|
|
|
virNWFilterUnlockIface(ifname);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
virNWFilterTeardownFilter(virNWFilterBindingDefPtr binding)
|
|
{
|
|
int ret;
|
|
virMutexLock(&updateMutex);
|
|
ret = _virNWFilterTeardownFilter(binding->portdevname);
|
|
virMutexUnlock(&updateMutex);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int
|
|
virNWFilterDomainFWUpdateCB(virDomainObjPtr obj,
|
|
void *data)
|
|
{
|
|
virDomainDefPtr vm = obj->def;
|
|
struct domUpdateCBStruct *cb = data;
|
|
size_t i;
|
|
bool skipIface;
|
|
int ret = 0;
|
|
|
|
virObjectLock(obj);
|
|
|
|
if (virDomainObjIsActive(obj)) {
|
|
for (i = 0; i < vm->nnets; i++) {
|
|
virDomainNetDefPtr net = vm->nets[i];
|
|
virNWFilterBindingDefPtr binding;
|
|
|
|
if ((net->filter) && (net->ifname) &&
|
|
(binding = virNWFilterBindingDefForNet(
|
|
vm->name, vm->uuid, net))) {
|
|
|
|
switch (cb->step) {
|
|
case STEP_APPLY_NEW:
|
|
ret = virNWFilterUpdateInstantiateFilter(cb->opaque,
|
|
binding,
|
|
&skipIface);
|
|
if (ret == 0 && skipIface) {
|
|
/* filter tree unchanged -- no update needed */
|
|
ret = virHashAddEntry(cb->skipInterfaces,
|
|
net->ifname,
|
|
(void *)~0);
|
|
}
|
|
break;
|
|
|
|
case STEP_TEAR_NEW:
|
|
if (!virHashLookup(cb->skipInterfaces, net->ifname))
|
|
ret = virNWFilterRollbackUpdateFilter(binding);
|
|
break;
|
|
|
|
case STEP_TEAR_OLD:
|
|
if (!virHashLookup(cb->skipInterfaces, net->ifname))
|
|
ret = virNWFilterTearOldFilter(binding);
|
|
break;
|
|
|
|
case STEP_APPLY_CURRENT:
|
|
ret = virNWFilterInstantiateFilter(cb->opaque,
|
|
binding);
|
|
if (ret)
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Failure while applying current filter on "
|
|
"VM %s"), vm->name);
|
|
break;
|
|
}
|
|
virNWFilterBindingDefFree(binding);
|
|
if (ret)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
virObjectUnlock(obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
virNWFilterBindingDefPtr
|
|
virNWFilterBindingDefForNet(const char *vmname,
|
|
const unsigned char *vmuuid,
|
|
virDomainNetDefPtr net)
|
|
{
|
|
virNWFilterBindingDefPtr ret;
|
|
|
|
if (VIR_ALLOC(ret) < 0)
|
|
return NULL;
|
|
|
|
if (VIR_STRDUP(ret->ownername, vmname) < 0)
|
|
goto error;
|
|
|
|
memcpy(ret->owneruuid, vmuuid, sizeof(ret->owneruuid));
|
|
|
|
if (VIR_STRDUP(ret->portdevname, net->ifname) < 0)
|
|
goto error;
|
|
|
|
if (net->type == VIR_DOMAIN_NET_TYPE_DIRECT &&
|
|
VIR_STRDUP(ret->linkdevname, net->data.direct.linkdev) < 0)
|
|
goto error;
|
|
|
|
ret->mac = net->mac;
|
|
|
|
if (VIR_STRDUP(ret->filter, net->filter) < 0)
|
|
goto error;
|
|
|
|
if (!(ret->filterparams = virNWFilterHashTableCreate(0)))
|
|
goto error;
|
|
|
|
if (net->filterparams &&
|
|
virNWFilterHashTablePutAll(net->filterparams, ret->filterparams) < 0)
|
|
goto error;
|
|
|
|
return ret;
|
|
|
|
error:
|
|
virNWFilterBindingDefFree(ret);
|
|
return NULL;
|
|
}
|