[libvirt] [PATCH 0/2] Extend NIC_RX_FILTER_CHANGED event handler to process multicast list

From: Tony Krowiak <akrowiak@linux.vnet.ibm.com> This patch set extends the handler for qemu's NIC_RX_FILTER_CHANGED event. It adds code to compare the old and new multicast MAC address lists and programs the macvtap filters to match the guest. Tony Krowiak (2): util: Functions to update host network device's multicast filter qemu: change macvtap multicast list in response to NIC_RX_FILTER_CHANGED

From: Tony Krowiak <akrowiak@linux.vnet.ibm.com> This patch provides the utility functions to needed to synchronize the changes made to a guest domain network device's multicast filter with the corresponding macvtap device's filter on the host: * Get/add/remove multicast MAC addresses * Get the macvtap device's RX filter list Signed-off-by: Tony Krowiak <akrowiak@linux.vnet.ibm.com> --- src/libvirt_private.syms | 4 + src/util/virmacaddr.c | 37 +++++ src/util/virmacaddr.h | 4 + src/util/virnetdev.c | 360 ++++++++++++++++++++++++++++++++++++++++++++++ src/util/virnetdev.h | 11 ++ 5 files changed, 416 insertions(+), 0 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index d6265ac..6d06a2c 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1590,13 +1590,16 @@ virMacAddrIsBroadcastRaw; virMacAddrIsMulticast; virMacAddrIsUnicast; virMacAddrParse; +virMacAddrParseHex; virMacAddrSet; virMacAddrSetRaw; # util/virnetdev.h +virNetDevAddMulti; virNetDevAddRoute; virNetDevClearIPv4Address; +virNetDevDelMulti; virNetDevExists; virNetDevGetIndex; virNetDevGetIPv4Address; @@ -1604,6 +1607,7 @@ virNetDevGetLinkInfo; virNetDevGetMAC; virNetDevGetMTU; virNetDevGetPhysicalFunction; +virNetDevGetRxFilter; virNetDevGetVirtualFunctionIndex; virNetDevGetVirtualFunctionInfo; virNetDevGetVirtualFunctions; diff --git a/src/util/virmacaddr.c b/src/util/virmacaddr.c index ebd1182..ae5e5d2 100644 --- a/src/util/virmacaddr.c +++ b/src/util/virmacaddr.c @@ -198,6 +198,43 @@ virMacAddrFormat(const virMacAddr *addr, return str; } +/** + * virMacAddrParseHex: + * @str: string hexadecimal representation of MAC address, e.g., "F801EFCE3aCB" + * @addr: 6-byte MAC address + * + * Parse the hexadecimal representation of a MAC address + * + * Return 0 upon success, or -1 in case of error. + */ +int +virMacAddrParseHex(const char* str, virMacAddrPtr addr) +{ + if (strlen(str) != VIR_MAC_HEXLEN) + return -1; + + size_t iaddr; + size_t istr; + + + for (istr = 0, iaddr = 0; iaddr < VIR_MAC_BUFLEN; istr += 2, iaddr++) { + unsigned int hex; + + if (sscanf(&str[istr], "%02x", &hex) != 1) + break; + + if (hex > UCHAR_MAX) + break; + + addr->addr[iaddr] = hex; + } + + if (istr == VIR_MAC_HEXLEN) + return 0; + + return -1; +} + void virMacAddrGenerate(const unsigned char prefix[VIR_MAC_PREFIX_BUFLEN], virMacAddrPtr addr) { diff --git a/src/util/virmacaddr.h b/src/util/virmacaddr.h index 49efc36..72a285a 100644 --- a/src/util/virmacaddr.h +++ b/src/util/virmacaddr.h @@ -27,6 +27,7 @@ # include "internal.h" # define VIR_MAC_BUFLEN 6 +#define VIR_MAC_HEXLEN (VIR_MAC_BUFLEN * 2) # define VIR_MAC_PREFIX_BUFLEN 3 # define VIR_MAC_STRING_BUFLEN (VIR_MAC_BUFLEN * 3) @@ -50,6 +51,9 @@ void virMacAddrGenerate(const unsigned char prefix[VIR_MAC_PREFIX_BUFLEN], virMacAddrPtr addr); int virMacAddrParse(const char* str, virMacAddrPtr addr) ATTRIBUTE_RETURN_CHECK; +int virMacAddrParseHex(const char* str, + virMacAddrPtr addr) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; bool virMacAddrIsUnicast(const virMacAddr *addr); bool virMacAddrIsMulticast(const virMacAddr *addr); bool virMacAddrIsBroadcastRaw(const unsigned char s[VIR_MAC_BUFLEN]); diff --git a/src/util/virnetdev.c b/src/util/virnetdev.c index db5623a..5e53f5f 100644 --- a/src/util/virnetdev.c +++ b/src/util/virnetdev.c @@ -56,6 +56,33 @@ VIR_LOG_INIT("util.netdev"); +# define VIR_MCAST_NAME_LEN (IFNAMSIZ + 1) +# define VIR_MCAST_INDEX_TOKEN_IDX 0 +# define VIR_MCAST_NAME_TOKEN_IDX 1 +# define VIR_MCAST_USERS_TOKEN_IDX 2 +# define VIR_MCAST_GLOBAL_TOKEN_IDX 3 +# define VIR_MCAST_ADDR_TOKEN_IDX 4 +# define VIR_MCAST_NUM_TOKENS 5 +# define VIR_MCAST_TOKEN_DELIMS " \n" +# define VIR_MCAST_ADDR_LEN (VIR_MAC_HEXLEN + 1) + +typedef struct _virNetDevMcastEntry virNetDevMcastEntry; +typedef virNetDevMcastEntry *virNetDevMcastEntryPtr; +struct _virNetDevMcastEntry { + int index; + char name[VIR_MCAST_NAME_LEN]; + int users; + bool global; + virMacAddr macaddr; +}; + +typedef struct _virNetDevMcast virNetDevMcast; +typedef virNetDevMcast *virNetDevMcastPtr; +struct _virNetDevMcast { + size_t nentries; + virNetDevMcastEntryPtr *entries; +}; + #if defined(HAVE_STRUCT_IFREQ) static int virNetDevSetupControlFull(const char *ifname, struct ifreq *ifr, @@ -1934,6 +1961,266 @@ virNetDevGetLinkInfo(const char *ifname, #endif /* defined(__linux__) */ +#if defined(SIOCADDMULTI) && defined(HAVE_STRUCT_IFREQ) +/** + * virNetDevAddMulti: + * @ifname: interface name to which to add multicast MAC address + * @macaddr: MAC address + * + * This function adds the @macaddr to the multicast list for a given interface + * @ifname. + * + * Returns 0 in case of success or -1 on failure + */ +int virNetDevAddMulti(const char *ifname, + virMacAddrPtr macaddr) +{ + int fd = -1; + int ret = -1; + struct ifreq ifr; + + if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0) + return -1; + + ifr.ifr_hwaddr.sa_family = AF_UNSPEC; + virMacAddrGetRaw(macaddr, (unsigned char *)ifr.ifr_hwaddr.sa_data); + + if (ioctl(fd, SIOCADDMULTI, &ifr) < 0) { + char macstr[VIR_MAC_STRING_BUFLEN]; + virReportSystemError(errno, + _("Cannot add multicast MAC %s on '%s' interface"), + virMacAddrFormat(macaddr, macstr), ifname); + goto cleanup; + } + + ret = 0; + + cleanup: + VIR_FORCE_CLOSE(fd); + return ret; +} +#else +int virNetDevAddMulti(const char *ifname, + virMacAddrPtr macaddr ATTRIBUTE_UNUSED) +{ + char macstr[VIR_MAC_STRING_BUFLEN]; + virReportSystemError(errno, + _("Cannot add multicast MAC %s on '%s' interface"), + virMacAddrFormat(macaddr, macstr), ifname); + return -1; +} +#endif + +#if defined(SIOCDELMULTI) && defined(HAVE_STRUCT_IFREQ) +/** + * virNetDevDelMulti: + * @ifname: interface name from which to delete the multicast MAC address + * @macaddr: MAC address + * + * This function deletes the @macaddr from the multicast list for a given + * interface @ifname. + * + * Returns 0 in case of success or -1 on failure + */ +int virNetDevDelMulti(const char *ifname, + virMacAddrPtr macaddr) +{ + int fd = -1; + int ret = -1; + struct ifreq ifr; + + if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0) + return -1; + + ifr.ifr_hwaddr.sa_family = AF_UNSPEC; + virMacAddrGetRaw(macaddr, (unsigned char *)ifr.ifr_hwaddr.sa_data); + + if (ioctl(fd, SIOCDELMULTI, &ifr) < 0) { + char macstr[VIR_MAC_STRING_BUFLEN]; + virReportSystemError(errno, + _("Cannot add multicast MAC %s on '%s' interface"), + virMacAddrFormat(macaddr, macstr), ifname); + goto cleanup; + } + + ret = 0; + + cleanup: + VIR_FORCE_CLOSE(fd); + return ret; +} +#else +int virNetDevDelMulti(const char *ifname, + virMacAddrPtr macaddr ATTRIBUTE_UNUSED) +{ + char macstr[VIR_MAC_STRING_BUFLEN]; + virReportSystemError(errno, + _("Cannot delete multicast MAC %s on '%s' interface"), + virMacAddrFormat(macaddr, macstr), ifname); + return -1; +} +#endif + +static int virNetDevParseMcast(char *buf, virNetDevMcastEntryPtr mcast) +{ + int ifindex; + int num; + char *next; + char *token; + char *saveptr; + char *endptr; + + for (ifindex = 0, next = buf; ifindex < VIR_MCAST_NUM_TOKENS; ifindex++, + next = NULL) { + token = strtok_r(next, VIR_MCAST_TOKEN_DELIMS, &saveptr); + + if (token == NULL) { + virReportSystemError(EINVAL, + _("failed to parse multicast address from '%s'"), + buf); + return -1; + } + + switch (ifindex) { + case VIR_MCAST_INDEX_TOKEN_IDX: + if (virStrToLong_i(token, &endptr, 10, &num) < 0) { + virReportSystemError(EINVAL, + _("Failed to parse index from '%s'"), + buf); + return -1; + + } + + mcast->index = num; + + break; + case VIR_MCAST_NAME_TOKEN_IDX: + if (virStrncpy(mcast->name, token, strlen(token), + VIR_MCAST_NAME_LEN) == NULL) { + virReportSystemError(EINVAL, + _("Failed to parse NIC name from '%s'"), + buf); + return -1; + } + + break; + case VIR_MCAST_USERS_TOKEN_IDX: + if (virStrToLong_i(token, &endptr, 10, &num) < 0) { + virReportSystemError(EINVAL, + _("Failed to parse users from '%s'"), + buf); + return -1; + + } + + mcast->users = num; + + break; + case VIR_MCAST_GLOBAL_TOKEN_IDX: + if (virStrToLong_i(token, &endptr, 10, &num) < 0) { + virReportSystemError(EINVAL, + _("Failed to parse users from '%s'"), + buf); + return -1; + + } + + mcast->global = num; + + break; + case VIR_MCAST_ADDR_TOKEN_IDX: + if (virMacAddrParseHex((const char*)token, + &mcast->macaddr) < 0) { + virReportSystemError(EINVAL, + _("Failed to parse MAC address from '%s'"), + buf); + } + + break; + default: + break; + } + } + + return 0; +} + + +static int virNetDevGetMcast(const char *ifname, + virNetDevMcastPtr mcast) +{ + FILE *file; + const char *path = "/proc/net/dev_mcast"; + char buf[256]; + int ret = -1; + virNetDevMcastEntry entry; + virNetDevMcastEntryPtr *entries = NULL; + size_t nentries = 0; + size_t entries_sz = 0; + size_t i; + mcast->entries = NULL; + mcast->nentries = 0; + + file = fopen(path, "r"); + + if (!file) { + virReportSystemError(errno, + _("cannot open multicast address file %s"), path); + return -1; + } + + while (fgets(buf, sizeof(buf), file)) { + if (virNetDevParseMcast(buf, &entry) < 0) { + goto error; + } + + if (entry.global && STREQ(ifname, entry.name)) { + if (VIR_RESIZE_N(entries, entries_sz, + nentries, 1) < 0) { + virReportSystemError(ENOMEM, + _("Failed to resize multicast MAC address array: " + "ptr=%p, alloc=%zu, count=%zu, add=1"), + entries, entries_sz, nentries); + goto error; + } + + if (VIR_ALLOC(entries[nentries]) < 0) { + char addr[VIR_MAC_STRING_BUFLEN]; + virReportSystemError(ENOMEM, + _("Failed to allocate storage for MAC address %s"), + virMacAddrFormat(&mcast->entries[nentries]->macaddr, + addr)); + goto error; + } + + memcpy(entries[nentries++], &entry, + sizeof(virNetDevMcastEntry)); + } + + memset(buf, 0, sizeof(buf)); + memset(&entry, 0, sizeof(virNetDevMcastEntry)); + } + + + mcast->nentries = nentries; + mcast->entries = entries; + ret = 0; + + error: + VIR_FORCE_FCLOSE(file); + + if ((ret < 0) && (nentries > 0)) { + for (i = 0; i < nentries; i++) { + VIR_FREE(entries[i]); + } + + VIR_FREE(entries); + } + + return ret; +} + + VIR_ENUM_IMPL(virNetDevRxFilterMode, VIR_NETDEV_RX_FILTER_MODE_LAST, "none", @@ -1941,6 +2228,37 @@ VIR_ENUM_IMPL(virNetDevRxFilterMode, "all"); +static int virNetDevGetMulticastTable(const char *ifname, + virNetDevRxFilterPtr filter) +{ + int i; + int ret = -1; + virNetDevMcast mcast; + filter->multicast.nTable = 0; + filter->multicast.table = NULL; + + if (virNetDevGetMcast(ifname, &mcast) < 0) + goto error; + + if (mcast.nentries > 0) { + if (VIR_ALLOC_N(filter->multicast.table, mcast.nentries)) + goto error; + + for (i = 0; i < mcast.nentries; i++) { + virMacAddrSet(&filter->multicast.table[i], + &mcast.entries[i]->macaddr); + } + + filter->multicast.nTable = mcast.nentries; + } + + ret = 0; + + error: + return ret; +} + + virNetDevRxFilterPtr virNetDevRxFilterNew(void) { @@ -1963,3 +2281,45 @@ virNetDevRxFilterFree(virNetDevRxFilterPtr filter) VIR_FREE(filter); } } + + +/** + * virNetDevGetRxFilter: + * This function supplies the RX filter list for a given device interface + * + * @ifname: Name of the interface + * @filter: The RX filter list + * + * Returns 0 or -1 on failure. + */ +int virNetDevGetRxFilter(const char *ifname, + virNetDevRxFilterPtr *filter) +{ + int ret = -1; + virNetDevRxFilterPtr fil = virNetDevRxFilterNew(); + + if (!fil) { + virReportSystemError(ENOMEM, + _("Failed to allocate filter for %s interface"), + ifname); + + } + + if (virNetDevGetMAC(ifname, &fil->mac)) + goto cleanup; + + if (virNetDevGetMulticastTable(ifname, fil)) + goto cleanup; + + ret = 0; + + cleanup: + if (ret < 0) { + virNetDevRxFilterFree(fil); + fil = NULL; + } + + *filter = fil; + + return ret; +} diff --git a/src/util/virnetdev.h b/src/util/virnetdev.h index 2a6e67d..1d274b3 100644 --- a/src/util/virnetdev.h +++ b/src/util/virnetdev.h @@ -30,6 +30,7 @@ # include "virmacaddr.h" # include "virpci.h" # include "device_conf.h" +# include "virutil.h" # ifdef HAVE_STRUCT_IFREQ typedef struct ifreq virIfreq; @@ -189,5 +190,15 @@ int virNetDevGetLinkInfo(const char *ifname, virNetDevRxFilterPtr virNetDevRxFilterNew(void) ATTRIBUTE_RETURN_CHECK; void virNetDevRxFilterFree(virNetDevRxFilterPtr filter); +int virNetDevGetRxFilter(const char *ifname, + virNetDevRxFilterPtr *filter) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; + +int virNetDevAddMulti(const char *ifname, + virMacAddrPtr macaddr) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; +int virNetDevDelMulti(const char *ifname, + virMacAddrPtr macaddr) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; #endif /* __VIR_NETDEV_H__ */ -- 1.7.1

