aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Berger <stefanb@us.ibm.com>2010-04-07 23:02:18 +0200
committerDaniel Veillard <veillard@redhat.com>2010-04-07 23:12:21 +0200
commit3bf24abc8c30df2d3075de7c6632b66b25e6ef30 (patch)
tree5b4c62f21c959e4f8e4907b5c37da42d22fa471c /src/nwfilter/nwfilter_gentech_driver.c
parentxenXMDomainDefineXML: remove dead store and useless/leaky virGetDomain (diff)
downloadlibvirt-3bf24abc8c30df2d3075de7c6632b66b25e6ef30.tar.gz
libvirt-3bf24abc8c30df2d3075de7c6632b66b25e6ef30.tar.bz2
libvirt-3bf24abc8c30df2d3075de7c6632b66b25e6ef30.zip
nwfilter: Support for learning a VM's IP address
This patch implements support for learning a VM's IP address. It uses the pcap library to listen on the VM's backend network interface (tap) or the physical ethernet device (macvtap) and tries to capture packets with source or destination MAC address of the VM and learn from DHCP Offers, ARP traffic, or first-sent IPv4 packet what the IP address of the VM's interface is. This then allows to instantiate the network traffic filtering rules without the user having to provide the IP parameter somewhere in the filter description or in the interface description as a parameter. This only supports to detect the parameter IP, which is for the assumed single IPv4 address of a VM. There is not support for interfaces that may have multiple IP addresses (IP aliasing) or IPv6 that may then require more than one valid IP address to be detected. A VM can have multiple independent interfaces that each uses a different IP address and in that case it will be attempted to detect each one of the address independently. So, when for example an interface description in the domain XML has looked like this up to now: <interface type='bridge'> <source bridge='mybridge'/> <model type='virtio'/> <filterref filter='clean-traffic'> <parameter name='IP' value='10.2.3.4'/> </filterref> </interface> you may omit the IP parameter: <interface type='bridge'> <source bridge='mybridge'/> <model type='virtio'/> <filterref filter='clean-traffic'/> </interface> Internally I am walking the 'tree' of a VM's referenced network filters and determine with the given variables which variables are missing. Now, the above IP parameter may be missing and this causes a libvirt-internal thread to be started that uses the pcap library's API to listen to the backend interface (in case of macvtap to the physical interface) in an attempt to determine the missing IP parameter. If the backend interface disappears the thread terminates assuming the VM was brought down. In case of a macvtap device a timeout is being used to wait for packets from the given VM (filtering by VM's interface MAC address). If the VM's macvtap device disappeared the thread also terminates. In all other cases it tries to determine the IP address of the VM and will then apply the rules late on the given interface, which would have happened immediately if the IP parameter had been explicitly given. In case an error happens while the firewall rules are applied, the VM's backend interface is 'down'ed preventing it to communicate. Reasons for failure for applying the network firewall rules may that an ebtables/iptables command failes or OOM errors. Essentially the same failure reasons may occur as when the firewall rules are applied immediately on VM start, except that due to the late application of the filtering rules the VM now is already running and cannot be hindered anymore from starting. Bringing down the whole VM would probably be considered too drastic. While a VM's IP address is attempted to be determined only limited updates to network filters are allowed. In particular it is prevented that filters are modified in such a way that they would introduce new variables. A caveat: The algorithm does not know which one is the appropriate IP address of a VM. If the VM spoofs an IP address in its first ARP traffic or IPv4 packets its filtering rules will be instantiated for this IP address, thus 'locking' it to the found IP address. So, it's still 'safer' to explicitly provide the IP address of a VM's interface in the filter description if it is known beforehand. * configure.ac: detect libpcap * libvirt.spec.in: require libpcap[-devel] if qemu is built * src/internal.h: add the new ATTRIBUTE_PACKED define * src/Makefile.am src/libvirt_private.syms: add the new modules and symbols * src/nwfilter/nwfilter_learnipaddr.[ch]: new module being added * src/nwfilter/nwfilter_driver.c src/conf/nwfilter_conf.[ch] src/nwfilter/nwfilter_ebiptables_driver.[ch] src/nwfilter/nwfilter_gentech_driver.[ch]: plu the new functionality in * tests/nwfilterxml2xmltest: extend testing
Diffstat (limited to 'src/nwfilter/nwfilter_gentech_driver.c')
-rw-r--r--src/nwfilter/nwfilter_gentech_driver.c406
1 files changed, 378 insertions, 28 deletions
diff --git a/src/nwfilter/nwfilter_gentech_driver.c b/src/nwfilter/nwfilter_gentech_driver.c
index 76967c10a..270dc2b05 100644
--- a/src/nwfilter/nwfilter_gentech_driver.c
+++ b/src/nwfilter/nwfilter_gentech_driver.c
@@ -24,6 +24,9 @@
#include <config.h>
#include <stdint.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <linux/if.h>
#include "internal.h"
@@ -34,12 +37,16 @@
#include "virterror_internal.h"
#include "nwfilter_gentech_driver.h"
#include "nwfilter_ebiptables_driver.h"
+#include "nwfilter_learnipaddr.h"
#define VIR_FROM_THIS VIR_FROM_NWFILTER
#define NWFILTER_STD_VAR_MAC "MAC"
+#define NWFILTER_STD_VAR_IP "IP"
+
+static int _virNWFilterTeardownFilter(const char *ifname);
static virNWFilterTechDriverPtr filter_tech_drivers[] = {
@@ -108,6 +115,8 @@ virNWFilterRuleInstFree(virNWFilterRuleInstPtr inst)
* @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.
@@ -116,7 +125,8 @@ virNWFilterRuleInstFree(virNWFilterRuleInstPtr inst)
*/
static int
virNWFilterVarHashmapAddStdValues(virNWFilterHashTablePtr table,
- char *macaddr)
+ char *macaddr,
+ char *ipaddr)
{
if (macaddr) {
if (virHashAddEntry(table->hashTable,
@@ -128,6 +138,16 @@ virNWFilterVarHashmapAddStdValues(virNWFilterHashTablePtr table,
}
}
+ if (ipaddr) {
+ if (virHashAddEntry(table->hashTable,
+ NWFILTER_STD_VAR_IP,
+ ipaddr) < 0) {
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+ "%s", _("Could not add variable 'IP' to hashmap"));
+ return 1;
+ }
+ }
+
return 0;
}
@@ -135,22 +155,24 @@ virNWFilterVarHashmapAddStdValues(virNWFilterHashTablePtr table,
/**
* virNWFilterCreateVarHashmap:
* @macaddr: pointer to string containing formatted MAC address of interface
+ * @ipaddr: pointer to string containing formatted IP address used by
+ * VM on this interface; may be NULL
*
* Create a hashmap used for evaluating the firewall rules. Initializes
- * it with the standard variable 'MAC'.
+ * it with the standard variable 'MAC' and 'IP' if provided.
*
* Returns pointer to hashmap, NULL if an error occcurred and error message
* is attached to the virConnect object.
*/
virNWFilterHashTablePtr
-virNWFilterCreateVarHashmap(char *macaddr) {
+virNWFilterCreateVarHashmap(char *macaddr, char *ipaddr) {
virNWFilterHashTablePtr table = virNWFilterHashTableCreate(0);
if (!table) {
virReportOOMError();
return NULL;
}
- if (virNWFilterVarHashmapAddStdValues(table, macaddr)) {
+ if (virNWFilterVarHashmapAddStdValues(table, macaddr, ipaddr)) {
virNWFilterHashTableFree(table);
return NULL;
}
@@ -276,9 +298,9 @@ _virNWFilterInstantiateRec(virConnectPtr conn,
virNWFilterHashTablePtr vars,
int *nEntries,
virNWFilterRuleInstPtr **insts,
- enum instCase useNewFilter, int *foundNewFilter)
+ enum instCase useNewFilter, int *foundNewFilter,
+ virNWFilterDriverStatePtr driver)
{
- virNWFilterDriverStatePtr driver = conn->nwfilterPrivateData;
virNWFilterPoolObjPtr obj;
int rc = 0;
int i;
@@ -356,7 +378,95 @@ _virNWFilterInstantiateRec(virConnectPtr conn,
tmpvars,
nEntries, insts,
useNewFilter,
- foundNewFilter);
+ foundNewFilter,
+ driver);
+
+ virNWFilterHashTableFree(tmpvars);
+
+ virNWFilterPoolObjUnlock(obj);
+ if (rc)
+ break;
+ } else {
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+ _("referenced filter '%s' is missing"),
+ inc->filterref);
+ rc = 1;
+ break;
+ }
+ }
+ }
+ return rc;
+}
+
+
+static int
+virNWFilterDetermineMissingVarsRec(virConnectPtr conn,
+ virNWFilterDefPtr filter,
+ virNWFilterHashTablePtr vars,
+ virNWFilterHashTablePtr missing_vars,
+ int useNewFilter,
+ virNWFilterDriverStatePtr driver)
+{
+ virNWFilterPoolObjPtr obj;
+ int rc = 0;
+ int i, j;
+ virNWFilterDefPtr next_filter;
+
+ 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->nvars; j++) {
+ if (!virHashLookup(vars->hashTable, rule->vars[j])) {
+ virNWFilterHashTablePut(missing_vars, rule->vars[j],
+ strdup("1"), 1);
+ }
+ }
+ } else if (inc) {
+ VIR_DEBUG("Following filter %s\n", inc->filterref);
+ obj = virNWFilterPoolObjFindByName(&driver->pools,
+ inc->filterref);
+ if (obj) {
+
+ if (obj->wantRemoved) {
+ virNWFilterReportError(VIR_ERR_NO_NWFILTER,
+ _("Filter '%s' is in use."),
+ inc->filterref);
+ rc = 1;
+ virNWFilterPoolObjUnlock(obj);
+ break;
+ }
+
+ // create a temporary hashmap for depth-first tree traversal
+ virNWFilterHashTablePtr tmpvars =
+ virNWFilterCreateVarsFrom(inc->params,
+ vars);
+ if (!tmpvars) {
+ virReportOOMError();
+ rc = 1;
+ virNWFilterPoolObjUnlock(obj);
+ break;
+ }
+
+ next_filter = obj->def;
+
+ switch (useNewFilter) {
+ case INSTANTIATE_FOLLOW_NEWFILTER:
+ if (obj->newDef) {
+ next_filter = obj->newDef;
+ }
+ break;
+ case INSTANTIATE_ALWAYS:
+ break;
+ }
+
+ rc = virNWFilterDetermineMissingVarsRec(conn,
+ next_filter,
+ tmpvars,
+ missing_vars,
+ useNewFilter,
+ driver);
virNWFilterHashTableFree(tmpvars);
@@ -429,9 +539,12 @@ virNWFilterInstantiate(virConnectPtr conn,
enum virDomainNetType nettype,
virNWFilterDefPtr filter,
const char *ifname,
+ const char *linkdev,
virNWFilterHashTablePtr vars,
enum instCase useNewFilter, int *foundNewFilter,
- bool teardownOld)
+ bool teardownOld,
+ const unsigned char *macaddr,
+ virNWFilterDriverStatePtr driver)
{
int rc;
int j, nptrs;
@@ -440,6 +553,44 @@ virNWFilterInstantiate(virConnectPtr conn,
void **ptrs = NULL;
int instantiate = 1;
+ virNWFilterLockFilterUpdates();
+
+ virNWFilterHashTablePtr missing_vars = virNWFilterHashTableCreate(0);
+ if (!missing_vars) {
+ virReportOOMError();
+ rc = 1;
+ goto err_exit;
+ }
+
+ rc = virNWFilterDetermineMissingVarsRec(conn,
+ filter,
+ vars,
+ missing_vars,
+ useNewFilter,
+ driver);
+ if (rc)
+ goto err_exit;
+
+ if (virHashSize(missing_vars->hashTable) == 1) {
+ if (virHashLookup(missing_vars->hashTable,
+ NWFILTER_STD_VAR_IP) != NULL) {
+ if (virNWFilterLookupLearnReq(ifname) == NULL) {
+ rc = virNWFilterLearnIPAddress(ifname,
+ linkdev,
+ nettype, macaddr,
+ filter->name,
+ vars, driver,
+ DETECT_DHCP|DETECT_STATIC);
+ }
+ goto err_exit;
+ }
+ rc = 1;
+ goto err_exit;
+ } else if (virHashSize(missing_vars->hashTable) > 1) {
+ rc = 1;
+ goto err_exit;
+ }
+
rc = _virNWFilterInstantiateRec(conn,
techdriver,
nettype,
@@ -447,7 +598,8 @@ virNWFilterInstantiate(virConnectPtr conn,
ifname,
vars,
&nEntries, &insts,
- useNewFilter, foundNewFilter);
+ useNewFilter, foundNewFilter,
+ driver);
if (rc)
goto err_exit;
@@ -478,24 +630,33 @@ virNWFilterInstantiate(virConnectPtr conn,
err_exit:
+ virNWFilterUnlockFilterUpdates();
+
for (j = 0; j < nEntries; j++)
virNWFilterRuleInstFree(insts[j]);
VIR_FREE(insts);
+ virNWFilterHashTableFree(missing_vars);
+
return rc;
}
static int
-_virNWFilterInstantiateFilter(virConnectPtr conn,
- const virDomainNetDefPtr net,
- bool teardownOld,
- enum instCase useNewFilter)
+__virNWFilterInstantiateFilter(virConnectPtr conn,
+ bool teardownOld,
+ const char *ifname,
+ const char *linkdev,
+ enum virDomainNetType nettype,
+ const unsigned char *macaddr,
+ const char *filtername,
+ virNWFilterHashTablePtr filterparams,
+ enum instCase useNewFilter,
+ virNWFilterDriverStatePtr driver)
{
int rc;
const char *drvname = EBIPTABLES_DRIVER_ID;
- virNWFilterDriverStatePtr driver = conn->nwfilterPrivateData;
virNWFilterTechDriverPtr techdriver;
virNWFilterPoolObjPtr obj;
virNWFilterHashTablePtr vars, vars1;
@@ -503,6 +664,8 @@ _virNWFilterInstantiateFilter(virConnectPtr conn,
char vmmacaddr[VIR_MAC_STRING_BUFLEN] = {0};
int foundNewFilter = 0;
char *str_macaddr = NULL;
+ const char *ipaddr;
+ char *str_ipaddr = NULL;
techdriver = virNWFilterTechDriverForName(drvname);
@@ -514,25 +677,25 @@ _virNWFilterInstantiateFilter(virConnectPtr conn,
return 1;
}
- VIR_DEBUG("filter name: %s", net->filter);
+ VIR_DEBUG("filter name: %s", filtername);
- obj = virNWFilterPoolObjFindByName(&driver->pools, net->filter);
+ obj = virNWFilterPoolObjFindByName(&driver->pools, filtername);
if (!obj) {
virNWFilterReportError(VIR_ERR_NO_NWFILTER,
_("Could not find filter '%s'"),
- net->filter);
+ filtername);
return 1;
}
if (obj->wantRemoved) {
virNWFilterReportError(VIR_ERR_NO_NWFILTER,
_("Filter '%s' is in use."),
- net->filter);
+ filtername);
rc = 1;
goto err_exit;
}
- virFormatMacAddr(net->mac, vmmacaddr);
+ virFormatMacAddr(macaddr, vmmacaddr);
str_macaddr = strdup(vmmacaddr);
if (!str_macaddr) {
virReportOOMError();
@@ -540,16 +703,27 @@ _virNWFilterInstantiateFilter(virConnectPtr conn,
goto err_exit;
}
- vars1 = virNWFilterCreateVarHashmap(str_macaddr);
+ ipaddr = virNWFilterGetIpAddrForIfname(ifname);
+ if (ipaddr) {
+ str_ipaddr = strdup(ipaddr);
+ if (!str_ipaddr) {
+ virReportOOMError();
+ rc = 1;
+ goto err_exit;
+ }
+ }
+
+ vars1 = virNWFilterCreateVarHashmap(str_macaddr, str_ipaddr);
if (!vars1) {
rc = 1;
goto err_exit;
}
str_macaddr = NULL;
+ str_ipaddr = NULL;
vars = virNWFilterCreateVarsFrom(vars1,
- net->filterparams);
+ filterparams);
if (!vars) {
rc = 1;
goto err_exit_vars1;
@@ -571,12 +745,15 @@ _virNWFilterInstantiateFilter(virConnectPtr conn,
rc = virNWFilterInstantiate(conn,
techdriver,
- net->type,
+ nettype,
filter,
- net->ifname,
+ ifname,
+ linkdev,
vars,
useNewFilter, &foundNewFilter,
- teardownOld);
+ teardownOld,
+ macaddr,
+ driver);
virNWFilterHashTableFree(vars);
@@ -584,15 +761,180 @@ err_exit_vars1:
virNWFilterHashTableFree(vars1);
err_exit:
-
virNWFilterPoolObjUnlock(obj);
+ VIR_FREE(str_ipaddr);
VIR_FREE(str_macaddr);
return rc;
}
+static int
+_virNWFilterInstantiateFilter(virConnectPtr conn,
+ const virDomainNetDefPtr net,
+ bool teardownOld,
+ enum instCase useNewFilter)
+{
+ const char *linkdev = (net->type == VIR_DOMAIN_NET_TYPE_DIRECT)
+ ? net->data.direct.linkdev
+ : NULL;
+ return __virNWFilterInstantiateFilter(conn,
+ teardownOld,
+ net->ifname,
+ linkdev,
+ net->type,
+ net->mac,
+ net->filter,
+ net->filterparams,
+ useNewFilter,
+ conn->nwfilterPrivateData);
+}
+
+
+// FIXME: move chgIfFlags, ifUp, checkIf into common file & share w/ macvtap.c
+
+/*
+ * chgIfFlags: Change flags on an interface
+ * @ifname : name of the interface
+ * @flagclear : the flags to clear
+ * @flagset : the flags to set
+ *
+ * The new flags of the interface will be calculated as
+ * flagmask = (~0 ^ flagclear)
+ * newflags = (curflags & flagmask) | flagset;
+ *
+ * Returns 0 on success, errno on failure.
+ */
+static int chgIfFlags(const char *ifname, short flagclear, short flagset) {
+ struct ifreq ifr;
+ int rc = 0;
+ int flags;
+ short flagmask = (~0 ^ flagclear);
+ int fd = socket(PF_PACKET, SOCK_DGRAM, 0);
+
+ if (fd < 0)
+ return errno;
+
+ if (virStrncpy(ifr.ifr_name,
+ ifname, strlen(ifname), sizeof(ifr.ifr_name)) == NULL) {
+ rc = ENODEV;
+ goto err_exit;
+ }
+
+ if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) {
+ rc = errno;
+ goto err_exit;
+ }
+
+ flags = (ifr.ifr_flags & flagmask) | flagset;
+
+ if (ifr.ifr_flags != flags) {
+ ifr.ifr_flags = flags;
+
+ if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0)
+ rc = errno;
+ }
+
+err_exit:
+ close(fd);
+ return rc;
+}
+
+/*
+ * ifUp
+ * @name: name of the interface
+ * @up: 1 for up, 0 for down
+ *
+ * Function to control if an interface is activated (up, 1) or not (down, 0)
+ *
+ * Returns 0 in case of success or an errno code in case of failure.
+ */
+static int
+ifUp(const char *name, int up)
+{
+ return chgIfFlags(name,
+ (up) ? 0 : IFF_UP,
+ (up) ? IFF_UP : 0);
+}
+
+
+/**
+ * checkIf
+ *
+ * @ifname: Name of the interface
+ * @macaddr: expected MAC address of the interface
+ *
+ * FIXME: the interface's index is another good parameter to check
+ *
+ * Determine whether a given interface is still available. If so,
+ * it must have the given MAC address.
+ *
+ * Returns an error code ENODEV in case the interface does not exist
+ * anymore or its MAC address is different, 0 otherwise.
+ */
+int
+checkIf(const char *ifname, const unsigned char *macaddr)
+{
+ struct ifreq ifr;
+ int fd = socket(PF_PACKET, SOCK_DGRAM, 0);
+ int rc = 0;
+
+ if (fd < 0)
+ return errno;
+
+ if (virStrncpy(ifr.ifr_name,
+ ifname, strlen(ifname), sizeof(ifr.ifr_name)) == NULL) {
+ rc = ENODEV;
+ goto err_exit;
+ }
+
+ if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) {
+ rc = errno;
+ goto err_exit;
+ }
+
+ if (memcmp(&ifr.ifr_hwaddr.sa_data, macaddr, 6) != 0)
+ rc = ENODEV;
+
+ err_exit:
+ close(fd);
+ return rc;
+}
+
+
+int
+virNWFilterInstantiateFilterLate(virConnectPtr conn,
+ const char *ifname,
+ const char *linkdev,
+ enum virDomainNetType nettype,
+ const unsigned char *macaddr,
+ const char *filtername,
+ virNWFilterHashTablePtr filterparams,
+ virNWFilterDriverStatePtr driver)
+{
+ int rc;
+ rc = __virNWFilterInstantiateFilter(conn,
+ 1,
+ ifname,
+ linkdev,
+ nettype,
+ macaddr,
+ filtername,
+ filterparams,
+ INSTANTIATE_ALWAYS,
+ driver);
+ if (rc) {
+ //something went wrong... 'DOWN' the interface
+ if (ifUp(ifname ,0)) {
+ // assuming interface disappeared...
+ _virNWFilterTeardownFilter(ifname);
+ }
+ }
+ return rc;
+}
+
+
int
virNWFilterInstantiateFilter(virConnectPtr conn,
const virDomainNetDefPtr net)
@@ -649,8 +991,8 @@ virNWFilterTearOldFilter(virConnectPtr conn,
}
-int
-virNWFilterTeardownFilter(const virDomainNetDefPtr net)
+static int
+_virNWFilterTeardownFilter(const char *ifname)
{
const char *drvname = EBIPTABLES_DRIVER_ID;
virNWFilterTechDriverPtr techdriver;
@@ -663,13 +1005,21 @@ virNWFilterTeardownFilter(const virDomainNetDefPtr net)
drvname);
return 1;
}
+ techdriver->allTeardown(ifname);
- techdriver->allTeardown(net->ifname);
+ virNWFilterDelIpAddrForIfname(ifname);
return 0;
}
+int
+virNWFilterTeardownFilter(const virDomainNetDefPtr net)
+{
+ return _virNWFilterTeardownFilter(net->ifname);
+}
+
+
void
virNWFilterDomainFWUpdateCB(void *payload,
const char *name ATTRIBUTE_UNUSED,