diff --git a/configure.ac b/configure.ac index e13961efa7..dd1298f178 100644 --- a/configure.ac +++ b/configure.ac @@ -41,6 +41,7 @@ XMLRPC_REQUIRED=1.14.0 HAL_REQUIRED=0.5.0 DEVMAPPER_REQUIRED=1.0.0 LIBCURL_REQUIRED="7.18.0" +LIBPCAP_REQUIRED="1.0.0" dnl Checks for C compiler. AC_PROG_CC @@ -1045,6 +1046,39 @@ AC_SUBST([NUMACTL_CFLAGS]) AC_SUBST([NUMACTL_LIBS]) +dnl pcap lib +LIBPCAP_CONFIG="pcap-config" +LIBPCAP_CFLAGS="" +LIBPCAP_LIBS="" +LIBPCAP_FOUND="no" + +AC_ARG_WITH([libpcap], AC_HELP_STRING([--with-libpcap=@<:@PFX@:>@], [libpcap location])) +if test "$with_qemu" = "yes"; then + if test "x$with_libpcap" != "xno" ; then + if test "x$with_libpcap" != "x" ; then + LIBPCAP_CONFIG=$with_libpcap/bin/$LIBPCAP_CONFIG + fi + AC_MSG_CHECKING(libpcap $LIBPCAP_CONFIG >= $LIBPCAP_REQUIRED ) + if ! $LIBPCAP_CONFIG --libs > /dev/null 2>&1 ; then + AC_MSG_RESULT(no) + else + LIBPCAP_LIBS="`$LIBPCAP_CONFIG --libs`" + LIBPCAP_CFLAGS="`$LIBPCAP_CONFIG --cflags`" + LIBPCAP_FOUND="yes" + AC_MSG_RESULT(yes) + fi + fi +fi + +if test "x$LIBPCAP_FOUND" = "xyes"; then + AC_DEFINE_UNQUOTED([HAVE_LIBPCAP], 1, [whether libpcap can be used]) +fi + +AC_SUBST([LIBPCAP_CFLAGS]) +AC_SUBST([LIBPCAP_LIBS]) + + + dnl dnl Checks for the UML driver dnl @@ -2129,6 +2163,11 @@ AC_MSG_NOTICE([ xmlrpc: $XMLRPC_CFLAGS $XMLRPC_LIBS]) else AC_MSG_NOTICE([ xmlrpc: no]) fi +if test "$with_qemu" = "yes" ; then +AC_MSG_NOTICE([ pcap: $LIBPCAP_CFLAGS $LIBPCAP_LIBS]) +else +AC_MSG_NOTICE([ pcap: no]) +fi AC_MSG_NOTICE([]) AC_MSG_NOTICE([Test suite]) AC_MSG_NOTICE([]) diff --git a/libvirt.spec.in b/libvirt.spec.in index dcbc139dcb..834b1a666e 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -61,6 +61,7 @@ %define with_udev 0%{!?_without_udev:0} %define with_hal 0%{!?_without_hal:0} %define with_yajl 0%{!?_without_yajl:0} +%define with_libpcap 0%{!?_without_libpcap:0} # Non-server/HV driver defaults which are always enabled %define with_python 0%{!?_without_python:1} @@ -147,6 +148,11 @@ %define with_yajl 0%{!?_without_yajl:%{server_drivers}} %endif +# Enable libpcap library +%if %{with_qemu} +%define with_libpcap 0%{!?_without_libpcap:%{server_drivers}} +%endif + # Force QEMU to run as non-root %if 0%{?fedora} >= 12 || 0%{?rhel} >= 6 %define qemu_user qemu @@ -266,6 +272,9 @@ BuildRequires: libpciaccess-devel >= 0.10.9 %if %{with_yajl} BuildRequires: yajl-devel %endif +%if %{with_libpcap} +BuildRequires: libpcap-devel +%endif %if %{with_avahi} BuildRequires: avahi-devel %endif diff --git a/src/Makefile.am b/src/Makefile.am index cd1848ef18..db69ab96b8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -308,7 +308,9 @@ NWFILTER_DRIVER_SOURCES = \ nwfilter/nwfilter_gentech_driver.c \ nwfilter/nwfilter_gentech_driver.h \ nwfilter/nwfilter_ebiptables_driver.c \ - nwfilter/nwfilter_ebiptables_driver.h + nwfilter/nwfilter_ebiptables_driver.h \ + nwfilter/nwfilter_learnipaddr.c \ + nwfilter/nwfilter_learnipaddr.h # Security framework and drivers for various models @@ -764,10 +766,11 @@ else libvirt_la_LIBADD += libvirt_driver_nwfilter.la noinst_LTLIBRARIES += libvirt_driver_nwfilter.la endif -libvirt_driver_nwfilter_la_CFLAGS = \ +libvirt_driver_nwfilter_la_CFLAGS = $(LIBPCAP_CFLAGS) \ -I@top_srcdir@/src/conf +libvirt_driver_nwfilter_la_LDFLAGS = $(LIBPCAP_LIBS) if WITH_DRIVER_MODULES -libvirt_driver_nwfilter_la_LDFLAGS = -module -avoid-version ../gnulib/lib/libgnu.la +libvirt_driver_nwfilter_la_LDFLAGS += -module -avoid-version ../gnulib/lib/libgnu.la endif libvirt_driver_nwfilter_la_SOURCES = $(NWFILTER_DRIVER_SOURCES) endif @@ -912,6 +915,7 @@ libvirt_la_LDFLAGS = $(VERSION_SCRIPT_FLAGS)libvirt.syms \ -version-info $(LIBVIRT_VERSION_INFO) \ $(COVERAGE_CFLAGS:-f%=-Wc,-f%) \ $(LIBXML_LIBS) \ + $(LIBPCAP_LIBS) \ $(DRIVER_MODULE_LIBS) \ $(CYGWIN_EXTRA_LDFLAGS) $(MINGW_EXTRA_LDFLAGS) libvirt_la_CFLAGS = $(COVERAGE_CFLAGS) -DIN_LIBVIRT @@ -960,8 +964,8 @@ libvirt_lxc_SOURCES = \ $(NODE_INFO_SOURCES) \ $(ENCRYPTION_CONF_SOURCES) \ $(DOMAIN_CONF_SOURCES) \ - $(NWFILTER_PARAM_CONF_SOURCES) \ - $(CPU_CONF_SOURCES) + $(CPU_CONF_SOURCES) \ + $(NWFILTER_PARAM_CONF_SOURCES) libvirt_lxc_LDFLAGS = $(WARN_CFLAGS) $(COVERAGE_LDCFLAGS) $(CAPNG_LIBS) $(YAJL_LIBS) libvirt_lxc_LDADD = $(LIBXML_LIBS) $(NUMACTL_LIBS) ../gnulib/lib/libgnu.la libvirt_lxc_CFLAGS = \ diff --git a/src/conf/nwfilter_conf.c b/src/conf/nwfilter_conf.c index 16c1a255ba..3991c3bf98 100644 --- a/src/conf/nwfilter_conf.c +++ b/src/conf/nwfilter_conf.c @@ -114,17 +114,18 @@ struct int_map { */ static virMutex updateMutex; -static void +void virNWFilterLockFilterUpdates(void) { virMutexLock(&updateMutex); } -static void +void virNWFilterUnlockFilterUpdates(void) { virMutexUnlock(&updateMutex); } + /* * attribute names for the rules XML */ @@ -2615,7 +2616,7 @@ int virNWFilterConfLayerInit(virHashIterator domUpdateCB) { virNWFilterDomainFWUpdateCB = domUpdateCB; - if (virMutexInit(&updateMutex)) + if (virMutexInitRecursive(&updateMutex)) return 1; return 0; diff --git a/src/conf/nwfilter_conf.h b/src/conf/nwfilter_conf.h index 37d75dfe54..fc3ce16e7a 100644 --- a/src/conf/nwfilter_conf.h +++ b/src/conf/nwfilter_conf.h @@ -558,6 +558,9 @@ virNWFilterDefPtr virNWFilterDefParseFile(virConnectPtr conn, void virNWFilterPoolObjLock(virNWFilterPoolObjPtr obj); void virNWFilterPoolObjUnlock(virNWFilterPoolObjPtr obj); +void virNWFilterLockFilterUpdates(void); +void virNWFilterUnlockFilterUpdates(void); + int virNWFilterConfLayerInit(virHashIterator domUpdateCB); void virNWFilterConfLayerShutdown(void); diff --git a/src/internal.h b/src/internal.h index 807288b868..2e73210d0e 100644 --- a/src/internal.h +++ b/src/internal.h @@ -138,6 +138,23 @@ # endif # endif +/** + * ATTRIBUTE_PACKED + * + * force a structure to be packed, i.e. not following architecture and + * compiler best alignments for its sub components. It's needed for example + * for the network filetering code when defining the content of raw + * ethernet packets. + * Others compiler than gcc may use something different e.g. #pragma pack(1) + */ +# ifndef ATTRIBUTE_PACKED +# if __GNUC_PREREQ (3, 3) +# define ATTRIBUTE_PACKED __attribute__((packed)) +# else +# error "Need an __attribute__((packed)) equivalent" +# endif +# endif + # ifndef ATTRIBUTE_NONNULL # if __GNUC_PREREQ (3, 3) # define ATTRIBUTE_NONNULL(m) __attribute__((__nonnull__(m))) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 1682b25cd1..814e2d8bc8 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -499,6 +499,8 @@ virNWFilterRegisterCallbackDriver; virNWFilterTestUnassignDef; virNWFilterConfLayerInit; virNWFilterConfLayerShutdown; +virNWFilterLockFilterUpdates; +virNWFilterUnlockFilterUpdates; #nwfilter_params.h @@ -514,6 +516,16 @@ virNWFilterInstantiateFilter; virNWFilterTeardownFilter; +#nwfilter_learnipaddr.h +ipAddressMap; +ipAddressMapLock; +pendingLearnReq; +pendingLearnReqLock; +virNWFilterGetIpAddrForIfname; +virNWFilterDelIpAddrForIfname; +virNWFilterLookupLearnReq; + + # pci.h pciGetDevice; pciFreeDevice; diff --git a/src/nwfilter/nwfilter_driver.c b/src/nwfilter/nwfilter_driver.c index 58df4e126a..f366005c70 100644 --- a/src/nwfilter/nwfilter_driver.c +++ b/src/nwfilter/nwfilter_driver.c @@ -37,6 +37,8 @@ #include "nwfilter_gentech_driver.h" +#include "nwfilter_learnipaddr.h" + #define VIR_FROM_THIS VIR_FROM_NWFILTER #define nwfilterLog(msg...) fprintf(stderr, msg) @@ -65,9 +67,12 @@ static int nwfilterDriverStartup(int privileged) { char *base = NULL; - if (virNWFilterConfLayerInit(virNWFilterDomainFWUpdateCB) < 0) + if (virNWFilterLearnInit() < 0) return -1; + if (virNWFilterConfLayerInit(virNWFilterDomainFWUpdateCB) < 0) + goto conf_init_err; + if (VIR_ALLOC(driverState) < 0) goto alloc_err_exit; @@ -120,6 +125,9 @@ error: alloc_err_exit: virNWFilterConfLayerShutdown(); +conf_init_err: + virNWFilterLearnShutdown(); + return -1; } @@ -413,5 +421,6 @@ static virStateDriver stateDriver = { int nwfilterRegister(void) { virRegisterNWFilterDriver(&nwfilterDriver); virRegisterStateDriver(&stateDriver); + virNWFilterLearnInit(); return 0; } diff --git a/src/nwfilter/nwfilter_ebiptables_driver.c b/src/nwfilter/nwfilter_ebiptables_driver.c index 4ec7edffc0..23c50dfd12 100644 --- a/src/nwfilter/nwfilter_ebiptables_driver.c +++ b/src/nwfilter/nwfilter_ebiptables_driver.c @@ -2519,6 +2519,242 @@ ebiptablesInstCommand(virBufferPtr buf, } +/** + * ebtablesApplyBasicRules + * + * @conn: virConnect object + * @ifname: name of the backend-interface to which to apply the rules + * @macaddr: MAC address the VM is using in packets sent through the + * interface + * + * Returns 0 on success, 1 on failure with the rules removed + * + * Apply basic filtering rules on the given interface + * - filtering for MAC address spoofing + * - allowing IPv4 & ARP traffic + */ +int +ebtablesApplyBasicRules(const char *ifname, + const unsigned char *macaddr) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + int cli_status; + char chain[MAX_CHAINNAME_LENGTH]; + char chainPrefix = CHAINPREFIX_HOST_IN_TEMP; + char macaddr_str[VIR_MAC_STRING_BUFLEN]; + + virFormatMacAddr(macaddr, macaddr_str); + + ebtablesUnlinkTmpRootChain(&buf, 1, ifname); + ebtablesUnlinkTmpRootChain(&buf, 0, ifname); + ebtablesRemoveTmpSubChains(&buf, ifname); + ebtablesRemoveTmpRootChain(&buf, 1, ifname); + ebtablesRemoveTmpRootChain(&buf, 0, ifname); + ebiptablesExecCLI(&buf, &cli_status); + + ebtablesCreateTmpRootChain(&buf, 1, ifname, 1); + + PRINT_ROOT_CHAIN(chain, chainPrefix, ifname); + virBufferVSprintf(&buf, + CMD_DEF(EBTABLES_CMD + " -t %s -A %s -s ! %s -j DROP") CMD_SEPARATOR + CMD_EXEC + "%s", + + EBTABLES_DEFAULT_TABLE, + chain, + macaddr_str, + CMD_STOPONERR(1)); + + virBufferVSprintf(&buf, + CMD_DEF(EBTABLES_CMD + " -t %s -A %s -p IPv4 -j ACCEPT") CMD_SEPARATOR + CMD_EXEC + "%s", + + EBTABLES_DEFAULT_TABLE, + chain, + CMD_STOPONERR(1)); + + virBufferVSprintf(&buf, + CMD_DEF(EBTABLES_CMD + " -t %s -A %s -p ARP -j ACCEPT") CMD_SEPARATOR + CMD_EXEC + "%s", + + EBTABLES_DEFAULT_TABLE, + chain, + CMD_STOPONERR(1)); + + virBufferVSprintf(&buf, + CMD_DEF(EBTABLES_CMD + " -t %s -A %s -j DROP") CMD_SEPARATOR + CMD_EXEC + "%s", + + EBTABLES_DEFAULT_TABLE, + chain, + CMD_STOPONERR(1)); + + ebtablesLinkTmpRootChain(&buf, 1, ifname, 1); + + if (ebiptablesExecCLI(&buf, &cli_status) || cli_status != 0) + goto tear_down_tmpebchains; + + return 0; + +tear_down_tmpebchains: + ebtablesRemoveBasicRules(ifname); + + virNWFilterReportError(VIR_ERR_BUILD_FIREWALL, + "%s", + _("Some rules could not be created.")); + + return 1; +} + + +/** + * ebtablesApplyDHCPOnlyRules + * + * @ifname: name of the backend-interface to which to apply the rules + * @macaddr: MAC address the VM is using in packets sent through the + * interface + * @dhcpserver: The DHCP server from which the VM may receive traffic + * from; may be NULL + * + * Returns 0 on success, 1 on failure with the rules removed + * + * Apply filtering rules so that the VM can only send and receive + * DHCP traffic and nothing else. + */ +int +ebtablesApplyDHCPOnlyRules(const char *ifname, + const unsigned char *macaddr, + const char *dhcpserver) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + int cli_status; + char chain_in [MAX_CHAINNAME_LENGTH], + chain_out[MAX_CHAINNAME_LENGTH]; + char macaddr_str[VIR_MAC_STRING_BUFLEN]; + char *srcIPParam = NULL; + + if (dhcpserver) { + virBufferVSprintf(&buf, " --ip-src %s", dhcpserver); + if (virBufferError(&buf)) + return 1; + srcIPParam = virBufferContentAndReset(&buf); + } + + virFormatMacAddr(macaddr, macaddr_str); + + ebtablesUnlinkTmpRootChain(&buf, 1, ifname); + ebtablesUnlinkTmpRootChain(&buf, 0, ifname); + ebtablesRemoveTmpSubChains(&buf, ifname); + ebtablesRemoveTmpRootChain(&buf, 1, ifname); + ebtablesRemoveTmpRootChain(&buf, 0, ifname); + ebiptablesExecCLI(&buf, &cli_status); + + ebtablesCreateTmpRootChain(&buf, 1, ifname, 1); + ebtablesCreateTmpRootChain(&buf, 0, ifname, 1); + + PRINT_ROOT_CHAIN(chain_in , CHAINPREFIX_HOST_IN_TEMP , ifname); + PRINT_ROOT_CHAIN(chain_out, CHAINPREFIX_HOST_OUT_TEMP, ifname); + + virBufferVSprintf(&buf, + CMD_DEF(EBTABLES_CMD + " -t %s -A %s" + " -s %s -d Broadcast " + " -p ipv4 --ip-protocol udp" + " --ip-src 0.0.0.0 --ip-dst 255.255.255.255" + " --ip-sport 68 --ip-dport 67" + " -j ACCEPT") CMD_SEPARATOR + CMD_EXEC + "%s", + + EBTABLES_DEFAULT_TABLE, + chain_in, + macaddr_str, + CMD_STOPONERR(1)); + + virBufferVSprintf(&buf, + CMD_DEF(EBTABLES_CMD + " -t %s -A %s -j DROP") CMD_SEPARATOR + CMD_EXEC + "%s", + + EBTABLES_DEFAULT_TABLE, + chain_in, + CMD_STOPONERR(1)); + + virBufferVSprintf(&buf, + CMD_DEF(EBTABLES_CMD + " -t %s -A %s" + " -d %s" + " -p ipv4 --ip-protocol udp" + " %s" + " --ip-sport 67 --ip-dport 68" + " -j ACCEPT") CMD_SEPARATOR + CMD_EXEC + "%s", + + EBTABLES_DEFAULT_TABLE, + chain_out, + macaddr_str, + srcIPParam != NULL ? srcIPParam : "", + CMD_STOPONERR(1)); + + virBufferVSprintf(&buf, + CMD_DEF(EBTABLES_CMD + " -t %s -A %s -j DROP") CMD_SEPARATOR + CMD_EXEC + "%s", + + EBTABLES_DEFAULT_TABLE, + chain_out, + CMD_STOPONERR(1)); + + ebtablesLinkTmpRootChain(&buf, 1, ifname, 1); + ebtablesLinkTmpRootChain(&buf, 0, ifname, 1); + + if (ebiptablesExecCLI(&buf, &cli_status) || cli_status != 0) + goto tear_down_tmpebchains; + + VIR_FREE(srcIPParam); + + return 0; + +tear_down_tmpebchains: + ebtablesRemoveBasicRules(ifname); + + virNWFilterReportError(VIR_ERR_BUILD_FIREWALL, + "%s", + _("Some rules could not be created.")); + + VIR_FREE(srcIPParam); + + return 1; +} + + +int +ebtablesRemoveBasicRules(const char *ifname) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + int cli_status; + + ebtablesUnlinkTmpRootChain(&buf, 1, ifname); + ebtablesUnlinkTmpRootChain(&buf, 0, ifname); + ebtablesRemoveTmpSubChains(&buf, ifname); + ebtablesRemoveTmpRootChain(&buf, 1, ifname); + ebtablesRemoveTmpRootChain(&buf, 0, ifname); + + ebiptablesExecCLI(&buf, &cli_status); + return 0; +} + + static int ebiptablesRuleOrderSort(const void *a, const void *b) { diff --git a/src/nwfilter/nwfilter_ebiptables_driver.h b/src/nwfilter/nwfilter_ebiptables_driver.h index d99de3b719..4129d05d8d 100644 --- a/src/nwfilter/nwfilter_ebiptables_driver.h +++ b/src/nwfilter/nwfilter_ebiptables_driver.h @@ -45,4 +45,12 @@ extern virNWFilterTechDriver ebiptables_driver; # define EBIPTABLES_DRIVER_ID "ebiptables" + +int ebtablesApplyBasicRules(const char *ifname, + const unsigned char *macaddr); +int ebtablesApplyDHCPOnlyRules(const char *ifname, + const unsigned char *macaddr, + const char *dhcpServer); +int ebtablesRemoveBasicRules(const char *ifname); + #endif diff --git a/src/nwfilter/nwfilter_gentech_driver.c b/src/nwfilter/nwfilter_gentech_driver.c index 76967c10a2..270dc2b05b 100644 --- a/src/nwfilter/nwfilter_gentech_driver.c +++ b/src/nwfilter/nwfilter_gentech_driver.c @@ -24,6 +24,9 @@ #include #include +#include +#include +#include #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, diff --git a/src/nwfilter/nwfilter_gentech_driver.h b/src/nwfilter/nwfilter_gentech_driver.h index 1068102357..ebb0b64f72 100644 --- a/src/nwfilter/nwfilter_gentech_driver.h +++ b/src/nwfilter/nwfilter_gentech_driver.h @@ -45,12 +45,24 @@ int virNWFilterRollbackUpdateFilter(virConnectPtr conn, int virNWFilterTearOldFilter(virConnectPtr conn, const virDomainNetDefPtr net); +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 virNWFilterTeardownFilter(const virDomainNetDefPtr net); -virNWFilterHashTablePtr virNWFilterCreateVarHashmap(char *macaddr); +virNWFilterHashTablePtr virNWFilterCreateVarHashmap(char *macaddr, + char *ipaddr); void virNWFilterDomainFWUpdateCB(void *payload, const char *name ATTRIBUTE_UNUSED, void *data); +int checkIf(const char *ifname, const unsigned char *macaddr); + #endif diff --git a/src/nwfilter/nwfilter_learnipaddr.c b/src/nwfilter/nwfilter_learnipaddr.c new file mode 100644 index 0000000000..4a74582967 --- /dev/null +++ b/src/nwfilter/nwfilter_learnipaddr.c @@ -0,0 +1,622 @@ +/* + * nwfilter_learnipaddr.c: support for learning IP address used by a VM + * on an interface + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Stefan Berger + */ + +#include + +#ifdef HAVE_LIBPCAP +# include +#endif + +#include +#include + +#include +#include +#include +#include +#include + +#include "internal.h" + +#include "buf.h" +#include "memory.h" +#include "logging.h" +#include "datatypes.h" +#include "virterror_internal.h" +#include "threads.h" +#include "conf/nwfilter_params.h" +#include "conf/domain_conf.h" +#include "nwfilter_gentech_driver.h" +#include "nwfilter_ebiptables_driver.h" +#include "nwfilter_learnipaddr.h" + +#define VIR_FROM_THIS VIR_FROM_NWFILTER + + +/* structure of an ARP request/reply message */ +struct f_arphdr { + struct arphdr arphdr; + uint8_t ar_sha[ETH_ALEN]; + uint32_t ar_sip; + uint8_t ar_tha[ETH_ALEN]; + uint32_t ar_tip; +} ATTRIBUTE_PACKED; + + +/* structure representing DHCP message */ +struct dhcp { + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + uint32_t xid; + uint16_t secs; + uint16_t flags; + uint32_t ciaddr; + uint32_t yiaddr; + uint32_t siaddr; + uint32_t giaddr; + uint8_t chaddr[16]; + /* omitted */ +} ATTRIBUTE_PACKED; + + +struct ether_vlan_header +{ + uint8_t dhost[ETH_ALEN]; + uint8_t shost[ETH_ALEN]; + uint16_t vlan_type; + uint16_t vlan_flags; + uint16_t ether_type; +} ATTRIBUTE_PACKED; + + +static virMutex pendingLearnReqLock; +static virHashTablePtr pendingLearnReq; + +static virMutex ipAddressMapLock; +static virNWFilterHashTablePtr ipAddressMap; + + +static void +virNWFilterIPAddrLearnReqFree(virNWFilterIPAddrLearnReqPtr req) { + if (!req) + return; + + VIR_FREE(req->filtername); + virNWFilterHashTableFree(req->filterparams); + + VIR_FREE(req); +} + + +#if HAVE_LIBPCAP + +static int +virNWFilterRegisterLearnReq(virNWFilterIPAddrLearnReqPtr req) { + int res = -1; + virMutexLock(&pendingLearnReqLock); + + if (!virHashLookup(pendingLearnReq, req->ifname)) + res = virHashAddEntry(pendingLearnReq, req->ifname, req); + + virMutexUnlock(&pendingLearnReqLock); + + return res; +} + +#endif + + +virNWFilterIPAddrLearnReqPtr +virNWFilterLookupLearnReq(const char *ifname) { + void *res; + + virMutexLock(&pendingLearnReqLock); + + res = virHashLookup(pendingLearnReq, ifname); + + virMutexUnlock(&pendingLearnReqLock); + + return res; +} + + +static void +freeLearnReqEntry(void *payload, const char *name ATTRIBUTE_UNUSED) { + virNWFilterIPAddrLearnReqFree(payload); +} + + +#ifdef HAVE_LIBPCAP + +static virNWFilterIPAddrLearnReqPtr +virNWFilterDeregisterLearnReq(const char *ifname) { + virNWFilterIPAddrLearnReqPtr res; + + virMutexLock(&pendingLearnReqLock); + + res = virHashLookup(pendingLearnReq, ifname); + + if (res) + virHashRemoveEntry(pendingLearnReq, ifname, NULL); + + virMutexUnlock(&pendingLearnReqLock); + + return res; +} + + + +static int +virNWFilterAddIpAddrForIfname(const char *ifname, char *addr) { + int ret; + + virMutexLock(&ipAddressMapLock); + + ret = virNWFilterHashTablePut(ipAddressMap, ifname, addr, 1); + + virMutexUnlock(&ipAddressMapLock); + + return ret; +} +#endif + + +void +virNWFilterDelIpAddrForIfname(const char *ifname) { + + virMutexLock(&ipAddressMapLock); + + if (virHashLookup(ipAddressMap->hashTable, ifname)) + virNWFilterHashTableRemoveEntry(ipAddressMap, ifname); + + virMutexUnlock(&ipAddressMapLock); +} + + +const char * +virNWFilterGetIpAddrForIfname(const char *ifname) { + const char *res; + + virMutexLock(&ipAddressMapLock); + + res = virHashLookup(ipAddressMap->hashTable, ifname); + + virMutexUnlock(&ipAddressMapLock); + + return res; +} + + +#ifdef HAVE_LIBPCAP + +/** + * learnIPAddressThread + * arg: pointer to virNWFilterIPAddrLearnReq structure + * + * Learn the IP address being used on an interface. Use ARP Request and + * Reply messages, DHCP offers and the first IP packet being sent from + * the VM to detect the IP address it is using. Detects only one IP address + * per interface (IP aliasing not supported). The method on how the + * IP address is detected can be chosen through flags. DETECT_DHCP will + * require that the IP address is detected from a DHCP OFFER, DETECT_STATIC + * will require that the IP address was taken from an ARP packet or an IPv4 + * packet. Both flags can be set at the same time. + */ +static void * +learnIPAddressThread(void *arg) +{ + char errbuf[PCAP_ERRBUF_SIZE] = {0}; + pcap_t *handle; + struct bpf_program fp; + struct pcap_pkthdr header; + const u_char *packet; + struct ether_header *ether_hdr; + struct ether_vlan_header *vlan_hdr; + virNWFilterIPAddrLearnReqPtr req = arg; + uint32_t vmaddr = 0; + unsigned int ethHdrSize; + char *listen_if = (strlen(req->linkdev) != 0) ? req->linkdev + : req->ifname; + int to_ms = (strlen(req->linkdev) != 0) ? 1000 + : 0; + char macaddr[VIR_MAC_STRING_BUFLEN]; + virBuffer buf = VIR_BUFFER_INITIALIZER; + char *filter= NULL; + uint16_t etherType; + enum howDetect howDetected = 0; + + req->status = 0; + + handle = pcap_open_live(listen_if, BUFSIZ, 0, to_ms, errbuf); + + if (handle == NULL) { + VIR_DEBUG("Couldn't open device %s: %s\n", listen_if, errbuf); + req->status = ENODEV; + goto done; + } + + virFormatMacAddr(req->macaddr, macaddr); + + switch (req->howDetect) { + case DETECT_DHCP: + virBufferVSprintf(&buf, " ether dst %s" + " and src port 67 and dst port 68", + macaddr); + break; + default: + virBufferVSprintf(&buf, "ether host %s", macaddr); + } + + if (virBufferError(&buf)) { + req->status = ENOMEM; + goto done; + } + + filter = virBufferContentAndReset(&buf); + + if (pcap_compile(handle, &fp, filter, 1, 0) != 0 || + pcap_setfilter(handle, &fp) != 0) { + VIR_DEBUG("Couldn't compile or set filter '%s'.\n", filter); + req->status = EINVAL; + goto done; + } + + while (req->status == 0 && vmaddr == 0) { + packet = pcap_next(handle, &header); + + if (!packet) { + if (to_ms == 0) { + /* assuming IF disappeared */ + req->status = ENODEV; + break; + } + /* listening on linkdev, check whether VM's dev is still there */ + if (checkIf(req->ifname, req->macaddr)) { + req->status = ENODEV; + break; + } + continue; + } + + if (header.len >= sizeof(struct ether_header)) { + ether_hdr = (struct ether_header*)packet; + + switch (ntohs(ether_hdr->ether_type)) { + + case ETHERTYPE_IP: + ethHdrSize = sizeof(struct ether_header); + etherType = ntohs(ether_hdr->ether_type); + break; + + case ETHERTYPE_VLAN: + ethHdrSize = sizeof(struct ether_vlan_header); + vlan_hdr = (struct ether_vlan_header *)packet; + if (ntohs(vlan_hdr->ether_type) != ETHERTYPE_IP || + header.len < ethHdrSize) + continue; + etherType = ntohs(vlan_hdr->ether_type); + break; + + default: + continue; + } + + if (memcmp(ether_hdr->ether_shost, + req->macaddr, + VIR_MAC_BUFLEN) == 0) { + // packets from the VM + + if (etherType == ETHERTYPE_IP && + (header.len >= ethHdrSize + + sizeof(struct iphdr))) { + struct iphdr *iphdr = (struct iphdr*)(packet + + ethHdrSize); + vmaddr = iphdr->saddr; + // skip eth. bcast and mcast addresses, + // and zero address in DHCP Requests + if ((ntohl(vmaddr) & 0xc0000000) || vmaddr == 0) { + vmaddr = 0; + continue; + } + + howDetected = DETECT_STATIC; + } else if (etherType == ETHERTYPE_ARP && + (header.len >= ethHdrSize + + sizeof(struct f_arphdr))) { + struct f_arphdr *arphdr = (struct f_arphdr*)(packet + + ethHdrSize); + switch (ntohs(arphdr->arphdr.ar_op)) { + case ARPOP_REPLY: + vmaddr = arphdr->ar_sip; + howDetected = DETECT_STATIC; + break; + case ARPOP_REQUEST: + vmaddr = arphdr->ar_tip; + howDetected = DETECT_STATIC; + break; + } + } + } else if (memcmp(ether_hdr->ether_dhost, + req->macaddr, + VIR_MAC_BUFLEN) == 0) { + // packets to the VM + if (etherType == ETHERTYPE_IP && + (header.len >= ethHdrSize + + sizeof(struct iphdr))) { + struct iphdr *iphdr = (struct iphdr*)(packet + + ethHdrSize); + if ((iphdr->protocol == IPPROTO_UDP) && + (header.len >= ethHdrSize + + iphdr->ihl * 4 + + sizeof(struct udphdr))) { + struct udphdr *udphdr= (struct udphdr *) + ((char *)iphdr + iphdr->ihl * 4); + if (ntohs(udphdr->source) == 67 && + ntohs(udphdr->dest) == 68 && + header.len >= ethHdrSize + + iphdr->ihl * 4 + + sizeof(struct udphdr) + + sizeof(struct dhcp)) { + struct dhcp *dhcp = (struct dhcp *) + ((char *)udphdr + sizeof(udphdr)); + if (dhcp->op == 2 /* DHCP OFFER */ && + !memcmp(&dhcp->chaddr[0], + req->macaddr, + 6)) { + vmaddr = dhcp->yiaddr; + howDetected = DETECT_DHCP; + } + } + } + } + } + } + if (vmaddr && (req->howDetect & howDetected) == 0) { + vmaddr = 0; + howDetected = 0; + } + } /* while */ + + done: + VIR_FREE(filter); + + if (handle) + pcap_close(handle); + + ebtablesRemoveBasicRules(req->ifname); + + if (req->status == 0) { + int ret; + char inetaddr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &vmaddr, inetaddr, sizeof(inetaddr)); + + virNWFilterAddIpAddrForIfname(req->ifname, strdup(inetaddr)); + + ret = virNWFilterInstantiateFilterLate(NULL, + req->ifname, + req->linkdev, + req->nettype, + req->macaddr, + req->filtername, + req->filterparams, + req->driver); + VIR_DEBUG("Result from applying firewall rules on " + "%s with IP addr %s : %d\n", req->ifname, inetaddr, ret); + } + + memset(&req->thread, 0x0, sizeof(req->thread)); + + VIR_DEBUG("pcap thread terminating for interface %s\n",req->ifname); + + virNWFilterDeregisterLearnReq(req->ifname); + + virNWFilterIPAddrLearnReqFree(req); + + return 0; +} + + +/** + * virNWFilterLearnIPAddress + * @conn: pointer to virConnect object + * @ifname: the name of the interface + * @linkdev : the name of the link device; currently only used in case of a + * macvtap device + * @nettype : the type of interface + * @macaddr : the MAC address of the interface + * @filtername : the name of the top-level filter to apply to the interface + * once its IP address has been detected + * @driver : the network filter driver + * @howDetect : the method on how the thread is supposed to detect the + * IP address; must choose any of the available flags + * + * Instruct to learn the IP address being used on a given interface (ifname). + * Unless there already is a thread attempting to learn the IP address + * being used on the interface, a thread is started that will listen on + * the traffic being sent on the interface (or link device) with the + * MAC address that is provided. Will then launch the application of the + * firewall rules on the interface. + */ +int +virNWFilterLearnIPAddress(const char *ifname, + const char *linkdev, + enum virDomainNetType nettype, + const unsigned char *macaddr, + const char *filtername, + virNWFilterHashTablePtr filterparams, + virNWFilterDriverStatePtr driver, + enum howDetect howDetect) { + int rc; + virNWFilterIPAddrLearnReqPtr req = NULL; + virNWFilterHashTablePtr ht = NULL; + + if (howDetect == 0) + return 1; + + if (VIR_ALLOC(req) < 0) { + virReportOOMError(); + goto err_no_req; + } + + ht = virNWFilterHashTableCreate(0); + if (ht == NULL) { + virReportOOMError(); + goto err_no_ht; + } + + if (virNWFilterHashTablePutAll(filterparams, ht)) + goto err_free_ht; + + req->filtername = strdup(filtername); + if (req->filtername == NULL) { + virReportOOMError(); + goto err_free_ht; + } + + if (virStrcpyStatic(req->ifname, ifname) == NULL) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("Destination buffer for ifname ('%s') " + "not large enough"), ifname); + goto err_free_ht; + } + + if (linkdev) { + if (virStrcpyStatic(req->linkdev, linkdev) == NULL) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("Destination buffer for linkdev ('%s') " + "not large enough"), linkdev); + goto err_free_ht; + } + } + req->nettype = nettype; + memcpy(req->macaddr, macaddr, sizeof(req->macaddr)); + req->driver = driver; + req->filterparams = ht; + ht = NULL; + req->howDetect = howDetect; + + rc = virNWFilterRegisterLearnReq(req); + + if (rc) + goto err_free_ht; + + switch (howDetect) { + case DETECT_DHCP: + if (ebtablesApplyDHCPOnlyRules(ifname, + macaddr, + NULL)) + goto err_free_ht; + break; + default: + if (ebtablesApplyBasicRules(ifname, + macaddr)) + goto err_free_ht; + } + + + if (pthread_create(&req->thread, + NULL, + learnIPAddressThread, + req) != 0) + goto err_remove_rules; + + return 0; + +err_remove_rules: + ebtablesRemoveBasicRules(ifname); +err_free_ht: + virNWFilterHashTableFree(ht); +err_no_ht: + virNWFilterIPAddrLearnReqFree(req); +err_no_req: + return 1; +} + +#else + +int +virNWFilterLearnIPAddress(const char *ifname ATTRIBUTE_UNUSED, + const char *linkdev ATTRIBUTE_UNUSED, + enum virDomainNetType nettype ATTRIBUTE_UNUSED, + const unsigned char *macaddr ATTRIBUTE_UNUSED, + const char *filtername ATTRIBUTE_UNUSED, + virNWFilterHashTablePtr filterparams ATTRIBUTE_UNUSED, + virNWFilterDriverStatePtr driver ATTRIBUTE_UNUSED, + enum howDetect howDetect ATTRIBUTE_UNUSED) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("IP parameter must be given since libvirt " + "was not compiled with IP address learning " + "support")); + return 1; +} +#endif /* HAVE_LIBPCAP */ + + +/** + * virNWFilterLearnInit + * Initialization of this layer + */ +int +virNWFilterLearnInit(void) { + pendingLearnReq = virHashCreate(0); + if (!pendingLearnReq) { + virReportOOMError(); + return 1; + } + + if (virMutexInit(&pendingLearnReqLock)) { + virNWFilterLearnShutdown(); + return 1; + } + + ipAddressMap = virNWFilterHashTableCreate(0); + if (!ipAddressMap) { + virReportOOMError(); + virNWFilterLearnShutdown(); + return 1; + } + + if (virMutexInit(&ipAddressMapLock)) { + virNWFilterLearnShutdown(); + return 1; + } + + return 0; +} + + +/** + * virNWFilterLearnShutdown + * Shutdown of this layer + */ +void +virNWFilterLearnShutdown(void) { + virHashFree(pendingLearnReq, freeLearnReqEntry); + pendingLearnReq = NULL; + + virNWFilterHashTableFree(ipAddressMap); + ipAddressMap = NULL; +} diff --git a/src/nwfilter/nwfilter_learnipaddr.h b/src/nwfilter/nwfilter_learnipaddr.h new file mode 100644 index 0000000000..ffdd342a95 --- /dev/null +++ b/src/nwfilter/nwfilter_learnipaddr.h @@ -0,0 +1,67 @@ +/* + * nwfilter_learnipaddr.h: support for learning IP address used by a VM + * on an interface + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Stefan Berger + */ + +#ifndef __NWFILTER_LEARNIPADDR_H +# define __NWFILTER_LEARNIPADDR_H + +enum howDetect { + DETECT_DHCP = 1, + DETECT_STATIC = 2, +}; + +typedef struct _virNWFilterIPAddrLearnReq virNWFilterIPAddrLearnReq; +typedef virNWFilterIPAddrLearnReq *virNWFilterIPAddrLearnReqPtr; +struct _virNWFilterIPAddrLearnReq { + char ifname[IF_NAMESIZE]; + char linkdev[IF_NAMESIZE]; + enum virDomainNetType nettype; + unsigned char macaddr[VIR_MAC_BUFLEN]; + char *filtername; + virNWFilterHashTablePtr filterparams; + virNWFilterDriverStatePtr driver; + enum howDetect howDetect; + + int status; + pthread_t thread; +}; + +int virNWFilterLearnIPAddress(const char *ifname, + const char *linkdev, + enum virDomainNetType nettype, + const unsigned char *macaddr, + const char *filtername, + virNWFilterHashTablePtr filterparams, + virNWFilterDriverStatePtr driver, + enum howDetect howDetect); + +virNWFilterIPAddrLearnReqPtr virNWFilterLookupLearnReq(const char *ifname); + + +void virNWFilterDelIpAddrForIfname(const char *ifname); +const char *virNWFilterGetIpAddrForIfname(const char *ifname); + +int virNWFilterLearnInit(void); +void virNWFilterLearnShutdown(void); + +#endif /* __NWFILTER_LEARNIPADDR_H */ diff --git a/tests/nwfilterxml2xmltest b/tests/nwfilterxml2xmltest new file mode 100755 index 0000000000..7382a9fb54 Binary files /dev/null and b/tests/nwfilterxml2xmltest differ