On 10/06/2014 05:37 PM, akrowiak@linux.vnet.ibm.com wrote:
From: Tony Krowiak <akrowiak@linux.vnet.ibm.com>
This patch provides the utility functions to needed to synchronize the changes made to a guest domain network device's multicast filter with the corresponding macvtap device's filter on the host:
* Get/add/remove multicast MAC addresses * Get the macvtap device's RX filter list
Signed-off-by: Tony Krowiak <akrowiak@linux.vnet.ibm.com> --- src/libvirt_private.syms | 4 + src/util/virmacaddr.c | 37 +++++ src/util/virmacaddr.h | 4 + src/util/virnetdev.c | 360 ++++++++++++++++++++++++++++++++++++++++++++++ src/util/virnetdev.h | 11 ++ 5 files changed, 416 insertions(+), 0 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index d6265ac..6d06a2c 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1590,13 +1590,16 @@ virMacAddrIsBroadcastRaw; virMacAddrIsMulticast; virMacAddrIsUnicast; virMacAddrParse; +virMacAddrParseHex; virMacAddrSet; virMacAddrSetRaw;
# util/virnetdev.h +virNetDevAddMulti; virNetDevAddRoute; virNetDevClearIPv4Address; +virNetDevDelMulti; virNetDevExists; virNetDevGetIndex; virNetDevGetIPv4Address; @@ -1604,6 +1607,7 @@ virNetDevGetLinkInfo; virNetDevGetMAC; virNetDevGetMTU; virNetDevGetPhysicalFunction; +virNetDevGetRxFilter; virNetDevGetVirtualFunctionIndex; virNetDevGetVirtualFunctionInfo; virNetDevGetVirtualFunctions; diff --git a/src/util/virmacaddr.c b/src/util/virmacaddr.c index ebd1182..ae5e5d2 100644 --- a/src/util/virmacaddr.c +++ b/src/util/virmacaddr.c @@ -198,6 +198,43 @@ virMacAddrFormat(const virMacAddr *addr, return str; }
+/** + * virMacAddrParseHex: + * @str: string hexadecimal representation of MAC address, e.g., "F801EFCE3aCB" + * @addr: 6-byte MAC address + * + * Parse the hexadecimal representation of a MAC address + * + * Return 0 upon success, or -1 in case of error. + */ +int +virMacAddrParseHex(const char* str, virMacAddrPtr addr) +{ + if (strlen(str) != VIR_MAC_HEXLEN) + return -1; + + size_t iaddr; + size_t istr; + + + for (istr = 0, iaddr = 0; iaddr < VIR_MAC_BUFLEN; istr += 2, iaddr++) { + unsigned int hex; + + if (sscanf(&str[istr], "%02x", &hex) != 1) + break;
I'm not keen on sscanf(), but since we're already using it elsewhere, and the existing virMacAddrParse uses the normally-blacklisted strtoul() (and using virStrToLong_ui() on pieces of the string would require a terminating character after each piece), I don't have a ready answer for an alternative. Eric, do you have an opinion on this? Is sscanf okay when we want to parse: XXXXXXXXXXXX of hex digits into 6 binary bytes?
+ + if (hex > UCHAR_MAX) + break; + + addr->addr[iaddr] = hex; + } + + if (istr == VIR_MAC_HEXLEN) + return 0; + + return -1; +} + void virMacAddrGenerate(const unsigned char prefix[VIR_MAC_PREFIX_BUFLEN], virMacAddrPtr addr) { diff --git a/src/util/virmacaddr.h b/src/util/virmacaddr.h index 49efc36..72a285a 100644 --- a/src/util/virmacaddr.h +++ b/src/util/virmacaddr.h @@ -27,6 +27,7 @@ # include "internal.h"
# define VIR_MAC_BUFLEN 6 +#define VIR_MAC_HEXLEN (VIR_MAC_BUFLEN * 2) # define VIR_MAC_PREFIX_BUFLEN 3 # define VIR_MAC_STRING_BUFLEN (VIR_MAC_BUFLEN * 3)
@@ -50,6 +51,9 @@ void virMacAddrGenerate(const unsigned char prefix[VIR_MAC_PREFIX_BUFLEN], virMacAddrPtr addr); int virMacAddrParse(const char* str, virMacAddrPtr addr) ATTRIBUTE_RETURN_CHECK; +int virMacAddrParseHex(const char* str, + virMacAddrPtr addr) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; bool virMacAddrIsUnicast(const virMacAddr *addr); bool virMacAddrIsMulticast(const virMacAddr *addr); bool virMacAddrIsBroadcastRaw(const unsigned char s[VIR_MAC_BUFLEN]); diff --git a/src/util/virnetdev.c b/src/util/virnetdev.c index db5623a..5e53f5f 100644 --- a/src/util/virnetdev.c +++ b/src/util/virnetdev.c @@ -56,6 +56,33 @@
VIR_LOG_INIT("util.netdev");
+# define VIR_MCAST_NAME_LEN (IFNAMSIZ + 1) +# define VIR_MCAST_INDEX_TOKEN_IDX 0 +# define VIR_MCAST_NAME_TOKEN_IDX 1 +# define VIR_MCAST_USERS_TOKEN_IDX 2 +# define VIR_MCAST_GLOBAL_TOKEN_IDX 3 +# define VIR_MCAST_ADDR_TOKEN_IDX 4 +# define VIR_MCAST_NUM_TOKENS 5 +# define VIR_MCAST_TOKEN_DELIMS " \n" +# define VIR_MCAST_ADDR_LEN (VIR_MAC_HEXLEN + 1) + +typedef struct _virNetDevMcastEntry virNetDevMcastEntry; +typedef virNetDevMcastEntry *virNetDevMcastEntryPtr; +struct _virNetDevMcastEntry { + int index; + char name[VIR_MCAST_NAME_LEN]; + int users; + bool global; + virMacAddr macaddr; +}; + +typedef struct _virNetDevMcast virNetDevMcast; +typedef virNetDevMcast *virNetDevMcastPtr; +struct _virNetDevMcast { + size_t nentries; + virNetDevMcastEntryPtr *entries; +}; + #if defined(HAVE_STRUCT_IFREQ) static int virNetDevSetupControlFull(const char *ifname, struct ifreq *ifr, @@ -1934,6 +1961,266 @@ virNetDevGetLinkInfo(const char *ifname, #endif /* defined(__linux__) */
+#if defined(SIOCADDMULTI) && defined(HAVE_STRUCT_IFREQ) +/** + * virNetDevAddMulti: + * @ifname: interface name to which to add multicast MAC address + * @macaddr: MAC address + * + * This function adds the @macaddr to the multicast list for a given interface + * @ifname. + * + * Returns 0 in case of success or -1 on failure + */ +int virNetDevAddMulti(const char *ifname, + virMacAddrPtr macaddr) +{ + int fd = -1; + int ret = -1; + struct ifreq ifr; + + if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0) + return -1; + + ifr.ifr_hwaddr.sa_family = AF_UNSPEC; + virMacAddrGetRaw(macaddr, (unsigned char *)ifr.ifr_hwaddr.sa_data); + + if (ioctl(fd, SIOCADDMULTI, &ifr) < 0) { + char macstr[VIR_MAC_STRING_BUFLEN]; + virReportSystemError(errno, + _("Cannot add multicast MAC %s on '%s' interface"), + virMacAddrFormat(macaddr, macstr), ifname); + goto cleanup; + } + + ret = 0; + + cleanup: + VIR_FORCE_CLOSE(fd); + return ret; +} +#else +int virNetDevAddMulti(const char *ifname, + virMacAddrPtr macaddr ATTRIBUTE_UNUSED) +{ + char macstr[VIR_MAC_STRING_BUFLEN]; + virReportSystemError(errno,
For all of these empty functions used on platforms that don't have the necessary functionality, you need to replace "errno" with "ENOSYS" - since we haven't tried to call anything, errno will not have been set.
+ _("Cannot add multicast MAC %s on '%s' interface"), + virMacAddrFormat(macaddr, macstr), ifname); + return -1; +} +#endif + +#if defined(SIOCDELMULTI) && defined(HAVE_STRUCT_IFREQ) +/** + * virNetDevDelMulti: + * @ifname: interface name from which to delete the multicast MAC address + * @macaddr: MAC address + * + * This function deletes the @macaddr from the multicast list for a given + * interface @ifname. + * + * Returns 0 in case of success or -1 on failure + */ +int virNetDevDelMulti(const char *ifname, + virMacAddrPtr macaddr) +{ + int fd = -1; + int ret = -1; + struct ifreq ifr; + + if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0) + return -1; + + ifr.ifr_hwaddr.sa_family = AF_UNSPEC; + virMacAddrGetRaw(macaddr, (unsigned char *)ifr.ifr_hwaddr.sa_data); + + if (ioctl(fd, SIOCDELMULTI, &ifr) < 0) { + char macstr[VIR_MAC_STRING_BUFLEN]; + virReportSystemError(errno, + _("Cannot add multicast MAC %s on '%s' interface"), + virMacAddrFormat(macaddr, macstr), ifname); + goto cleanup; + } + + ret = 0; + + cleanup: + VIR_FORCE_CLOSE(fd); + return ret; +} +#else +int virNetDevDelMulti(const char *ifname, + virMacAddrPtr macaddr ATTRIBUTE_UNUSED) +{ + char macstr[VIR_MAC_STRING_BUFLEN]; + virReportSystemError(errno,
ENOSYS
+ _("Cannot delete multicast MAC %s on '%s' interface"), + virMacAddrFormat(macaddr, macstr), ifname); + return -1; +} +#endif + +static int virNetDevParseMcast(char *buf, virNetDevMcastEntryPtr mcast) +{ + int ifindex; + int num; + char *next; + char *token; + char *saveptr; + char *endptr; + + for (ifindex = 0, next = buf; ifindex < VIR_MCAST_NUM_TOKENS; ifindex++, + next = NULL) { + token = strtok_r(next, VIR_MCAST_TOKEN_DELIMS, &saveptr); + + if (token == NULL) { + virReportSystemError(EINVAL, + _("failed to parse multicast address from '%s'"), + buf); + return -1; + } + + switch (ifindex) { + case VIR_MCAST_INDEX_TOKEN_IDX: + if (virStrToLong_i(token, &endptr, 10, &num) < 0) { + virReportSystemError(EINVAL, + _("Failed to parse index from '%s'"),
"Failed to parse interface index from '%s'"
+ buf); + return -1; + + } + + mcast->index = num; + + break; + case VIR_MCAST_NAME_TOKEN_IDX: + if (virStrncpy(mcast->name, token, strlen(token), + VIR_MCAST_NAME_LEN) == NULL) { + virReportSystemError(EINVAL, + _("Failed to parse NIC name from '%s'"),
I prefer "network device" rather than "NIC".
+ buf); + return -1; + } + + break; + case VIR_MCAST_USERS_TOKEN_IDX: + if (virStrToLong_i(token, &endptr, 10, &num) < 0) { + virReportSystemError(EINVAL, + _("Failed to parse users from '%s'"), + buf); + return -1; + + } + + mcast->users = num; + + break; + case VIR_MCAST_GLOBAL_TOKEN_IDX: + if (virStrToLong_i(token, &endptr, 10, &num) < 0) { + virReportSystemError(EINVAL, + _("Failed to parse users from '%s'"), + buf); + return -1; + + } + + mcast->global = num; + + break; + case VIR_MCAST_ADDR_TOKEN_IDX: + if (virMacAddrParseHex((const char*)token, + &mcast->macaddr) < 0) { + virReportSystemError(EINVAL, + _("Failed to parse MAC address from '%s'"), + buf); + } + + break; + default: + break; + } + } + + return 0; +} + + +static int virNetDevGetMcast(const char *ifname, + virNetDevMcastPtr mcast) +{ + FILE *file; + const char *path = "/proc/net/dev_mcast"; + char buf[256]; + int ret = -1; + virNetDevMcastEntry entry; + virNetDevMcastEntryPtr *entries = NULL; + size_t nentries = 0; + size_t entries_sz = 0; + size_t i; + mcast->entries = NULL; + mcast->nentries = 0; + + file = fopen(path, "r"); + + if (!file) { + virReportSystemError(errno, + _("cannot open multicast address file %s"), path); + return -1; + } + + while (fgets(buf, sizeof(buf), file)) {
We prefer to not use fgets(), but instead use virFileReadAll() to read the entire file into a buffer, then parse it from there. There are several examples of this in the code, for example network/bridge_driver_linux.c:networkCheckRouteCollision().\
+ if (virNetDevParseMcast(buf, &entry) < 0) { + goto error; + } + + if (entry.global && STREQ(ifname, entry.name)) { + if (VIR_RESIZE_N(entries, entries_sz, + nentries, 1) < 0) { + virReportSystemError(ENOMEM, + _("Failed to resize multicast MAC address array: " + "ptr=%p, alloc=%zu, count=%zu, add=1"), + entries, entries_sz, nentries);
Any memory allocation error will already automatically get an error message logged, so this is redundant. Aside from that, you could instead use VIR_APPEND_ELEMENT() to replace the VIR_RESIZE_N(...., 1) ... memcpy(entries[nentries++], &entry) ... memset(&entry...) (it takes care of all three of those) . Basically, make "entry" a virNetDevMcastEntryPtr entry = NULL;, then each time through the loop allocate a new table entry in "entry" and fill it in, then call if (VIR_APPEND_ELEMENT(entries, nentries, entry) < 0) goto cleanup; (and by the way, because normal execution also goes past the "error" label, it should be changed to cleanup in order to avoid misunderstandings)
+ goto error; + } + + if (VIR_ALLOC(entries[nentries]) < 0) { + char addr[VIR_MAC_STRING_BUFLEN]; + virReportSystemError(ENOMEM, + _("Failed to allocate storage for MAC address %s"), + virMacAddrFormat(&mcast->entries[nentries]->macaddr, + addr));
Same here - no need to report the error, as a general error has already been reported, and we know that the source of the error has nothing to do with this specific MAC address, so no need to log an extra message.
+ goto error; + } + + memcpy(entries[nentries++], &entry, + sizeof(virNetDevMcastEntry)); + } + + memset(buf, 0, sizeof(buf)); + memset(&entry, 0, sizeof(virNetDevMcastEntry)); + } + + + mcast->nentries = nentries; + mcast->entries = entries; + ret = 0; + + error: + VIR_FORCE_FCLOSE(file); + + if ((ret < 0) && (nentries > 0)) {
If nentries == 0, the following code becomes a NOP anyway, so you can just follow the libvirt convention of unconditionally calling VIR_FREE() (as long as ret < 0)
+ for (i = 0; i < nentries; i++) { + VIR_FREE(entries[i]); + } + + VIR_FREE(entries);
The above should be turned into a helper function called something like virNetDevMcastEntryListFree(), because you're going to need it down below...
+ } + + return ret; +} + + VIR_ENUM_IMPL(virNetDevRxFilterMode, VIR_NETDEV_RX_FILTER_MODE_LAST, "none", @@ -1941,6 +2228,37 @@ VIR_ENUM_IMPL(virNetDevRxFilterMode, "all");
+static int virNetDevGetMulticastTable(const char *ifname, + virNetDevRxFilterPtr filter) +{ + int i; + int ret = -1; + virNetDevMcast mcast; + filter->multicast.nTable = 0; + filter->multicast.table = NULL; + + if (virNetDevGetMcast(ifname, &mcast) < 0) + goto error; + + if (mcast.nentries > 0) { + if (VIR_ALLOC_N(filter->multicast.table, mcast.nentries)) + goto error; + + for (i = 0; i < mcast.nentries; i++) { + virMacAddrSet(&filter->multicast.table[i], + &mcast.entries[i]->macaddr); + } + + filter->multicast.nTable = mcast.nentries; + } + + ret = 0; + + error:
Same here - also change this label to cleanup. Also, you returned without freeing the virNetDevMcastEntry list. You should call the new virNetDevMcastEntryListFree() here.
+ return ret; +} + + virNetDevRxFilterPtr virNetDevRxFilterNew(void) { @@ -1963,3 +2281,45 @@ virNetDevRxFilterFree(virNetDevRxFilterPtr filter) VIR_FREE(filter); } } + + +/** + * virNetDevGetRxFilter: + * This function supplies the RX filter list for a given device interface + * + * @ifname: Name of the interface + * @filter: The RX filter list + * + * Returns 0 or -1 on failure. + */ +int virNetDevGetRxFilter(const char *ifname, + virNetDevRxFilterPtr *filter) +{ + int ret = -1; + virNetDevRxFilterPtr fil = virNetDevRxFilterNew(); + + if (!fil) { + virReportSystemError(ENOMEM, + _("Failed to allocate filter for %s interface"), + ifname);
No need to log an extra error here - it's already done when the memory allocation fails.
+ + } + + if (virNetDevGetMAC(ifname, &fil->mac)) + goto cleanup;
Indentation problem ^^
+ + if (virNetDevGetMulticastTable(ifname, fil)) + goto cleanup; + + ret = 0; + + cleanup:
remove the blank line between ret = 0 and cleanup: to reduce the chance that someone will attempt to add new code in between them.
+ if (ret < 0) { + virNetDevRxFilterFree(fil); + fil = NULL; + } + + *filter = fil; + + return ret; +} diff --git a/src/util/virnetdev.h b/src/util/virnetdev.h index 2a6e67d..1d274b3 100644 --- a/src/util/virnetdev.h +++ b/src/util/virnetdev.h @@ -30,6 +30,7 @@ # include "virmacaddr.h" # include "virpci.h" # include "device_conf.h" +# include "virutil.h"
# ifdef HAVE_STRUCT_IFREQ typedef struct ifreq virIfreq; @@ -189,5 +190,15 @@ int virNetDevGetLinkInfo(const char *ifname, virNetDevRxFilterPtr virNetDevRxFilterNew(void) ATTRIBUTE_RETURN_CHECK; void virNetDevRxFilterFree(virNetDevRxFilterPtr filter); +int virNetDevGetRxFilter(const char *ifname, + virNetDevRxFilterPtr *filter) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; + +int virNetDevAddMulti(const char *ifname, + virMacAddrPtr macaddr) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; +int virNetDevDelMulti(const char *ifname, + virMacAddrPtr macaddr) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK;
#endif /* __VIR_NETDEV_H__ */
Otherwise it looks okay. As we've discussed, it would be nice to use netlink instead of /proc/net and ioctl(), but that can be added later.

On 10/08/2014 09:36 AM, Laine Stump wrote:
On 10/06/2014 05:37 PM, akrowiak@linux.vnet.ibm.com wrote:
From: Tony Krowiak <akrowiak@linux.vnet.ibm.com>
This patch provides the utility functions to needed to synchronize the changes made to a guest domain network device's multicast filter with the corresponding macvtap device's filter on the host:
* Get/add/remove multicast MAC addresses * Get the macvtap device's RX filter list
Signed-off-by: Tony Krowiak <akrowiak@linux.vnet.ibm.com> ---
+/** + * virMacAddrParseHex: + * @str: string hexadecimal representation of MAC address, e.g., "F801EFCE3aCB" + * @addr: 6-byte MAC address + * + * Parse the hexadecimal representation of a MAC address + * + * Return 0 upon success, or -1 in case of error. + */ +int +virMacAddrParseHex(const char* str, virMacAddrPtr addr)
Wrong spacing around '*'.
+{ + if (strlen(str) != VIR_MAC_HEXLEN) + return -1; + + size_t iaddr; + size_t istr; + + + for (istr = 0, iaddr = 0; iaddr < VIR_MAC_BUFLEN; istr += 2, iaddr++) { + unsigned int hex; + + if (sscanf(&str[istr], "%02x", &hex) != 1) + break;
I'm not keen on sscanf(), but since we're already using it elsewhere, and the existing virMacAddrParse uses the normally-blacklisted strtoul() (and using virStrToLong_ui() on pieces of the string would require a terminating character after each piece), I don't have a ready answer for an alternative.
Why not just open-code it? It's just two input bytes per iteration, and we already have a helper function that makes it a very short function. And strspn makes it easy to do input validation before starting the loop: int virMacAddrParseHex(const char *str, virMacAddrPtr addr) { size_t i; if (strspn(str, "0123456789abcdefABCDEF") != VIR_MAC_HEXLEN || str[VIR_MAC_HEXLEN]) return -1; for (i = 0; i < VIR_MAC_BUFLEN; i++) addr->addr[i] = (virHexToBin(str[2 * i]) << 4 | virHexToBin(str[2 * i + 1])); return 0; } -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 10/08/2014 12:59 PM, Eric Blake wrote:
On 10/08/2014 09:36 AM, Laine Stump wrote:
On 10/06/2014 05:37 PM, akrowiak@linux.vnet.ibm.com wrote:
From: Tony Krowiak <akrowiak@linux.vnet.ibm.com>
This patch provides the utility functions to needed to synchronize the changes made to a guest domain network device's multicast filter with the corresponding macvtap device's filter on the host:
* Get/add/remove multicast MAC addresses * Get the macvtap device's RX filter list
Signed-off-by: Tony Krowiak <akrowiak@linux.vnet.ibm.com> --- +/** + * virMacAddrParseHex: + * @str: string hexadecimal representation of MAC address, e.g., "F801EFCE3aCB" + * @addr: 6-byte MAC address + * + * Parse the hexadecimal representation of a MAC address + * + * Return 0 upon success, or -1 in case of error. + */ +int +virMacAddrParseHex(const char* str, virMacAddrPtr addr) Wrong spacing around '*'. I will fix that.
+{ + if (strlen(str) != VIR_MAC_HEXLEN) + return -1; + + size_t iaddr; + size_t istr; + + + for (istr = 0, iaddr = 0; iaddr < VIR_MAC_BUFLEN; istr += 2, iaddr++) { + unsigned int hex; + + if (sscanf(&str[istr], "%02x", &hex) != 1) + break; I'm not keen on sscanf(), but since we're already using it elsewhere, and the existing virMacAddrParse uses the normally-blacklisted strtoul() (and using virStrToLong_ui() on pieces of the string would require a terminating character after each piece), I don't have a ready answer for an alternative. Why not just open-code it? It's just two input bytes per iteration, and we already have a helper function that makes it a very short function. And strspn makes it easy to do input validation before starting the loop:
int virMacAddrParseHex(const char *str, virMacAddrPtr addr) { size_t i;
if (strspn(str, "0123456789abcdefABCDEF") != VIR_MAC_HEXLEN || str[VIR_MAC_HEXLEN]) return -1;
for (i = 0; i < VIR_MAC_BUFLEN; i++) addr->addr[i] = (virHexToBin(str[2 * i]) << 4 | virHexToBin(str[2 * i + 1])); return 0; } Looks good, I'll put that in.
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

On 10/08/2014 11:36 AM, Laine Stump wrote:
On 10/06/2014 05:37 PM, akrowiak@linux.vnet.ibm.com wrote:
From: Tony Krowiak <akrowiak@linux.vnet.ibm.com>
This patch provides the utility functions to needed to synchronize the changes made to a guest domain network device's multicast filter with the corresponding macvtap device's filter on the host:
* Get/add/remove multicast MAC addresses * Get the macvtap device's RX filter list
Signed-off-by: Tony Krowiak <akrowiak@linux.vnet.ibm.com> --- src/libvirt_private.syms | 4 + src/util/virmacaddr.c | 37 +++++ src/util/virmacaddr.h | 4 + src/util/virnetdev.c | 360 ++++++++++++++++++++++++++++++++++++++++++++++ src/util/virnetdev.h | 11 ++ 5 files changed, 416 insertions(+), 0 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index d6265ac..6d06a2c 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1590,13 +1590,16 @@ virMacAddrIsBroadcastRaw; virMacAddrIsMulticast; virMacAddrIsUnicast; virMacAddrParse; +virMacAddrParseHex; virMacAddrSet; virMacAddrSetRaw;
# util/virnetdev.h +virNetDevAddMulti; virNetDevAddRoute; virNetDevClearIPv4Address; +virNetDevDelMulti; virNetDevExists; virNetDevGetIndex; virNetDevGetIPv4Address; @@ -1604,6 +1607,7 @@ virNetDevGetLinkInfo; virNetDevGetMAC; virNetDevGetMTU; virNetDevGetPhysicalFunction; +virNetDevGetRxFilter; virNetDevGetVirtualFunctionIndex; virNetDevGetVirtualFunctionInfo; virNetDevGetVirtualFunctions; diff --git a/src/util/virmacaddr.c b/src/util/virmacaddr.c index ebd1182..ae5e5d2 100644 --- a/src/util/virmacaddr.c +++ b/src/util/virmacaddr.c @@ -198,6 +198,43 @@ virMacAddrFormat(const virMacAddr *addr, return str; }
+/** + * virMacAddrParseHex: + * @str: string hexadecimal representation of MAC address, e.g., "F801EFCE3aCB" + * @addr: 6-byte MAC address + * + * Parse the hexadecimal representation of a MAC address + * + * Return 0 upon success, or -1 in case of error. + */ +int +virMacAddrParseHex(const char* str, virMacAddrPtr addr) +{ + if (strlen(str) != VIR_MAC_HEXLEN) + return -1; + + size_t iaddr; + size_t istr; + + + for (istr = 0, iaddr = 0; iaddr < VIR_MAC_BUFLEN; istr += 2, iaddr++) { + unsigned int hex; + + if (sscanf(&str[istr], "%02x", &hex) != 1) + break; I'm not keen on sscanf(), but since we're already using it elsewhere, and the existing virMacAddrParse uses the normally-blacklisted strtoul() (and using virStrToLong_ui() on pieces of the string would require a terminating character after each piece), I don't have a ready answer for an alternative.
Eric, do you have an opinion on this? Is sscanf okay when we want to parse:
XXXXXXXXXXXX
of hex digits into 6 binary bytes? See my response to Eric's response.
+ + if (hex > UCHAR_MAX) + break; + + addr->addr[iaddr] = hex; + } + + if (istr == VIR_MAC_HEXLEN) + return 0; + + return -1; +} + void virMacAddrGenerate(const unsigned char prefix[VIR_MAC_PREFIX_BUFLEN], virMacAddrPtr addr) { diff --git a/src/util/virmacaddr.h b/src/util/virmacaddr.h index 49efc36..72a285a 100644 --- a/src/util/virmacaddr.h +++ b/src/util/virmacaddr.h @@ -27,6 +27,7 @@ # include "internal.h"
# define VIR_MAC_BUFLEN 6 +#define VIR_MAC_HEXLEN (VIR_MAC_BUFLEN * 2) # define VIR_MAC_PREFIX_BUFLEN 3 # define VIR_MAC_STRING_BUFLEN (VIR_MAC_BUFLEN * 3)
@@ -50,6 +51,9 @@ void virMacAddrGenerate(const unsigned char prefix[VIR_MAC_PREFIX_BUFLEN], virMacAddrPtr addr); int virMacAddrParse(const char* str, virMacAddrPtr addr) ATTRIBUTE_RETURN_CHECK; +int virMacAddrParseHex(const char* str, + virMacAddrPtr addr) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; bool virMacAddrIsUnicast(const virMacAddr *addr); bool virMacAddrIsMulticast(const virMacAddr *addr); bool virMacAddrIsBroadcastRaw(const unsigned char s[VIR_MAC_BUFLEN]); diff --git a/src/util/virnetdev.c b/src/util/virnetdev.c index db5623a..5e53f5f 100644 --- a/src/util/virnetdev.c +++ b/src/util/virnetdev.c @@ -56,6 +56,33 @@
VIR_LOG_INIT("util.netdev");
+# define VIR_MCAST_NAME_LEN (IFNAMSIZ + 1) +# define VIR_MCAST_INDEX_TOKEN_IDX 0 +# define VIR_MCAST_NAME_TOKEN_IDX 1 +# define VIR_MCAST_USERS_TOKEN_IDX 2 +# define VIR_MCAST_GLOBAL_TOKEN_IDX 3 +# define VIR_MCAST_ADDR_TOKEN_IDX 4 +# define VIR_MCAST_NUM_TOKENS 5 +# define VIR_MCAST_TOKEN_DELIMS " \n" +# define VIR_MCAST_ADDR_LEN (VIR_MAC_HEXLEN + 1) + +typedef struct _virNetDevMcastEntry virNetDevMcastEntry; +typedef virNetDevMcastEntry *virNetDevMcastEntryPtr; +struct _virNetDevMcastEntry { + int index; + char name[VIR_MCAST_NAME_LEN]; + int users; + bool global; + virMacAddr macaddr; +}; + +typedef struct _virNetDevMcast virNetDevMcast; +typedef virNetDevMcast *virNetDevMcastPtr; +struct _virNetDevMcast { + size_t nentries; + virNetDevMcastEntryPtr *entries; +}; + #if defined(HAVE_STRUCT_IFREQ) static int virNetDevSetupControlFull(const char *ifname, struct ifreq *ifr, @@ -1934,6 +1961,266 @@ virNetDevGetLinkInfo(const char *ifname, #endif /* defined(__linux__) */
+#if defined(SIOCADDMULTI) && defined(HAVE_STRUCT_IFREQ) +/** + * virNetDevAddMulti: + * @ifname: interface name to which to add multicast MAC address + * @macaddr: MAC address + * + * This function adds the @macaddr to the multicast list for a given interface + * @ifname. + * + * Returns 0 in case of success or -1 on failure + */ +int virNetDevAddMulti(const char *ifname, + virMacAddrPtr macaddr) +{ + int fd = -1; + int ret = -1; + struct ifreq ifr; + + if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0) + return -1; + + ifr.ifr_hwaddr.sa_family = AF_UNSPEC; + virMacAddrGetRaw(macaddr, (unsigned char *)ifr.ifr_hwaddr.sa_data); + + if (ioctl(fd, SIOCADDMULTI, &ifr) < 0) { + char macstr[VIR_MAC_STRING_BUFLEN]; + virReportSystemError(errno, + _("Cannot add multicast MAC %s on '%s' interface"), + virMacAddrFormat(macaddr, macstr), ifname); + goto cleanup; + } + + ret = 0; + + cleanup: + VIR_FORCE_CLOSE(fd); + return ret; +} +#else +int virNetDevAddMulti(const char *ifname, + virMacAddrPtr macaddr ATTRIBUTE_UNUSED) +{ + char macstr[VIR_MAC_STRING_BUFLEN]; + virReportSystemError(errno, For all of these empty functions used on platforms that don't have the necessary functionality, you need to replace "errno" with "ENOSYS" - since we haven't tried to call anything, errno will not have been set. Oops, cut and paste error.
+ _("Cannot add multicast MAC %s on '%s' interface"), + virMacAddrFormat(macaddr, macstr), ifname); + return -1; +} +#endif + +#if defined(SIOCDELMULTI) && defined(HAVE_STRUCT_IFREQ) +/** + * virNetDevDelMulti: + * @ifname: interface name from which to delete the multicast MAC address + * @macaddr: MAC address + * + * This function deletes the @macaddr from the multicast list for a given + * interface @ifname. + * + * Returns 0 in case of success or -1 on failure + */ +int virNetDevDelMulti(const char *ifname, + virMacAddrPtr macaddr) +{ + int fd = -1; + int ret = -1; + struct ifreq ifr; + + if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0) + return -1; + + ifr.ifr_hwaddr.sa_family = AF_UNSPEC; + virMacAddrGetRaw(macaddr, (unsigned char *)ifr.ifr_hwaddr.sa_data); + + if (ioctl(fd, SIOCDELMULTI, &ifr) < 0) { + char macstr[VIR_MAC_STRING_BUFLEN]; + virReportSystemError(errno, + _("Cannot add multicast MAC %s on '%s' interface"), + virMacAddrFormat(macaddr, macstr), ifname); + goto cleanup; + } + + ret = 0; + + cleanup: + VIR_FORCE_CLOSE(fd); + return ret; +} +#else +int virNetDevDelMulti(const char *ifname, + virMacAddrPtr macaddr ATTRIBUTE_UNUSED) +{ + char macstr[VIR_MAC_STRING_BUFLEN]; + virReportSystemError(errno, ENOSYS Ditto on the cut and paste error.
+ _("Cannot delete multicast MAC %s on '%s' interface"), + virMacAddrFormat(macaddr, macstr), ifname); + return -1; +} +#endif + +static int virNetDevParseMcast(char *buf, virNetDevMcastEntryPtr mcast) +{ + int ifindex; + int num; + char *next; + char *token; + char *saveptr; + char *endptr; + + for (ifindex = 0, next = buf; ifindex < VIR_MCAST_NUM_TOKENS; ifindex++, + next = NULL) { + token = strtok_r(next, VIR_MCAST_TOKEN_DELIMS, &saveptr); + + if (token == NULL) { + virReportSystemError(EINVAL, + _("failed to parse multicast address from '%s'"), + buf); + return -1; + } + + switch (ifindex) { + case VIR_MCAST_INDEX_TOKEN_IDX: + if (virStrToLong_i(token, &endptr, 10, &num) < 0) { + virReportSystemError(EINVAL, + _("Failed to parse index from '%s'"), "Failed to parse interface index from '%s'" I will rephrase the message.
+ buf); + return -1; + + } + + mcast->index = num; + + break; + case VIR_MCAST_NAME_TOKEN_IDX: + if (virStrncpy(mcast->name, token, strlen(token), + VIR_MCAST_NAME_LEN) == NULL) { + virReportSystemError(EINVAL, + _("Failed to parse NIC name from '%s'"), I prefer "network device" rather than "NIC". I will rephrase the message.
+ buf); + return -1; + } + + break; + case VIR_MCAST_USERS_TOKEN_IDX: + if (virStrToLong_i(token, &endptr, 10, &num) < 0) { + virReportSystemError(EINVAL, + _("Failed to parse users from '%s'"), + buf); + return -1; + + } + + mcast->users = num; + + break; + case VIR_MCAST_GLOBAL_TOKEN_IDX: + if (virStrToLong_i(token, &endptr, 10, &num) < 0) { + virReportSystemError(EINVAL, + _("Failed to parse users from '%s'"), + buf); + return -1; + + } + + mcast->global = num; + + break; + case VIR_MCAST_ADDR_TOKEN_IDX: + if (virMacAddrParseHex((const char*)token, + &mcast->macaddr) < 0) { + virReportSystemError(EINVAL, + _("Failed to parse MAC address from '%s'"), + buf); + } + + break; + default: + break; + } + } + + return 0; +} + + +static int virNetDevGetMcast(const char *ifname, + virNetDevMcastPtr mcast) +{ + FILE *file; + const char *path = "/proc/net/dev_mcast"; + char buf[256]; + int ret = -1; + virNetDevMcastEntry entry; + virNetDevMcastEntryPtr *entries = NULL; + size_t nentries = 0; + size_t entries_sz = 0; + size_t i; + mcast->entries = NULL; + mcast->nentries = 0; + + file = fopen(path, "r"); + + if (!file) { + virReportSystemError(errno, + _("cannot open multicast address file %s"), path); + return -1; + } + + while (fgets(buf, sizeof(buf), file)) { We prefer to not use fgets(), but instead use virFileReadAll() to read the entire file into a buffer, then parse it from there. There are several examples of this in the code, for example network/bridge_driver_linux.c:networkCheckRouteCollision().\ I will take a look at that and use virFileReadAll(). For the record, what do you have against fgets?
+ if (virNetDevParseMcast(buf, &entry) < 0) { + goto error; + } + + if (entry.global && STREQ(ifname, entry.name)) { + if (VIR_RESIZE_N(entries, entries_sz, + nentries, 1) < 0) { + virReportSystemError(ENOMEM, + _("Failed to resize multicast MAC address array: " + "ptr=%p, alloc=%zu, count=%zu, add=1"), + entries, entries_sz, nentries); Any memory allocation error will already automatically get an error message logged, so this is redundant. Aside from that, you could instead use VIR_APPEND_ELEMENT() to replace the VIR_RESIZE_N(...., 1) ... memcpy(entries[nentries++], &entry) ... memset(&entry...) (it takes care of all three of those) . Yes, I now see that after looking the VIR_RESIZE_N macro.
Basically, make "entry" a virNetDevMcastEntryPtr entry = NULL;, then each time through the loop allocate a new table entry in "entry" and fill it in, then call
if (VIR_APPEND_ELEMENT(entries, nentries, entry) < 0) goto cleanup;
(and by the way, because normal execution also goes past the "error" label, it should be changed to cleanup in order to avoid misunderstandings) I will take a look at VIR_APPEND_ELEMENT(). Sounds like it might be cleaner and easier to understand.
+ goto error; + } + + if (VIR_ALLOC(entries[nentries]) < 0) { + char addr[VIR_MAC_STRING_BUFLEN]; + virReportSystemError(ENOMEM, + _("Failed to allocate storage for MAC address %s"), + virMacAddrFormat(&mcast->entries[nentries]->macaddr, + addr)); Same here - no need to report the error, as a general error has already been reported, and we know that the source of the error has nothing to do with this specific MAC address, so no need to log an extra message. Yep
+ goto error; + } + + memcpy(entries[nentries++], &entry, + sizeof(virNetDevMcastEntry)); + } + + memset(buf, 0, sizeof(buf)); + memset(&entry, 0, sizeof(virNetDevMcastEntry)); + } + + + mcast->nentries = nentries; + mcast->entries = entries; + ret = 0; + + error: + VIR_FORCE_FCLOSE(file); + + if ((ret < 0) && (nentries > 0)) { If nentries == 0, the following code becomes a NOP anyway, so you can just follow the libvirt convention of unconditionally calling VIR_FREE() (as long as ret < 0) Got it.
+ for (i = 0; i < nentries; i++) { + VIR_FREE(entries[i]); + } + + VIR_FREE(entries); The above should be turned into a helper function called something like virNetDevMcastEntryListFree(), because you're going to need it down below... Okay
+ } + + return ret; +} + + VIR_ENUM_IMPL(virNetDevRxFilterMode, VIR_NETDEV_RX_FILTER_MODE_LAST, "none", @@ -1941,6 +2228,37 @@ VIR_ENUM_IMPL(virNetDevRxFilterMode, "all");
+static int virNetDevGetMulticastTable(const char *ifname, + virNetDevRxFilterPtr filter) +{ + int i; + int ret = -1; + virNetDevMcast mcast; + filter->multicast.nTable = 0; + filter->multicast.table = NULL; + + if (virNetDevGetMcast(ifname, &mcast) < 0) + goto error; + + if (mcast.nentries > 0) { + if (VIR_ALLOC_N(filter->multicast.table, mcast.nentries)) + goto error; + + for (i = 0; i < mcast.nentries; i++) { + virMacAddrSet(&filter->multicast.table[i], + &mcast.entries[i]->macaddr); + } + + filter->multicast.nTable = mcast.nentries; + } + + ret = 0; + + error: Same here - also change this label to cleanup.
Also, you returned without freeing the virNetDevMcastEntry list. You should call the new virNetDevMcastEntryListFree() here. Okay
+ return ret; +} + + virNetDevRxFilterPtr virNetDevRxFilterNew(void) { @@ -1963,3 +2281,45 @@ virNetDevRxFilterFree(virNetDevRxFilterPtr filter) VIR_FREE(filter); } } + + +/** + * virNetDevGetRxFilter: + * This function supplies the RX filter list for a given device interface + * + * @ifname: Name of the interface + * @filter: The RX filter list + * + * Returns 0 or -1 on failure. + */ +int virNetDevGetRxFilter(const char *ifname, + virNetDevRxFilterPtr *filter) +{ + int ret = -1; + virNetDevRxFilterPtr fil = virNetDevRxFilterNew(); + + if (!fil) { + virReportSystemError(ENOMEM, + _("Failed to allocate filter for %s interface"), + ifname); No need to log an extra error here - it's already done when the memory allocation fails. Got it.
+ + } + + if (virNetDevGetMAC(ifname, &fil->mac)) + goto cleanup; Indentation problem ^^ Will fix.
+ + if (virNetDevGetMulticastTable(ifname, fil)) + goto cleanup; + + ret = 0; + + cleanup: remove the blank line between ret = 0 and cleanup: to reduce the chance that someone will attempt to add new code in between them. Okay
+ if (ret < 0) { + virNetDevRxFilterFree(fil); + fil = NULL; + } + + *filter = fil; + + return ret; +} diff --git a/src/util/virnetdev.h b/src/util/virnetdev.h index 2a6e67d..1d274b3 100644 --- a/src/util/virnetdev.h +++ b/src/util/virnetdev.h @@ -30,6 +30,7 @@ # include "virmacaddr.h" # include "virpci.h" # include "device_conf.h" +# include "virutil.h"
# ifdef HAVE_STRUCT_IFREQ typedef struct ifreq virIfreq; @@ -189,5 +190,15 @@ int virNetDevGetLinkInfo(const char *ifname, virNetDevRxFilterPtr virNetDevRxFilterNew(void) ATTRIBUTE_RETURN_CHECK; void virNetDevRxFilterFree(virNetDevRxFilterPtr filter); +int virNetDevGetRxFilter(const char *ifname, + virNetDevRxFilterPtr *filter) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; + +int virNetDevAddMulti(const char *ifname, + virMacAddrPtr macaddr) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; +int virNetDevDelMulti(const char *ifname, + virMacAddrPtr macaddr) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK;
#endif /* __VIR_NETDEV_H__ */ Otherwise it looks okay. As we've discussed, it would be nice to use netlink instead of /proc/net and ioctl(), but that can be added later. I agree. When I originally started working on this, that was my plan, however; after spending days trying to understand how to use netlink to do retrieve the multicast list, I gave up when I found that iproute2 does it by reading the file. Can you point me to some good, detailed netlink documentation? I've googled for it, looked at code examples and experimented by writing code snippets and have not been able to figure out how to retrieve the multicast list using netlink.
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

On 10/08/2014 01:41 PM, Tony Krowiak wrote:
Otherwise it looks okay. As we've discussed, it would be nice to use netlink instead of /proc/net and ioctl(), but that can be added later. I agree. When I originally started working on this, that was my plan, however; after spending days trying to understand how to use netlink to do retrieve the multicast list, I gave up when I found that iproute2 does it by reading the file. Can you point me to some good, detailed netlink documentation? I've googled for it, looked at code examples and experimented by writing code snippets and have not been able to figure out how to retrieve the multicast list using netlink.
No :-( That is a *big* problem with netlink, and I went through the same journey as you (although probably spent less time, as you sent your first message when I'd only been looking at it for a day or so). I *think* there is a libnl cache that can be used to retrieve the multicast list (look for the libnl-devel mailing list archives for a message I sent there several days ago, along with a couple of responses), but it's definitely not as straightforward as reading a file in /proc.

From: Tony Krowiak <akrowiak@linux.vnet.ibm.com> This patch adds functionality to processNicRxFilterChangedEvent(). The old and new multicast lists are compared and the filters in the macvtap are programmed to match the guest's filters. Signed-off-by: Tony Krowiak <akrowiak@linux.vnet.ibm.com> --- src/qemu/qemu_driver.c | 138 +++++++++++++++++++++++++++++++++++++++-------- 1 files changed, 114 insertions(+), 24 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 4e2b356..7ff9c38 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -4147,6 +4147,106 @@ processDeviceDeletedEvent(virQEMUDriverPtr driver, static void +syncNicRxFilterMacAddr(char *ifname, virNetDevRxFilterPtr guestFilter, + virNetDevRxFilterPtr hostFilter) +{ + char newMacStr[VIR_MAC_STRING_BUFLEN]; + + if (virMacAddrCmp(&hostFilter->mac, &guestFilter->mac)) { + virMacAddrFormat(&guestFilter->mac, newMacStr); + + /* set new MAC address from guest to associated macvtap device */ + if (virNetDevSetMAC(ifname, &guestFilter->mac)) { + VIR_WARN("Couldn't set new MAC address %s to device %s " + "while responding to NIC_RX_FILTER_CHANGED", + newMacStr, ifname); + } else { + VIR_DEBUG("device %s MAC address set to %s", ifname, newMacStr); + } + } +} + + +static void +syncNicRxFilterGuestMulticast(char *ifname, virNetDevRxFilterPtr guestFilter, + virNetDevRxFilterPtr hostFilter) +{ + size_t i, j; + bool found; + char macstr[VIR_MAC_STRING_BUFLEN]; + + for (i = 0; i < guestFilter->multicast.nTable; i++) { + found = false; + + for (j = 0; j < hostFilter->multicast.nTable; j++) { + if (virMacAddrCmp(&guestFilter->multicast.table[i], + &hostFilter->multicast.table[j]) == 0) { + found = true; + break; + } + } + + if (!found) { + virMacAddrFormat(&guestFilter->multicast.table[i], macstr); + + if (virNetDevAddMulti(ifname, &guestFilter->multicast.table[i])) { + VIR_WARN("Couldn't add new multicast MAC address %s to " + "device %s while responding to NIC_RX_FILTER_CHANGED", + macstr, ifname); + } else { + VIR_DEBUG("Added multicast MAC %s to %s interface", + macstr, ifname); + } + } + } +} + + +static void +syncNicRxFilterHostMulticast(char *ifname, virNetDevRxFilterPtr guestFilter, + virNetDevRxFilterPtr hostFilter) +{ + size_t i, j; + bool found; + char macstr[VIR_MAC_STRING_BUFLEN]; + + for (i = 0; i < hostFilter->multicast.nTable; i++) { + found = false; + + for (j = 0; j < guestFilter->multicast.nTable; j++) { + if (virMacAddrCmp(&hostFilter->multicast.table[i], + &guestFilter->multicast.table[j]) == 0) { + found = true; + break; + } + } + + if (!found) { + virMacAddrFormat(&hostFilter->multicast.table[i], macstr); + + if (virNetDevDelMulti(ifname, &hostFilter->multicast.table[i])) { + VIR_WARN("Couldn't delete multicast MAC address %s from " + "device %s while responding to NIC_RX_FILTER_CHANGED", + macstr, ifname); + } else { + VIR_DEBUG("Deleted multicast MAC %s from %s interface", + macstr, ifname); + } + } + } +} + + +static void +syncNicRxFilterMulticast(char *ifname, + virNetDevRxFilterPtr guestFilter, + virNetDevRxFilterPtr hostFilter) +{ + syncNicRxFilterGuestMulticast(ifname, guestFilter, hostFilter); + syncNicRxFilterHostMulticast(ifname, guestFilter, hostFilter); +} + +static void processNicRxFilterChangedEvent(virQEMUDriverPtr driver, virDomainObjPtr vm, char *devAlias) @@ -4155,9 +4255,8 @@ processNicRxFilterChangedEvent(virQEMUDriverPtr driver, qemuDomainObjPrivatePtr priv = vm->privateData; virDomainDeviceDef dev; virDomainNetDefPtr def; - virNetDevRxFilterPtr filter = NULL; - virMacAddr oldMAC; - char newMacStr[VIR_MAC_STRING_BUFLEN]; + virNetDevRxFilterPtr guestFilter = NULL; + virNetDevRxFilterPtr hostFilter = NULL; int ret; VIR_DEBUG("Received NIC_RX_FILTER_CHANGED event for device %s " @@ -4202,37 +4301,27 @@ processNicRxFilterChangedEvent(virQEMUDriverPtr driver, "device %s in domain %s", def->info.alias, vm->def->name); qemuDomainObjEnterMonitor(driver, vm); - ret = qemuMonitorQueryRxFilter(priv->mon, devAlias, &filter); + ret = qemuMonitorQueryRxFilter(priv->mon, devAlias, &guestFilter); qemuDomainObjExitMonitor(driver, vm); if (ret < 0) goto endjob; - virMacAddrFormat(&filter->mac, newMacStr); - if (virDomainNetGetActualType(def) == VIR_DOMAIN_NET_TYPE_DIRECT) { - /* For macvtap connections, set the macvtap device's MAC - * address to match that of the guest device. - */ - - if (virNetDevGetMAC(def->ifname, &oldMAC) < 0) { - VIR_WARN("Couldn't get current MAC address of device %s " + if (virNetDevGetRxFilter(def->ifname, &hostFilter)) { + VIR_WARN("Couldn't get current RX filter for device %s " "while responding to NIC_RX_FILTER_CHANGED", def->ifname); goto endjob; } - if (virMacAddrCmp(&oldMAC, &filter->mac)) { - /* set new MAC address from guest to associated macvtap device */ - if (virNetDevSetMAC(def->ifname, &filter->mac) < 0) { - VIR_WARN("Couldn't set new MAC address %s to device %s " - "while responding to NIC_RX_FILTER_CHANGED", - newMacStr, def->ifname); - } else { - VIR_DEBUG("device %s MAC address set to %s", - def->ifname, newMacStr); - } - } + /* For macvtap connections, set the following macvtap network device + * attributes to match those of the guest network device: + * - MAC address + * - Multicast MAC address table + */ + syncNicRxFilterMacAddr(def->ifname, guestFilter, hostFilter); + syncNicRxFilterMulticast(def->ifname, guestFilter, hostFilter); } endjob: @@ -4242,7 +4331,8 @@ processNicRxFilterChangedEvent(virQEMUDriverPtr driver, ignore_value(qemuDomainObjEndJob(driver, vm)); cleanup: - virNetDevRxFilterFree(filter); + virNetDevRxFilterFree(hostFilter); + virNetDevRxFilterFree(guestFilter); VIR_FREE(devAlias); virObjectUnref(cfg); } -- 1.7.1

On 10/06/2014 05:37 PM, akrowiak@linux.vnet.ibm.com wrote:
From: Tony Krowiak <akrowiak@linux.vnet.ibm.com>
This patch adds functionality to processNicRxFilterChangedEvent(). The old and new multicast lists are compared and the filters in the macvtap are programmed to match the guest's filters.
Signed-off-by: Tony Krowiak <akrowiak@linux.vnet.ibm.com> --- src/qemu/qemu_driver.c | 138 +++++++++++++++++++++++++++++++++++++++-------- 1 files changed, 114 insertions(+), 24 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 4e2b356..7ff9c38 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -4147,6 +4147,106 @@ processDeviceDeletedEvent(virQEMUDriverPtr driver,
static void +syncNicRxFilterMacAddr(char *ifname, virNetDevRxFilterPtr guestFilter, + virNetDevRxFilterPtr hostFilter) +{ + char newMacStr[VIR_MAC_STRING_BUFLEN]; + + if (virMacAddrCmp(&hostFilter->mac, &guestFilter->mac)) { + virMacAddrFormat(&guestFilter->mac, newMacStr); + + /* set new MAC address from guest to associated macvtap device */ + if (virNetDevSetMAC(ifname, &guestFilter->mac)) { + VIR_WARN("Couldn't set new MAC address %s to device %s " + "while responding to NIC_RX_FILTER_CHANGED", + newMacStr, ifname); + } else { + VIR_DEBUG("device %s MAC address set to %s", ifname, newMacStr); + } + } +} + + +static void +syncNicRxFilterGuestMulticast(char *ifname, virNetDevRxFilterPtr guestFilter, + virNetDevRxFilterPtr hostFilter) +{ + size_t i, j; + bool found; + char macstr[VIR_MAC_STRING_BUFLEN]; + + for (i = 0; i < guestFilter->multicast.nTable; i++) { + found = false; + + for (j = 0; j < hostFilter->multicast.nTable; j++) { + if (virMacAddrCmp(&guestFilter->multicast.table[i], + &hostFilter->multicast.table[j]) == 0) { + found = true; + break; + } + } + + if (!found) { + virMacAddrFormat(&guestFilter->multicast.table[i], macstr); + + if (virNetDevAddMulti(ifname, &guestFilter->multicast.table[i])) { + VIR_WARN("Couldn't add new multicast MAC address %s to " + "device %s while responding to NIC_RX_FILTER_CHANGED", + macstr, ifname); + } else { + VIR_DEBUG("Added multicast MAC %s to %s interface", + macstr, ifname); + } + } + } +} + + +static void +syncNicRxFilterHostMulticast(char *ifname, virNetDevRxFilterPtr guestFilter, + virNetDevRxFilterPtr hostFilter) +{ + size_t i, j; + bool found; + char macstr[VIR_MAC_STRING_BUFLEN]; + + for (i = 0; i < hostFilter->multicast.nTable; i++) { + found = false; + + for (j = 0; j < guestFilter->multicast.nTable; j++) { + if (virMacAddrCmp(&hostFilter->multicast.table[i], + &guestFilter->multicast.table[j]) == 0) { + found = true; + break; + } + } + + if (!found) { + virMacAddrFormat(&hostFilter->multicast.table[i], macstr); + + if (virNetDevDelMulti(ifname, &hostFilter->multicast.table[i])) { + VIR_WARN("Couldn't delete multicast MAC address %s from " + "device %s while responding to NIC_RX_FILTER_CHANGED", + macstr, ifname); + } else { + VIR_DEBUG("Deleted multicast MAC %s from %s interface", + macstr, ifname); + } + } + } +} + + +static void +syncNicRxFilterMulticast(char *ifname, + virNetDevRxFilterPtr guestFilter, + virNetDevRxFilterPtr hostFilter) +{ + syncNicRxFilterGuestMulticast(ifname, guestFilter, hostFilter); + syncNicRxFilterHostMulticast(ifname, guestFilter, hostFilter);
Interesting method. I had planned to sort both lists, then do a single loop that went through the two in parallel, deleting/adding from the host interface filter at each step as necessary. Yours works, too, though. :-) ACK to this patch. I'll push it along with 1/2 once a new eversion of that patch has gotten ACK. BTW, once we're programming the multicast filter, do we need to set any other mode of the macvtap device in order for this to have a positive impact on performance? Or does multicast simply not work properly without this patch (I don't use multicast for anything, and don't have any tests setup to use multicast).
+} + +static void processNicRxFilterChangedEvent(virQEMUDriverPtr driver, virDomainObjPtr vm, char *devAlias) @@ -4155,9 +4255,8 @@ processNicRxFilterChangedEvent(virQEMUDriverPtr driver, qemuDomainObjPrivatePtr priv = vm->privateData; virDomainDeviceDef dev; virDomainNetDefPtr def; - virNetDevRxFilterPtr filter = NULL; - virMacAddr oldMAC; - char newMacStr[VIR_MAC_STRING_BUFLEN]; + virNetDevRxFilterPtr guestFilter = NULL; + virNetDevRxFilterPtr hostFilter = NULL; int ret;
VIR_DEBUG("Received NIC_RX_FILTER_CHANGED event for device %s " @@ -4202,37 +4301,27 @@ processNicRxFilterChangedEvent(virQEMUDriverPtr driver, "device %s in domain %s", def->info.alias, vm->def->name);
qemuDomainObjEnterMonitor(driver, vm); - ret = qemuMonitorQueryRxFilter(priv->mon, devAlias, &filter); + ret = qemuMonitorQueryRxFilter(priv->mon, devAlias, &guestFilter); qemuDomainObjExitMonitor(driver, vm); if (ret < 0) goto endjob;
- virMacAddrFormat(&filter->mac, newMacStr); - if (virDomainNetGetActualType(def) == VIR_DOMAIN_NET_TYPE_DIRECT) {
- /* For macvtap connections, set the macvtap device's MAC - * address to match that of the guest device. - */ - - if (virNetDevGetMAC(def->ifname, &oldMAC) < 0) { - VIR_WARN("Couldn't get current MAC address of device %s " + if (virNetDevGetRxFilter(def->ifname, &hostFilter)) { + VIR_WARN("Couldn't get current RX filter for device %s " "while responding to NIC_RX_FILTER_CHANGED", def->ifname); goto endjob; }
- if (virMacAddrCmp(&oldMAC, &filter->mac)) { - /* set new MAC address from guest to associated macvtap device */ - if (virNetDevSetMAC(def->ifname, &filter->mac) < 0) { - VIR_WARN("Couldn't set new MAC address %s to device %s " - "while responding to NIC_RX_FILTER_CHANGED", - newMacStr, def->ifname); - } else { - VIR_DEBUG("device %s MAC address set to %s", - def->ifname, newMacStr); - } - } + /* For macvtap connections, set the following macvtap network device + * attributes to match those of the guest network device: + * - MAC address + * - Multicast MAC address table + */ + syncNicRxFilterMacAddr(def->ifname, guestFilter, hostFilter); + syncNicRxFilterMulticast(def->ifname, guestFilter, hostFilter); }
endjob: @@ -4242,7 +4331,8 @@ processNicRxFilterChangedEvent(virQEMUDriverPtr driver, ignore_value(qemuDomainObjEndJob(driver, vm));
cleanup: - virNetDevRxFilterFree(filter); + virNetDevRxFilterFree(hostFilter); + virNetDevRxFilterFree(guestFilter); VIR_FREE(devAlias); virObjectUnref(cfg); }

From: Tony Krowiak <akrowiak@linux.vnet.ibm.com>
This patch adds functionality to processNicRxFilterChangedEvent(). The old and new multicast lists are compared and the filters in the macvtap are programmed to match the guest's filters.
Signed-off-by: Tony Krowiak <akrowiak@linux.vnet.ibm.com> --- src/qemu/qemu_driver.c | 138 +++++++++++++++++++++++++++++++++++++++-------- 1 files changed, 114 insertions(+), 24 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 4e2b356..7ff9c38 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -4147,6 +4147,106 @@ processDeviceDeletedEvent(virQEMUDriverPtr driver,
static void +syncNicRxFilterMacAddr(char *ifname, virNetDevRxFilterPtr guestFilter, + virNetDevRxFilterPtr hostFilter) +{ + char newMacStr[VIR_MAC_STRING_BUFLEN]; + + if (virMacAddrCmp(&hostFilter->mac, &guestFilter->mac)) { + virMacAddrFormat(&guestFilter->mac, newMacStr); + + /* set new MAC address from guest to associated macvtap device */ + if (virNetDevSetMAC(ifname, &guestFilter->mac)) { + VIR_WARN("Couldn't set new MAC address %s to device %s " + "while responding to NIC_RX_FILTER_CHANGED", + newMacStr, ifname); + } else { + VIR_DEBUG("device %s MAC address set to %s", ifname, newMacStr); + } + } +} + + +static void +syncNicRxFilterGuestMulticast(char *ifname, virNetDevRxFilterPtr guestFilter, + virNetDevRxFilterPtr hostFilter) +{ + size_t i, j; + bool found; + char macstr[VIR_MAC_STRING_BUFLEN]; + + for (i = 0; i < guestFilter->multicast.nTable; i++) { + found = false; + + for (j = 0; j < hostFilter->multicast.nTable; j++) { + if (virMacAddrCmp(&guestFilter->multicast.table[i], + &hostFilter->multicast.table[j]) == 0) { + found = true; + break; + } + } + + if (!found) { + virMacAddrFormat(&guestFilter->multicast.table[i], macstr); + + if (virNetDevAddMulti(ifname, &guestFilter->multicast.table[i])) { + VIR_WARN("Couldn't add new multicast MAC address %s to " + "device %s while responding to NIC_RX_FILTER_CHANGED", + macstr, ifname); + } else { + VIR_DEBUG("Added multicast MAC %s to %s interface", + macstr, ifname); + } + } + } +} + + +static void +syncNicRxFilterHostMulticast(char *ifname, virNetDevRxFilterPtr guestFilter, + virNetDevRxFilterPtr hostFilter) +{ + size_t i, j; + bool found; + char macstr[VIR_MAC_STRING_BUFLEN]; + + for (i = 0; i < hostFilter->multicast.nTable; i++) { + found = false; + + for (j = 0; j < guestFilter->multicast.nTable; j++) { + if (virMacAddrCmp(&hostFilter->multicast.table[i], + &guestFilter->multicast.table[j]) == 0) { + found = true; + break; + } + } + + if (!found) { + virMacAddrFormat(&hostFilter->multicast.table[i], macstr); + + if (virNetDevDelMulti(ifname, &hostFilter->multicast.table[i])) { + VIR_WARN("Couldn't delete multicast MAC address %s from " + "device %s while responding to NIC_RX_FILTER_CHANGED", + macstr, ifname); + } else { + VIR_DEBUG("Deleted multicast MAC %s from %s interface", + macstr, ifname); + } + } + } +} + + +static void +syncNicRxFilterMulticast(char *ifname, + virNetDevRxFilterPtr guestFilter, + virNetDevRxFilterPtr hostFilter) +{ + syncNicRxFilterGuestMulticast(ifname, guestFilter, hostFilter); + syncNicRxFilterHostMulticast(ifname, guestFilter, hostFilter); Interesting method. I had planned to sort both lists, then do a single loop that went through the two in parallel, deleting/adding from the host interface filter at each step as necessary. Yours works, too,
On 10/06/2014 05:37 PM, akrowiak@linux.vnet.ibm.com wrote: though. :-)
ACK to this patch. I'll push it along with 1/2 once a new eversion of that patch has gotten ACK.
BTW, once we're programming the multicast filter, do we need to set any other mode of the macvtap device in order for this to have a positive impact on performance? Or does multicast simply not work properly without this patch (I don't use multicast for anything, and don't have any tests setup to use multicast). To be honest, the only testing I did was to make sure that if I added or deleted a multicast MAC on the guest, that it showed up in the macvtap
On 10/08/2014 03:15 PM, Laine Stump wrote: list on the host. I am not too networking literate and rely on our system test team to flesh out those issues. The next thing on my todo list is to synchronize changes to the guest network device flags - such as the PROMISCUOUS, MULTICAST, ALLMULTI and BROADCAST flags - to the macvtap device. In fact, I've already written the virnetdev functions to do that, but the ioctl's I used don't always do what they seem to imply. I do have a question related to my (mis)understanding of the data returned from the query_rx_filter call vis a vis these device flags. These are my assumptions regarding to what network device flags the following query-rx-filter data elements equate: * promiscuous : true => ip link set macvtap0 promisc on * promiscuous : false => ip link set macvtap0 promisc off * multicast : normal => ip link set macvtap0 multicast on * multicast : all => ip link set macvtap0 allmulticast on * multicast : none => ip link set macvtap0 multicast off, ip link set macvtap0 allmulticast off * broadcast-allowed : true => ???????? I used the SIOCSIFFLAGS and SIOCGIFFLAGS ioctl's to set and get the network device flags respectively, as follows: * IFF_PROMISC to get/set promiscuous flag * IFF_MULTICAST to get/set multicast flag * IFF_ALLMULTI to get/set allmulticast flag * IFF_BROADCAST to get/set the broadcast flag The thing is, I can set both the MULTICAST and ALLMULTI flags using the *ip link* command, but the normal, all and none values for multicast do not lend themselves to setting these flags independently. Maybe thats because I am misunderstanding what they mean? Also, using the ioctl to set the BROADCAST flag does not seem to work. Any light you can shed on this would be helpful. Thanks.
+} + +static void processNicRxFilterChangedEvent(virQEMUDriverPtr driver, virDomainObjPtr vm, char *devAlias) @@ -4155,9 +4255,8 @@ processNicRxFilterChangedEvent(virQEMUDriverPtr driver, qemuDomainObjPrivatePtr priv = vm->privateData; virDomainDeviceDef dev; virDomainNetDefPtr def; - virNetDevRxFilterPtr filter = NULL; - virMacAddr oldMAC; - char newMacStr[VIR_MAC_STRING_BUFLEN]; + virNetDevRxFilterPtr guestFilter = NULL; + virNetDevRxFilterPtr hostFilter = NULL; int ret;
VIR_DEBUG("Received NIC_RX_FILTER_CHANGED event for device %s " @@ -4202,37 +4301,27 @@ processNicRxFilterChangedEvent(virQEMUDriverPtr driver, "device %s in domain %s", def->info.alias, vm->def->name);
qemuDomainObjEnterMonitor(driver, vm); - ret = qemuMonitorQueryRxFilter(priv->mon, devAlias, &filter); + ret = qemuMonitorQueryRxFilter(priv->mon, devAlias, &guestFilter); qemuDomainObjExitMonitor(driver, vm); if (ret < 0) goto endjob;
- virMacAddrFormat(&filter->mac, newMacStr); - if (virDomainNetGetActualType(def) == VIR_DOMAIN_NET_TYPE_DIRECT) {
- /* For macvtap connections, set the macvtap device's MAC - * address to match that of the guest device. - */ - - if (virNetDevGetMAC(def->ifname, &oldMAC) < 0) { - VIR_WARN("Couldn't get current MAC address of device %s " + if (virNetDevGetRxFilter(def->ifname, &hostFilter)) { + VIR_WARN("Couldn't get current RX filter for device %s " "while responding to NIC_RX_FILTER_CHANGED", def->ifname); goto endjob; }
- if (virMacAddrCmp(&oldMAC, &filter->mac)) { - /* set new MAC address from guest to associated macvtap device */ - if (virNetDevSetMAC(def->ifname, &filter->mac) < 0) { - VIR_WARN("Couldn't set new MAC address %s to device %s " - "while responding to NIC_RX_FILTER_CHANGED", - newMacStr, def->ifname); - } else { - VIR_DEBUG("device %s MAC address set to %s", - def->ifname, newMacStr); - } - } + /* For macvtap connections, set the following macvtap network device + * attributes to match those of the guest network device: + * - MAC address + * - Multicast MAC address table + */ + syncNicRxFilterMacAddr(def->ifname, guestFilter, hostFilter); + syncNicRxFilterMulticast(def->ifname, guestFilter, hostFilter); }
endjob: @@ -4242,7 +4331,8 @@ processNicRxFilterChangedEvent(virQEMUDriverPtr driver, ignore_value(qemuDomainObjEndJob(driver, vm));
cleanup: - virNetDevRxFilterFree(filter); + virNetDevRxFilterFree(hostFilter); + virNetDevRxFilterFree(guestFilter); VIR_FREE(devAlias); virObjectUnref(cfg); } -- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list
participants (4)
-
akrowiak@linux.vnet.ibm.com
-
Eric Blake
-
Laine Stump
-
Tony Krowiak